Experimenting with PowerShell V2 Remoting

As I continue to experiment with PowerShell v2 in Windows Server 2008 R2, I will share some of what I learn here on the blog. This time I am focusing on PowerShell Remoting. If you never played with PowerShell before, please start by reading http://blogs.technet.com/josebda/archive/2009/07/25/experimenting-with-powershell.aspx and http://blogs.technet.com/josebda/archive/2009/08/09/experimenting-with-powershell-cmdlets-snap-ins-and-modules.aspx


In general, “remoting” relates to the ability to query another computer or execute tasks against these remote computers. Many command line tools can act upon a remote server (using something like an RPC). You also can always use RDP (Remote Desktop Protocol) to bring an entire remote desktop to you, but this does not help much if you’re trying to interact with a large set of servers. PowerShell can help a lot with that.

When we talk about “PowerShell Remoting”, we’re usually referring to the ability to sit in the comfort of your client (running Windows 7, for instance), open a PowerShell window and issue commands that interact with a remote server (running Windows Server 2008 R2, for instance). This can also function as the basics to create interesting PowerShell scripts that gather information from a set of servers and/or perform tasks involving multiple servers. This could including compiling a list of all servers in a domain running the DHCP server service, generating a report with the free space on system volume for you domain controllers or pointing all servers that do not have a specific hotfix applied.

Cmdlets take take a –computername parameter

A number of PowerShell commandlets can act on a remote server by simply using a –computername parameter (it typically can be shortened to –cn). These commandlets will use DCOM/RPC to communicate with the remote computer and execute the commands. The results are provided back to you in the PowerShell pipeline, in the same way it happens for local command. Here’s a list of some of the PowerShell commandlets that support this parameter:

  • Get-Service
  • Get-Process
  • Get-HotFix
  • Get-Counter
  • Get-EventLog
  • Get-WinEvent
  • Get-WmiObject

To help you understand how these cmdlets work with the computername parameter, I am adding a few examples below. Please note that sometimes I will show the same command line twice, once in a more verbose version and the other in a more succinct version using aliases. The output for two equivalent comment is the same and is showed only once.

Example: Find out the status of the SQL Server service running on a remote computer

Get-Service MSSQLServer -ComputerName josebda-s0
gsv MSSQLServer –cn josebda-s0

Status   Name               DisplayName
------   ----               -----------

Example: Get a list of hotfixes installed on a remote computer

Get-Hotfix -computername josebda-s0 | Select HotfixID, Description, InstalledOn | Sort-Object InstalledOn
Get-Hotfix –cn josebda-s0 | Select HotfixID, Description, InstalledOn | sort InstalledOn

HotfixID  Description      InstalledOn
--------  -----------      -----------
KB978207  Update           3/28/2010 12:00:00 AM
KB975560  Security Update  3/28/2010 12:00:00 AM
KB978262  Security Update  3/28/2010 12:00:00 AM
KB978251  Security Update  3/28/2010 12:00:00 AM
KB972270  Security Update  3/28/2010 12:00:00 AM
KB971468  Security Update  3/28/2010 12:00:00 AM
KB975467  Hotfix           3/28/2010 12:00:00 AM
KB974571  Security Update  3/28/2010 12:00:00 AM

Example: Use WMI to get a list of volumes (including size and free space) on a remote computer

Get-WmiObject Win32_LogicalDisk -ComputerName josebda1 | Format-Table
gwmi Win32_LogicalDisk –cn josebda1 | ft

DeviceID    DriveType ProviderName    FreeSpace           Size VolumeName
--------    --------- ------------    ---------           ---- ----------
C:                  3               62085390336   119926681600
D:                  5
E:                  5

This example showing the use of WMI objects from a remote computer opens a really wide set of possibilities, since there are a lot ofof WMI providers out there…

Protocols and Permissions

Also note that, while these are PowerShell commandlets using a –computername parameter execute on a remote computer, they are not technically considered “PowerShell Remoting”. They actually use DCOM/RPC to execute. You do want to make sure that your management computer can talk to the server and the account you’re using has the permissions to execute RPCs. If you can’t connect to that server (if the firewall does not allow RPC connections, for instance) or you do not have the right permissions, you might see an error message like:

Get-Service –ComputerName josebda-s0
gsv -cn josebda-s0

