Windows PowerShell is a powerful scripting language. However, in PowerShell 1.0, remote management is limited to using those cmdlets that support a remote computer name parameter. For example, the following command lets you get a list of Windows services running on a remote system named Server1:

Get-service -ComputerName Server1

This method has several drawbacks. It's cmdlet-dependent, and not all the cmdlets have a remote computer name parameter. In addition, each cmdlet requires different access and network protocols.

Thankfully, Microsoft introduced PowerShell remoting in PowerShell 2.0 and has been enhancing it in each new release. In PowerShell 3.0, there are a lot of new remoting features, including some that simplify the configuration of PowerShell remoting on systems.

Let's dive in and explore what PowerShell remoting is and how to enable it. I'll also show you the different ways you can execute PowerShell commands on remote systems, which is imperative in a world where servers and workstations are increasingly geographically remote and managed in large numbers.

PowerShell Remoting Under the Covers

PowerShell remoting relies on a standards-based protocol named Web Services for Management (WS-Management, or WS-Man). This protocol is, in turn, implemented in Windows as Windows Remote Management (WinRM). If you go into the Services Control Panel on any version of Windows starting with Windows 7, you'll see a Windows Remote Management service configured to start manually, as shown in Figure 1.

Observing the WinRM Service in Its Natural State

WS-Man is a Web services-based protocol that uses HTTP to send messages between a client and server. This makes it very flexible and friendly from a network firewall perspective, compared with legacy protocols such as DCOM and remote procedure call (RPC), which require multiple ports that are often dynamic. You can configure WinRM to use a specific port and use HTTP Secure (HTTPS) for encryption. In general, WinRM provides more supportability over a greater variety of networks than traditional remoting protocols.

To leverage PowerShell remoting, you need to have at least PowerShell 2.0 installed and preferably PowerShell 3.0, which is the version I'll be using here. PowerShell 2.0 first shipped with Windows 7 and Windows Server 2008 R2, but if you have earlier versions of Windows, you can install the appropriate Windows Management Framework Core package, which includesPowerShell 2.0 and WinRM 2.0. There are core packages for Windows Vista, Windows XP, Windows Server 2008, and Windows Server 2003. The free Windows Management Framework Core packages are available in the Microsoft Download Center.

Microsoft introduced PowerShell 3.0 in Windows 8 and Windows Server 2012. Consequently, Microsoft also released Windows Management Framework 3.0, which includes PowerShell 3.0 and the latest WinRM stack. You can install Windows Management Framework 3.0 on Windows 7 and Server 2008 R2 to get the latest and greatest PowerShell remoting functionality. The free Windows Management Framework 3.0 is available in the Microsoft Download Center. For information on how to install it, see the article "PowerShell Basics: System Requirements."

Note that the Microsoft Download Center also offers the Windows Management Framework 4.0 Preview, which includes PowerShell 4.0. However, I won't be discussing the remoting functionality in PowerShell 4.0 because it's still in the preview stage.

Enabling PowerShell Remoting

Before you can use PowerShell remoting, several configurations need to be made:

  • The startup type for the WinRM service needs to be changed from Manual to Automatic. That way, the service will automatically create a WinRM "listener," which listens for and accepts WS-Man traffic.
  • Windows Firewall exceptions need to be set up on the machine so that the ports used by the WinRM listener can be accessed remotely.

Fortunately, making these configurations is an easy task. In Server 2012 (but not Windows 8), PowerShell remoting is enabled by default, so you don't need to do anything. In the other Windows OSs, the quickest way to enable PowerShell remoting on a given system is to fire up PowerShell as an Administrator and run the command:

Enable-PSRemoting

This command prompts you a couple of times to confirm actions, then makes the necessary changes, as shown in Figure 2.

Using the Enable-PSRemoting Cmdlet to Enable PowerShell Remoting

If you need to customize a WS-Man endpoint (e.g., customize it to use different ports or different authentication protocols) you can use the command-line winrmutility, which contains many options for customizing WinRM on a given system.

You can also use Group Policy to make the necessary configurations en-masse, across many different machines. This is useful if you don't want to run Enable-PSRemoting manually on each client machine.

To make the configurations using Group Policy, you must enable the policy under \Computer Configuration\Policies\Administrative Templates\Windows Components\Windows Remote Management (WinRM)\WinRM\Service\Allow remote server management through WinRM. When you enable this policy (and select the appropriate IP address filters, if any), you're essentially performing most of the tasks done by the Enable-PSRemoting cmdlet.

