Author Posts

December 8, 2017 at 8:29 pm

Hi all,

I'm looking to use C# to accomplish what Copy-Item with the -ToSession parameter does. PowerShell is my tool of choice but in this context I'm attempting to replace C# code that uses ftp to transfer a file. I'd like to basically call the Copy-Item command and pass it a PSSession object. I have 95% of this working and have managed to copy the file locally, but have hit a road block with the PSSession. I can't seem to create one of type System.Management.Automation.Runspaces.PSSession. I've tried everything I have been able to find on the old interwebs but still can't seem to get it to work. Below is a snippet of code and if anyone out there has done this, some help would be greatly appreciated.

Thanks,
Justin

//Create a PowerShell object and load the New-PSSession command
PowerShell pshsession = PowerShell.Create();
pshsession.AddCommand("New-PSSession");
pshsession.AddParameter("ComputerName", computer);
pshsession.AddParameter("Credential", cred);

//Create a second PowerShell object and load the Copy-Item command
PowerShell localpsh = PowerShell.Create();
localpsh.AddCommand("Copy-Item");
localpsh.AddParameter("Path", "C:\\temp\\todo.txt");
localpsh.AddParameter("Destination", "C:\\temp\\todo.txt");
//The idea here is to create the PSSession as the parameter is called. I was hoping this would force it to type PSSession
//but it hasn't worked. I've also tried to implicitly convert it but I get an exception that it can't do that.
localpsh.AddParameter("ToSession", pshsession.Invoke());
localpsh.AddParameter("Force", true);
localpsh.Invoke();

December 12, 2017 at 3:25 pm

I hate to do this, but I think you've kind of exceeded this community's purpose ;). We don't get a lot of C# coders here.

My guess is that you can't "copy" a PSSession object between PowerShell engines – that would require a level of marshaling that I suspect doesn't exist. When we do this in the shell, we're doing it inside a single engine, and persisting (usually) the PSSession in a variable within that engine's VARIABLE: drive. That's a lot harder to gin up in your own host.

December 12, 2017 at 4:05 pm

Thanks for reply Don. I actually figured out a way to do this yesterday and if anyone is interested in it I've posted the code below. Now, I totally agree that this is way easier and should be done in PowerShell. For this case I ran into a problem that we wanted to solve with PowerShell but we already had a C# program that handled the "legacy" version of it along with a bunch of other stuff. I could have called a PS script that did it all but our first option was to have C# code that did the job for us. And I passes this of to a C# developer so if it looks terrible, then it probably is but he should be able to clean it up 🙂

-Justin

// Username and Password for the remote machine.
var userName = "DOMAIN\\USERNAME";
string pw = "MYSECRETPWASPLAINTEXT";

// Path to the files are to be copied
string path = "C:\\temp\\stuff.txt";

// Path on the remote machine where the files need to be placed
string destination = "C:\\temp\\mystuff.txt";

//ComputerName
string computerName = "REMOTESERVER"

// Creates a secure string for the password
SecureString securePassword = new SecureString();
foreach (char c in pw)
{
    securePassword.AppendChar(c);
}
securePassword.MakeReadOnly();

// Creates a PSCredential object
PSCredential creds = new PSCredential(userName, securePassword);

// Creates the runspace for PowerShell
Runspace runspace = RunspaceFactory.CreateRunspace();

// Create the PSSession connection to the remote machine.
PowerShell powershell = PowerShell.Create();
PSCommand command = new PSCommand();
command.AddCommand("New-PSSession");
command.AddParameter("ComputerName", computerName);
command.AddParameter("Credential", creds);
powershell.Commands = command;
runspace.Open(); powershell.Runspace = runspace;
Collection result = powershell.Invoke();

// Takes the PSSession object and places it into a PowerShell variable
powershell = PowerShell.Create();
command = new PSCommand();
command.AddCommand("Set-Variable");
command.AddParameter("Name", "ra");
command.AddParameter("Value", result[0]);
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke();

// Calls the Copy-Item cmdlet as a script and passes the PSSession, Path and destination parameters to it
powershell = PowerShell.Create();
command = new PSCommand();
command.AddScript("Copy-Item -Path " + path + " -Destination " + destination + " -ToSession $ra");
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke();

December 12, 2017 at 4:06 pm

Another option would be to refactor the legacy C# code into a PowerShell cmdlet, which is pretty trivial in terms of work, usually. Then you've got a compiled module you can just load directly into the shell. Thanks for sharing the solution code!