Get-Service : Cannot open Service Control Manager on computer 'josebda-s0'. This operation might require other privileges.
At line:1 char:12
+ get-service <<<< -cn josebda-s0
+ CategoryInfo : NotSpecified: (:) [Get-Service], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException,Microsoft.PowerShell.Commands.GetServiceCommand


The other two ways I will describe are the proper “PowerShell Remoting” features, which use WS-Management (also referred to as Windows Remote Management or WinRM). In order to use them, you need to make sure you have a listener service properly configure on the remote machine you will be communicating with. All you need to do is run the Enable-PSRemoting commandlet and confirm at the prompts. See the example below:


WinRM Quick Configuration
Running command "Set-WSManQuickConfig" to enable this machine for remote management through WinRM service.
This includes:
1. Starting or restarting (if already started) the WinRM service
2. Setting the WinRM service type to auto start
3. Creating a listener to accept requests on any IP address
4. Enabling firewall exception for WS-Management traffic (for http only).

Do you want to continue?
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): <ENTER>

WinRM has been updated to receive requests.
WinRM service type changed successfully.
WinRM service started.

WinRM has been updated for remote management.
Created a WinRM listener on HTTP://* to accept WS-Man requests to any IP on this machine.
WinRM firewall exception enabled.

Are you sure you want to perform this action?
Performing operation "Registering session configuration" on Target "Session configuration "Microsoft.PowerShell32" is
not found. Running command "Register-PSSessionConfiguration Microsoft.PowerShell32 -processorarchitecture x86 -force"
to create "Microsoft.PowerShell32" session configuration. This will restart WinRM service.".
[Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Y"): <ENTER>

This needs to be performed only once for every host you intend to remotely query or configure using the two sets of commands I will describe next. There’s a bit of a chicken-and-egg problem here, since you cannot run “Enable-PSRemoting” remotely and the default configuration has this disabled. You need to somehow overcome this by making this your default server configuration, logging locally at least once to configure this or using Remote Desktop (RDP) to run it.

Use Invoke-Command

Once you have run Enable-PSRemoting, you can use Invoke-Command to run any PowerShell command to execute commands agains a remote computer. It’s that simple. As with a locally executed command, you get the resulting objects in your pipeline. PowerShell remoting takes care of serializing the objects over the wire and reassembling them on your client. Here are a few examples.

Example: Getting a list of environment variables starting with the letter C in a remote computer

Invoke-Command josebda-s0 {Get-ChildItem Env:C*}
icm josebda-s0 {dir env:c*}

Name                           Value                                        PSComputerName
----                           -----                                        --------------
COMPUTERNAME                   JOSEBDA-S0                                   josebda-s0
CommonProgramW6432             C:Program FilesCommon Files                josebda-s0
CommonProgramFiles(x86)        C:Program Files (x86)Common Files          josebda-s0
CommonProgramFiles             C:Program FilesCommon Files                josebda-s0
ComSpec                        C:Windowssystem32cmd.exe                  josebda-s0

Example: Getting a list of PowerShell drives on a remote computer

icm josebda-s0 {Get-PSDrive} | Select Name, Root, Free, Used

Name                          Root                          Free                          Used
----                          ----                          ----                          ----
C                             C:                           227056844800                  22895357952
D                             D:                                                         0
HKCU                          HKEY_CURRENT_USER
HKLM                          HKEY_LOCAL_MACHINE

Example: Check the status of the Spooler service on a remote computer

Get-Service Spooler -ComputerName josebda-s0
gsv Spooler -cn josebda-s0

Status   Name               DisplayName
------   ----               -----------
Running  Spooler            Print Spooler

Invoke-Command josebda-s0 {Get-Service Spooler}
icm josebda-s0 {gsv "Spooler"}

Status   Name               DisplayName                            PSComputerName
------   ----               -----------                            --------------
Running  Spooler            Print Spooler                          josebda-s0

As you can see, for commands that support the –computername parameter, you can use that form or choose to use Invoke-Command instead. The results should be very similar, but the difference is that you are using a different protocol over the wire (DCOM/RPC instead of WinRM).

Interactive Remoting

If you intend to run a set of commands on a specific computer, you can use Interactive remoting to create a remote session and get a new prompt where all commands go to that remote computer. This is a convenient way to explore the configuration of a remote computer, but not so useful when creating a batch file. You start by using an Enter-PSSession command (or simply ETSN), then you get a new prompt to execute the remote commands. When you’re done, you close the remote session using Exit-PSSession (or simply EXSN or EXIT). Here’s an example:

PS C:Windowssystem32> Enter-PSSession josebda-s0
[josebda-s0]: PS C:Windowssystem32> dir env:c*

Name Value
---- -----
CommonProgramW6432 C:Program FilesCommon Files
CommonProgramFiles(x86) C:Program Files (x86)Common Files
CommonProgramFiles C:Program FilesCommon Files
ComSpec C:Windowssystem32cmd.exe

[josebda-s0]: PS C:Windowssystem32> dir C:

Directory: C:

Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 7/13/2009 8:20 PM PerfLogs
d-r-- 3/30/2010 9:23 AM Program Files
d-r-- 3/29/2010 6:49 AM Program Files (x86)
d---- 3/28/2010 9:15 PM Temp
d-r-- 3/28/2010 2:28 PM Users
d---- 3/30/2010 3:45 PM Windows

[josebda-s0]: PS C:Windowssystem32> Exit-PSSession
PS C:Windowssystem32>

You can even create a persistent session using the New-PSSession command, which you can then use with Enter-PSSession. This is useful if you intend to keep the session open and re-enter multiple times.

Multiple servers at once

By now you should probably be convinced that executing commands remotely with PowerShell is very convenient. However, you can take it one step further by executing the same command in multiple servers in a single line. And you still get a single set of objects in the pipeline as a result.

As with most things in PowerShell, there are a few different ways to do it. First, you can provide a set of names (separated by comma) in your –computername parameter. You can also create a variable that contains a set of names and use that variable. You can even store the computernames in a text file and use the Get-Content commandlet (or the alias Type) to bring that data in as part of your commandline. This last one can be done by including the Type command in parenthesis, right after the –computername, ). The same options apply to Invoke-Command. 