The one task that isn't done for you is setting the Windows Firewall exception for TCP port 5985, which is the default port used by WinRM to listen for traffic over HTTP. You'll need to create a separate Group Policy exception for this port if you're using Windows Firewall to protect inbound traffic on your hosts.

Leveraging PowerShell Remoting

After you've set up WinRM so that it can be used as the "conduit" for PowerShell remoting, you can leverage it. The key to PowerShell remoting lies in the concept of a PowerShell Session (PSSession). In fact, many of the key cmdlets related to remoting contain the PSSession noun somewhere in their name. A PSSession is simply a persistent connection to a remote machine. Once the connection is made, you can perform tasks against that remote machine. As of PowerShell 3.0, when you start a PSSession from a PowerShell console, then close that PSSession locally, the PSSession on the remote system remains running and you can reconnect to it from any machine (i.e., the same machine or a different machine from which you created it). This feature is pretty cool because it means you can kick off long running tasks against a bunch of machines and even if your administrative machine loses contact with the network, those remote tasks will still be running.

Let's look at the different ways you can interact with a PSSession. The easiest cmdlet to use is Enter-PSSession. This cmdlet serves the same purpose as Secure Shell (SSH) in the UNIX world. It's your secure method for getting an interactive command prompt on a remote machine.

The Enter-PSSession cmdlet's syntax is straightforward. Suppose you want to get a remote command prompt on a server named 2008-64-1. Within your PowerShell console, you'd run the command:

Enter-PSSession -ComputerName 2008-64-1

You'll know when you're successfully connected because your PowerShell prompt will change. The name of the remote server to which you're connected will be added in square brackets in front of the usual prompt, as Figure 3 shows.

Using the Enter-PSSession Cmdlet to Enter a Remote PowerShell Session

Once the PSSession is established, you can run commands on the remote server as if you were logged on to the remote server's console. However, there's one thing to note about creating this kind of PSSession. Previously I mentioned that a PSSession is a persistent connection to the remote computer. However, when you call the Enter-PSSession or Invoke-Command cmdlet directly, it creates only a temporary PSSession for the life of the connection. Once the remote console session is done or the command has been invoked, the PSSession is terminated. As you'll see in a bit, this is not the case if you first create a PSSession and then use that session to perform tasks.

Since I mentioned Invoke-Command, let's take a look at what that cmdlet does. If Enter-PSSession is all about giving you an interactive shell on a remote computer, Invoke-Command is designed to let you perform automated tasks on remote computers without interaction. In the simplest case, if you pass a PowerShell command to Invoke-Command, the command will be run on the remote system and the results will be returned to your PowerShell console. For example, here's a command that runs the Get-Process cmdlet on a remote server to get a list of the processes running on that server:

Invoke-Command -ComputerName 2008-64-1 `
  -ScriptBlock {Get-Process}

By using the -ScriptBlock parameter, you can pass one or more PowerShell commands to the remote machine. Script blocks are enclosed in curly brackets to indicate that they're PowerShell commands. In this case, I'm passing only a single PowerShell cmdlet (Get-Process) to the remote machine. However, within the script block, I could've called multiple cmdlets, passing the output of one to another using the pipeline. Or I simply could've called multiple cmdlets by delimiting each with a semicolon. You will see this convention across PowerShell and not just with these remoting cmdlets.

You can also run a PowerShell script on one or more remote systems, without needing to have a copy of the script on all the remote computers it's run against. For example, suppose you have a script on your local computer, c:\scripts\getworkingset.ps1, that returns the memory usage for a specific process. You can call Invoke-Command and use its -FilePath parameter to specify the script's pathname, like this:

Invoke-Command -ComputerName 2008-64-1 `
  -FilePath c:\scripts\getworkingset.ps1

The script will be read from your local computer, but be processed and run on the remote server 2008-64-1. The only caveat to running scripts this way is that whatever cmdlets or functions you call in that script must be available on the target system. For example, if the script uses a cmdlet that's part of the Active Directory PowerShell module and that module isn't installed on the remote system, the script will fail.

Another option that's available with Invoke-Command is the ability to pass credentials to a command. This is handy if you have servers that require different credentials to access them (e.g., you have servers in different trusted domains or servers that require alternative administrator credentials to access). In the simplest case, you can call Invoke-Command with the -Credential parameter. For example, if my domain user ID is cpandl\darren, I'd run the command:

