Execute multiple line PowerShell Script from C# - c#

I am new to PowerShell. I am trying to execute PowerShell script from C#. PS Script that I have written transfer xml file from host computer (running PS script) to a remote computer. Script is as follows
$Username = "User"
$Password = "Pass"
$SecurePass = ConvertTo-SecureString -AsPlainText $Password -Force"
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$SecurePass"
$contents = [IO.File]::ReadAllBytes("D:\MyFile.xml")
Invoke-Command -ComputerName ComputerName -Credential $Cred {[IO.File]::WriteAllBytes("D:\MyFile.xml",$using:contents)}
I store username and password of remote computer, to which I want to transfer my file, in variables. Convert password to secure string and create a credential object containing username and password. Use credential to copy the contents of file from my computer to remote computer.
This code works fine if I execute these commands one by one from my powershell or by running these lines from a .ps1 powershell script from my computer.
I used following code in C# to execute above script and transfer the file.
Runspace runSpace = RunspaceFactory.CreateRunspace();
Pipeline pipeline = runSpace.CreatePipeline();
runSpace.Open();
string script = "";
script = "$Username = \"User\"\n";
script = script + "$Password = \"Pass\"\n";
script = script + "$SecurePass = ConvertTo-SecureString -AsPlainText $Password -Force\n";
script = script + "$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$SecurePass\n";
script = script + "$contents = [IO.File]::ReadAllBytes(\"D:\\MyFile.xml\")\n";
script = script + "Invoke-Command -ComputerName ComputerName -Credential $Cred {[IO.File]::WriteAllBytes(\"D:\\MyFile.xml\",$using:contents)}\n";
// pipeline.Runspace.SessionStateProxy.SetVariable
pipeline.AddScript(script);
Collection<PSObject> results = pipeline.Invoke();
However, it did not transfer the file. So, I thought I need to Add each line in script one by one to the pipeline and then execute it. So I changed it to following.
string[] commands = script.Split('\n');
foreach (string command in commands)
{
pipeline.AddScript(command);
}
Collection<PSObject> results = pipeline.Invoke();
It did not work either. I executed single Invoke-Command from C# code on my computer and it worked fine. Also, if I try to execute other single PS commands they work. So I assessed that setting variable values like this $User = "user" is not working through C#.
I used SessionStateSetProcy.SetVariable to Store variable values in powershell session as follows.
pipeline.Runspace.SessionStateProxy.SetVariable("User", "User");
pipeline.Runspace.SessionStateProxy.SetVariable("Password", "Pass");
var value = pipeline.Runspace.SessionStateProxy.GetVariable("Password");
Console.WriteLine(value);
So, I was able to set value and retrieve as well. But, since I was using Other cmdlets like ConvertTo-SecureString, I realized I need to execute these cmdlets separately in my code.
I finally I came up with following piece of code.
pipeline.Runspace.SessionStateProxy.SetVariable("User", "User");
pipeline.Runspace.SessionStateProxy.SetVariable("Password", "Pass");
Command command = new Command("ConvertTo-SecureString");
string pass_value = pipeline.Runspace.SessionStateProxy.PSVariable.GetValue("Password").ToString();
command.Parameters.Add("AsPlainText",pass_value);
command.Parameters.Add("Force");
pipeline.Runspace.SessionStateProxy.SetVariable("SecurePass",command);
/*Build a command2*/
Command command1 = new Command("New-Object");
string user_name = pipeline.Runspace.SessionStateProxy.PSVariable.GetValue("User").ToString();
command1.Parameters.Add("TypeName", "System.Management.Automation.PSCredential");
string args = user_name + " , "+pass_value;
command1.Parameters.Add("-ArgumentList", args);
pipeline.Runspace.SessionStateProxy.SetVariable("Cred", command1);
byte[] wifiXML = System.IO.File.ReadAllBytes(#"D:\MyFile.xml");
pipeline.Runspace.SessionStateProxy.SetVariable("contents", wifiXML);
Object a = pipeline.Runspace.SessionStateProxy.PSVariable.GetValue("contents");
string script = "Invoke-Command -ComputerName ComputerName -Credential $Cred {[IO.File]::WriteAllBytes(\"D:\MyFile.xml\",$using:contents)}";
pipeline.Commands.AddScript(script);
Collection<PSObject> results = pipeline.Invoke();
It gave some weird error in System.Management.Automation. I know in SetVariable I am doing it wrong by passing command variable.
I have tried PowerShell ps = PowerShell.Create() instead of pipeline as well and tried ps.AddCommand and ps.AddParameters followed by ps.AddArgument as well. But I am not sure that once I run cmdlet ConvertTo-SecureString through ps.AddCommad, ps.AddArgument etc., how should I set it's output value to $Cred variable?
I am aware that solution to this won't be this complicated and someone must have executed multiple line powershell script through C#. All I want to accomplish is execute the .ps1 script lines mentioned in very start from C# code.I have tried bunch of methods including mentioned above to no use, so any help related to it would be much appreciated?

Try in this way
string script = #"
$Username = 'User'
$Password = 'Password'
$SecurePass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$SecurePass
$contents = [IO.File]::ReadAllBytes('D:\MyFile.xml')
Invoke-Command -ComputerName ComputerName -Credential $Cred {[IO.File]::WriteAllBytes('D:\\MyFile.xml',$using:contents)}
";
pipeline.Commands.AddScript(script);
Collection<PSObject> results = pipeline.Invoke();

Related

The term 'Get-SmbOpenFile' is not recognized when running script from C# code

I have a PowerShell script that closes opened shared files on remote server
$User = "user"
$PWord = ConvertTo-SecureString -String "pas" -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
$cim = New-CimSession -ComputerName epicorlive -Credential $Credential
Get-SmbOpenFile -CimSession $cim | Where-Object Path -like "*dll\Form.dll" | Close-SmbOpenFile -Force
Running this script from native PowerShell window works fine.
but when i try to run from C# code there is an error:
The term 'Get-SmbOpenFile' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again
string scriptText = //"\r\n" +
"$User = \"user\"\r\n" +
"$PWord = ConvertTo-SecureString -String \"pas\" -AsPlainText -Force\r\n" +
"$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord\r\n" +
"$cim = New-CimSession -ComputerName epicorlive -Credential $Credential\r\n" +
"Get-SmbOpenFile -CimSession $cim | Where-Object Path -like \"*dll\\" + file + "\" | Close-SmbOpenFile -Force";
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
Why is this command not recognized running from C#?
How can i fix it?
If there is some other options to close opened shared files on remote computer from C# code, I will be glad to hear about them :)

Get powershell script text that is executed before calling System.Management.Automation.PowerShell.Invoke()

Is there a way to get text of executed powershell script in C#.
For example:
using (PowerShell powerShellInstance = PowerShell.Create())
{
Collection<PSObject> result;
// psScript object is script text loaded from some .ps1 file
powerShellInstance.AddScript(psScript);
//parameters object is list of passed parameters
powerShellInstance.AddParameters(parameters);
result = powerShellInstance.Invoke();
}
Can we get executed script text anywhere in above code so we could just copy it and try to execute it directly in powershell.
EDIT:
My .ps1 script file looks like this:
param($CustomerPrimaryO365Domain, $AdminUsername, $AdminPassword, $Domain)
#####################################################################################################################################################################
function Get-CustomerDomain()
#####################################################################################################################################################################
{
$O365Cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AdminUsername,(ConvertTo-SecureString -AsPlainText -Force -String $AdminPassword)
Connect-MsolService -Credential $O365Cred
$tenID=(Get-MSOLPartnerContract -Domain $CustomerPrimaryO365Domain).tenantId.guid
Get-MsolDomain -DomainName $Domain -TenantId $tenID
}
Import-Module MSOnline
Get-CustomerDomain
But when it is executed it needs to look something like this:
#####################################################################################################################################################################
function Get-CustomerDomain()
#####################################################################################################################################################################
{
$O365Cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList "MyAdmin#test.test",(ConvertTo-SecureString -AsPlainText -Force -String "testpassword")
Connect-MsolService -Credential $O365Cred
$tenID=(Get-MSOLPartnerContract -Domain "mydomain.onmicrosoft.com").tenantId.guid
Get-MsolDomain -DomainName "testdomain.info" -TenantId $tenID
}
Import-Module MSOnline
Get-CustomerDomain
Is there a way to get "executed" script from powerShellInstance object.

Azure site created from PowerShell in C# not being found

I'm having a strange problem. I'm trying to create an Azure site by running PowerShell script from my C# code. I open the script file, modify it to insert the necessary data, save it and then run it. This is the script:
$azureUserName = "myusername"
$azurePass = "mypass"
$siteName = "myurl"
$clientID = "*******"
$clientSecret = "********"
$subscriptionID = "*******"
$securePassword = ConvertTo-SecureString -String $azurePass -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($azureUserName, $securePassword)
Add-AzureAccount -Credential $cred
Select-AzureSubscription -SubscriptionId $subscriptionID
New-AzureWebsite $siteName | Set-AzureWebsite -WebSocketsEnabled $true -AppSettings #{"ClientID"=$clientID;"ClientSecret"=$clientSecret}
#Get the website's propeties.
$website = Get-AzureWebSite -Name $siteName
$siteProperties = $website.SiteProperties.Properties
#extract url, username and password
$url = ($siteProperties | ?{ $_.Name -eq "RepositoryURI" }).Value.ToString() + "/MsDeploy.axd"
$userName = ($siteProperties | ?{ $_.Name -eq "PublishingUsername" }).Value
$pw = ($siteProperties | ?{ $_.Name -eq "PublishingPassword" }).Value
#build the command line argument for the deploy.cmd :
$argFormat = ' /y /m:"{0}" -allowUntrusted /u:"{1}" /p:"{2}" /a:Basic'
$arguments = [string]::Format($argFormat, $url, $userName, $pw)
The thing is. If I modify the data manually and then run it from PowerShell ISE the site is being created and is running perfectly. If I modify the data from code and run it, the site is also being created and seems to be working ok but when I try navigating to it it is not being found.
This is how I run the script:
using (PowerShell powershell = PowerShell.Create())
{
String file = System.IO.File.ReadAllText(System.IO.Path.Combine(filesPath, "CreateAzureSite.ps1"),Encoding.UTF8);
powershell.AddScript(file);
Collection<PSObject> results = powershell.Invoke();
}
Use the .NET management libraries:
Documentation: https://msdn.microsoft.com/en-us/library/azure/microsoft.windowsazure.management.websites(v=azure.11).aspx
Nuget: https://www.nuget.org/packages/Microsoft.WindowsAzure.Management.WebSites/4.4.1-prerelease

Mapping a drive with powershell in a scheduled task via Start-Job in c#

I'm currently trying to run a powershell script on my web service, which maps a drive to a shared folder on another system and copies a folder to it. The strange problem I currently have is that the script seems to be executed just fine, however the job in which the script is running doesn't seem to finish properly. Therefore I have to set a timeout for the task to be finished by force, otherwise it won't finish at all. However this is not really what I want, since it can have some nasty side effects if the script takes longer than expected etc. On the other hand I want to have the execution as fast as possible in the given scenario, so I'd like to let the script finished "naturally".
This is my current setup
The C# web service calls the powershell script like this:
public Collection<PSObject> executeCommand(String pCommand, String pName)
{
// Call the script
var runspaceConfiguration = RunspaceConfiguration.Create();
var runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
var pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(pCommand);
pipeline.Commands.AddScript("Wait-Job -Name " + pName + " -Timeout 60");
pipeline.Commands.AddScript("Receive-Job -Name " + pName);
return pipeline.Invoke();
}
String shareArguments = "some stuff here";
String shareCommandName = "copyFolder";
String shareCommand = "Start-Job -filepath " + currentDirectory + "\\scripts\\copyFolder.ps1 -ArgumentList " + shareArguments + " -Name " + shareCommandName + " -RunAs32";
Collection<PSObject> results1 = executeCommand(shareCommand, shareCommandName);
StreamWriter sharestream = new StreamWriter("D:\\shareoutput.txt");
foreach (PSObject obj in results1)
{
sharestream.WriteLine(obj.ToString());
}
sharestream.Close();
The script itself:
param($sharepath,$shareuser,$sharepassword,$hostname,$sourcefolder)
# create a credentials object
$secpasswd = ConvertTo-SecureString $sharepassword -AsPlainText -Force
Write-Output "0"
$cred = New-Object System.Management.Automation.PSCredential ($shareuser, $secpasswd)
Write-Output "1"
# Access the share
New-PSDrive -Name J -PSProvider FileSystem -Root $sharepath -Credential $cred
Write-Output "2"
# Copy the folder including the file
Copy-Item $sourcefolder "J:\" -Recurse -Force
Write-Output "3"
# Unmap drive
Remove-PSDrive -Name J
When I retrieve the debug output of the job, the output looks like this. So it seems that the New-PSDrive call seems to block here somehow:
0
1
Any idea what is the cause for this and how I can fix it?
Thanks in advance for any hint
You have problem with New-PSDrive. First Check your PSCredential object.
then update the script with
New-PSDrive -Name J -PSProvider FileSystem -Root $sharepath -Credential $cred -Persist
Perfect article:
https://technet.microsoft.com/en-us/library/hh849829.aspx

passing parameter to the powershell script file from C# environment

i understand that the subject line looks too mundane and there are posts addressing many such questions. I did not find what i was exactly looking for and hence this post.
I am invoking a powershell file from machine A to be executed on a remote machine(machine B).
//here is the code snippet:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
string scripttext = "$secpasswd = ConvertTo-SecureString '222_bbbb' -AsPlainText –Force";
string scripttext1 = "$mycreds = New-object -typename System.Management.Automation.PSCredential('TS-TEST-09\\Administrator',$secpasswd)";
string scripttext2 = "$s = New-PSSession -ComputerName TS-TEST-09 -Credential $mycreds";
**string scripttext5 = "Invoke-Command -Session $s -FilePath 'helper.ps1' | out-null";**
pipeline.Commands.AddScript(scripttext);
pipeline.Commands.AddScript(scripttext1);
pipeline.Commands.AddScript(scripttext2);
pipeline.Commands.AddScript(scripttext5);
Collection<PSObject> results = pipeline.Invoke();
now in line
string scripttext5 = "Invoke-Command -Session $s -FilePath 'helper.ps1' | out-null";
i have to pass a few parameters(for example, username from c# environment : useralias) to this helper.ps1 file for processing. can any one guide me to a correct method of doing so?
TIA,
Manish
If the username and useralias come from the local machine then you can do so like this:
string scripttext5 = "Invoke-Command -Session $s -FilePath 'helper.ps1' -Args " +
username + "," + useralias + " | Out-Null";
This assumes that your helper.ps1 file takes at least two parameters. If so, then the values of the C# variables username and useralias will get assigned to the first two parameters.
okay so here is the solution that worked for me.
it takes its bits from Keith's solution. the trick is to expose variables from c# using
runspace.SessionStateProxy.SetVariable("PSuseralias", this.useralias);
now using $PSuseralias as
string scripttext5 = "Invoke-Command -Session $s -FilePath 'helper.ps1' -Args $PSuseralias;
this does the trick.

Categories