There is a tricky part about doing this, which is making sure you get a property that tells which computer returned that specific object in the resulting set. There’s usually a way to tell, but this is not always the same property.

The next set of examples explores the possibilities for running commands in multiple server in the same line and include more elaborate command lines. I hope you enjoy them.

Example: Get a list of all hard drives (drive type is 3) for a set of computers. Multiple names passed directly in the command line

Get-WmiObject Win32_LogicalDisk -ComputerName josebda1, josebda-s0, josebda-s1 | Where-Object { $_.DriveType -eq 3 } | select SystemName, DeviceID, Size, FreeSpace | Format-Table
gwmi Win32_LogicalDisk -cn josebda1, josebda-s0, josebda-s1 | ? { $_.DriveType -eq 3 } | Select SystemName, DeviceID, Size, FreeSpace | ft

SystemName DeviceID Size FreeSpace
---------- -------- ---- ---------
JOSEBDA1 C: 119926681600 62085210112
JOSEBDA-S0 C: 249952202752 227057037312
JOSEBDA-S1 C: 249952202752 222086516736

Example: Get a list of all SMB shares for a set of servers. List of computer names stored in a variable ($a) that’s passed in the command line.

$a="josebda1", "josebda-s0", "josebda-s1"
Get-WmiObject Win32_Share -ComputerName $a | Select __Server, Name, Path, Description
gwmi Win32_Share -cn $a | Select __Server, Name, Path, Description

__SERVER Name Path Description
-------- ---- ---- -----------
JOSEBDA1 ADMIN$ C:Windows Remote Admin
JOSEBDA1 C$ C: Default share
JOSEBDA-S0 ADMIN$ C:Windows Remote Admin
JOSEBDA-S0 C$ C: Default share
JOSEBDA-S1 ADMIN$ C:Windows Remote Admin
JOSEBDA-S1 C$ C: Default share
JOSEBDA-S1 josebda-dfs C:DFSRootsjosebda-dfs
JOSEBDA-S1 Software C:Software

Example: Get a list of PowerShell drives for a set of computers. List of computer names stored in a file (serverlist.txt) that’s passed in the command line via Get-Content (4 different forms!?).

type serverlist.txt
Get-Content serverlist.txt


Invoke-Command (Get-Content "serverlist.txt") {Get-PSDrive}
icm (type "serverlist.txt") {Get-PSDrive}
Get-Content serverlist.txt | ForEach-Object { Invoke-Command $_ {Get-PSDrive}}

type serverlist.txt | % {icm $_ {Get-PSDrive}}

Name Used (GB) Free (GB) Provider Root CurrentLocation PSComputerName
---- --------- --------- -------- ---- --------------- --------------
Alias josebda-s1
C 25.96 206.83 C: &n