Invoke-Command -ComputerName 2008-64-1 `
  -Credential cpandl\darren -ScriptBlock {Get-Process}

Before you run this command, you need to specify your domain user ID with the -Credential parameter. When you execute it, a dialog box will pop up prompting you for your password. Once you correctly submit it, the command will continue to execute.

This process can be tedious if you need to run a command against many computers. So, a different way of approaching this is to store your credentials in a variable, then use the variable in subsequent commands. (If you're unfamiliar with PowerShell variables, see "PowerShell Basics: Variables.") For example, the following commands store my credentials in a variable named $cred, then use the credentials in that variable to run Get-Process on remote server 2008-64-1:

$cred = Get-Credential cpandl\darren
Invoke-Command -ComputerName 2008-64-1 `
  -Credential $cred -ScriptBlock {Get-Process}

I can re-use that variable in any subsequent Invoke-Command operations without ever getting prompted.

Leveraging Persistent PSSessions

Enter-PSSession and Invoke-Command are great tools, but they don't create persistent PSSessions to remote systems. Once the commands or tasks finish, the session to the remote system goes away. This has some unfortunate implications:

  • If you have a long-running task that you need to execute on several remote systems and the computer on which you started those tasks loses connection to the network, the status of those remote tasks is lost. You won't know when or if they finished.
  • There's no ability to share data between two separate Invoke-Command instances. For example, you can't run Invoke-Command to get a list of processes, then run it again against the list you got in the first command to filter the data further. Once the first Invoke-Command finishes, its state is gone.

To avoid these problems, you can use the New-PSSessioncmdlet. It creates a persistent PSSession on one or more remote computers. Once created, the PSSession objects persist until explicitly deleted. And in PowerShell 3.0, you can disconnect and reconnect to persistent PSSessions as needed.

Let's look at an example of how this works. The following command creates a new PSSession and assigns it to a session variable named $sess:

$sess = New-PSSession -ComputerName 2008-64-1, 2008-R2-DC1 `
  -Credential $cred

In this example, I'm creating a new PSSession that connects to two separate servers. I'm passing in my credentials by means of the $cred variable that I created earlier. Once this session is created, I'll have two persistent connections to those two servers within a single PSSession.

I can then use the $sess variable to reference this PSSession in any subsequent calls to Invoke-Command, as shown here:

Invoke-Command -Session $sess -ScriptBlock {Get-Process}

Using New-PSSession is also advantageous when you need to share data across multiple commands. Suppose that you want to count the number of processes running on remote systems and, whenever you find a system that doesn't have the expected number, send a note to the console about that server. Here's how you can do that with the PSSession just created:

Invoke-Command -Session $sess `
  -ScriptBlock {$count = (Get-Process).Count}
Invoke-Command -Session $sess `
  -ScriptBlock {
  if ($count -lt 100) {
    write-host "$env:COMPUTERNAME is missing some processes"
    }
  }

In this example, the first call to Invoke-Command counts the number of processes running on each remote server and stores that number in the variable named $count on each server. The next call to Invoke-Command tests the value in $count to see whether it's less than 100. If it is, a message that includes the computer name is written to the console.

This is a simple example to show how you can share data across multiple Invoke-Command instances when you're using a persistent PSSession. It's a powerful concept that can be applied to a whole host of remote management tasks.

Note that you can also use the Enter-PSSession cmdlet with sessions that you create with New-PSSession. Here's an example:

Enter-PSSession -Session $sess

However, this won't work if your session object is connected to multiple servers. With Enter-PSSession, you can interact with only one server at a time.

Disconnecting PSSessions

If you're running PowerShell 3.0, you can use the Disconnect-PSSession cmdlet to disconnect your machine from a PSSession you created with New-PSSession and later use Connect-PSSession to reconnect. What is the advantage of that? Well, suppose you created a new PSSession on your machine to run some long-running scripts against a bunch of remote servers. While the scripts are running, your system tells you that it's about to shut down to perform an update. If you run a Disconnect-PSSession command before the shutdown occurs, the state of the PSSessions will be maintained on the remote computers. After the update, you can reboot your system and run a Connect-PSSession command to re-establish the PSSession. In the interim, work will continue on those remote systems. They'd be none the wiser that their "master" had gone away for a while. This is a cool feature that makes the PowerShell remoting capabilities in PowerShell 3.0 robust for critical automation tasks that absolutely can't be interrupted in the middle of their operation.

A Powerful Tool for Automating Remote System Management

PowerShell remoting is a powerful tool for automating the management of remote servers and workstations by means of standards-based protocols. Using WS-Man as the backbone, you can work interactively with a remote system using Enter-PSSession and Invoke-Command. If you need to manage multiple systems in a persistent session or want to share data across different commands on those multiple remote systems, you can use New-PSSession. It lets you create persistent connections to multiple remote systems. Using the established PSSession, you can then run all types of commands to manage those systems.