1
votes

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?

1

1 Answers

5
votes

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();