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