Powershell Automation and Remoting (a c# love story)
OK all of you readers out there should know and love Powershell by now and you are using it constantly, right? If not get on with it! One of the most exciting things that came out of version 2 was the ability to perform remoting. Basically the ability to connect a session to a remote system and have the commands executed there. Think about that for a minute...
Powershell: finally a real scripting environment (those of us refugees from the Unix Wars finally can feel whole again) in windows but one that works in real live .Net objects!
Remoting: like telnet without the craptastic-ness leveraging WS-Management as infrastructure marshaling commands / responses securely across the network.
This is pure bliss - almost as if I was back in BSD again!
Now there's plenty of stuff on the interwebs about how to work with remoting sessions interactively. There's also some information on how to work with powershell's automation API (read: your CLR code is running and hosting powershell directly in process). What it doesn't seem is that there's any examples of working with powershell automation with a remote session out there. Or if there are, they are not working in what I expect is the most common use case:
- Machines in Active Directory with Trust relationships leveraging Kerberos for the interactive user credential
So to fix this I've gone and whipped up a small sample application that will show you the extra "bits" you need to use / configure to remote your automation applications. This post expects that you have a working understanding of powershell and the automation API (or at least can get the essence of it from the very understandable API).
As anyone performing automation knows your entry point to hosting powershell lays in the System.Management.Automation assembly (This is normally found in the %Program Files x86%\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0 directory). The first thing you need to do is to figure out what computer you are going to connect to and build the appropriate Uri for the WS-Management endpoint. There's scant information on this out there but basically the format is "<Scheme>://<ServerName>:<Port>/wsman". The two schemes that are supported are "http" and "https" and by default the management port will be 5985. editorial note: this port can be changed. Don't do it! So let's see some code:
var remoteComputer = new Uri(String.Format("{0}://{1}:5985/wsman", Scheme, ComputerName));
From there you need to create one of the RunspaceConnectionInfo types. There are two that come out of the box, NewProcessConnectionInfo and WSManConnectionInfo. You'll want the latter (obviously). This type has a constructor with a plethora of overloads. For the curious you have options to supply the PSCredentials here as well which is great if you have the credentials for the user to connect as which great if they need to be different from the interactive user OR you need to perform a double hop and Kerberos Delegation is not enabled.
var connection = new WSManConnectionInfo(remoteComputer);
From there you need to create a runspace (pretty much par course for powershell automation) and supply your connection object and then open the connection.
var runspace = RunspaceFactory.CreateRunstpace(connection);
runspace.Open();
From there create your powershell instance and bind it to the runspace (note: this is an IDisposable class so wrap it in a using statement or other construct)
var powershell = PowerShell.Create())
powershell.Runspace = runspace;
From there it's all the standard powershell automation calls you are already familiar with (e.g. AddCommand, AddScript, Add* methods). Here I'll just assume I have a command already built in a string variable and will invoke it, returning the output of the command (as a collection os PSObjects).
powershell.AddScript(command);
var results = powershell.Invoke();
foreach (var output in results.Where(o => o != null)
{
Console.WriteLine(output.ToString());
}
//Assume we're done
powerrshell.Close();
And that's that! As you can see there's really no mystery in combining Powershell Automation and Remoting. It's very straight forward to use and accomplish; In all likelyhood you'll spend more time getting your Powershell statements correct than worrying about automation issues =D). Now do be aware that you need to enable powershell remoting and all the appropriate firewall/security settings on the target computer. All this can easily be handled by the WinRM service with a handy and easy configuration statement. Simply run (on an elevated command prompt if UAC enabled) "WinRM quickconfig" and it will prompt you to make the required configuration changes with the default values. It supports a quiet mode and the ability to supply all parameters via the command line which is important say for installers and whatnot.
I've put together the world's ugliest console application to show off listing files in a given path on a specified computer via Powershell Automation Remoting.
Comments
Anonymous
March 23, 2012
Good stuff!Anonymous
March 26, 2012
Hey Jeffrey thanks! That means quite to get noticed from a D.E.! Cheers, mate! PS Anything else topical you'd like to see? :)Anonymous
December 12, 2013
I'd like to see how the following command be passed to PowerShell automation :) Get-MessageTrackingLog -start "2010-01-01 00:00:00" -sender "test.user@contoso.com" | where { $.EventID -eq "SEND" -or $.eventid -eq "RECEIVE" } | select EventID,MessageSubject | ft -autosize I can get the output for the first command but after that got lots of errors... Any pointers would be appreciated.Anonymous
December 19, 2014
How can I use this same method in case where my remote machine (Where cmdlets are to be executed) is in some other domain ??Anonymous
June 17, 2015
Hi Virtual Guy! I'm actually no longer at MS so I missed the notifications about your post. Just in case you happen to see this again (better late than never) or someone else needs the same pointers there's some basic approaches one would take.
- If the remote computer (A) and the invoking computer (B) are in different domains but a trust relationship exists from A to B, then nothing more should be required.
- If there's no trust relationship between the systems then one would instead need to use the overload to WSManConnetionInfo that accepts a PSCredential instance (or IIRC there's the ability to set a property for it after construction as well). There's you'd be required to use some identity and proof (user name and password basically) that the remote untrusting domain would accept. If this is an interactive application you could prompt the user for it or use a setting/configuration of some type). If this was something non-interactive like a server based application, one would need to have this safely stored in some type of configuration and supplied to the routine itself.