I was reading PowerShell remoting in Windows Server 2008 R2. The great thing about this article is that it documents how to use Group Policy to enable PSRemoting on lots of machines automatically. In the article there was the following heads up:
Warning:
Commands that are executed against a remote machine do not have access to information defined within your local profile. As such, commands that use a function or alias defined in your local profile will fail unless they are defined on the remote machine as well.
The great thing about PowerShell is that it is a toolbox.
The great thing about toolboxes is that they have general purpose utilities that creative people can use their inspiration to do things that the tool designers didn’t think of when they developed the tool. One of my favorite examples of this came from a problem with SELECT. SELECT is a utility which selects a set of properties from the incoming object. It creates a new object and adds those selected properties as NoteProperties on that new object. e.g.
PS> Get-Process *ss
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
——- —— —– —– —– —— — ———–
917 14 3420 2364 52 9.97 424 csrss
771 27 3484 13428 189 108.02 512 csrss
1989 45 18272 17820 87 95.83 556 lsass
30 2 476 124 5 0.06 320 smss
PS> Get-Process *ss |Select ID,Handles,ProcessName
Id Handles ProcessName
— ——- ———–
424 930 csrss
512 770 csrss
556 1984 lsass
320 30 smss
PS> Get-Process *ss |Select ID,Handles,ProcessName |Get-Member
TypeName: Selected.System.Diagnostics.Process
Name MemberType Definition
—- ———- ———-
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Handles NoteProperty System.Int32 Handles=925
Id NoteProperty System.Int32 Id=424
ProcessName NoteProperty System.String ProcessName=csrss
PS> Get-Process *ss |Select ProcessName
ProcessName
———–
csrss
csrss
lsass
smss
Sidenote: notice that the TYPE of the result is Selected.System.Diagnostics.Process. We did that in V2 to be consistent with our principle of always trying to help you keep track of where information came from. The idea is that if you are debugging a script and have an examine an object and see this typename, you have more information about where it came from to help debug your script.
Anyway, that is great but notice what happens when you select a single property you get an object with a single property. Often you just want the values of the property not an object with a single property. The solution to that has been:
PS> Get-Process *ss |foreach {$_.ProcessName}
csrss
csrss
lsass
smss
That works but I don’t like it very much. We had lots of discussions about what to do about this. Some people wrote a function which did this operation and thought we should include it in the next version. Others felt like we ought to put a switch on Select which say something like –VALUEONLY. It was Lee Holmes that pointed out that we already support this. I looked at him like he had a rat’s tail hanging out of his mouth when he said that. He said that Select –ExpandProperty did the trick. This switch was designed to work with objects that have properties which are collections. For instance:
PS> Get-Service WinMgmt |Select DependentServices
DependentServices
—————–
{wscsvc, SharedAccess, iphlpsvc}
PS> Get-Service WinMgmt |Select -ExpandProperty DependentServices
Status Name DisplayName
—— —- ———–
Running wscsvc Security Center
Stopped SharedAccess Internet Connection Sharing (ICS)
Running iphlpsvc IP Helper
Pretty cool right? This tool was designed to work on properties which are collections. Lee Holmes wondered, “What would it do on a property which wasn’t a collection?”.
PS> Get-Process *ss |Select -ExpandProperty ProcessName
csrss
csrss
lsass
smss
OH YEAH BABY! You asked for it you got it! Using the toolkit mindset, Lee was able to figure out that we already supported this function. (You might point out that this is longer than the foreach but it turns out to be a ton easier because of tab completion.)
So – what does this have to do with the remoting warning about not having access to any of the functions you defined in your profile file? Well, it turns out that the command Invoke-Command has a parameter –FILEPATH which allows you to specify a file that you want to run on the remote machine. We added this feature to help people machine machines in DMZs where it can be difficult to ensure that the script you want to run will be on that machine and it can be difficult for that machine to have access to a shared folder of scripts. We send the file across and execute it so there is deployment free script execution. (This is a super important feature that people haven’t quite tuned into yet. I’m going to write a blog in the future about how to use this capability to use PowerShell as a bootstrap management agent to do all of your management functions.) So the example of this is something like
PS> $DmzMachines = New-PSSession -ComputerName (cat DmzMachines.txt)
PS> Invoke-Command $DmzMachines -FilePath c:\scripts\Get-Inventory.ps1
….
PowerShell transfers and executes the Get-Inventory.ps1 script to all the machines. From there, you can connect the dots to putting the functions you want everywhere into a file called RemoteProfile and doing the following:
PS> Invoke-Command $DmzMachines -FilePath c:\scripts\RemoteProfile.ps1
Be aware that this mechanism has limitations. In a local profile, you can invoke other files on your system. Using this technique, those files aren’t on the other system so that won’t work – the files have to be self contained. That is why I called it RemoteProfile.ps1 . I keep it separate from my normal profile.
Enjoy!
Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx
0 comments