When I first started using PowerShell, I was playing around with the Get-Service command and noticed that it had a -ComputerName parameter. Hmmm . . . does that mean it can get services from other computers, too? After a bit of experimenting, I discovered that's exactly what it did. I got very excited and started looking for -ComputerName parameters for other cmdlets. I was disappointed to find that there were very few.

What I've realized since then is that PowerShell's creators were a bit lazy—and that's a good thing. They didn't want to have to code a -ComputerName parameter for every cmdlet, so they created a shell-wide system called remoting. Basically, it enables any cmdlet to be run on a remote computer. You can even run cmdlets that exist on the remote computer but don't exist on your own computer, which means you don't always have to install every administrative cmdlet on your workstation. This remoting system is powerful and provides a number of interesting administrative capabilities.

PowerShell offers two distinct types of remoting: one-to-one (1:1) remoting and one-to-many (1:n) remoting. Before I tell you about them, though, you need to be familiar with the basics.

The Idea Behind PowerShell Remoting

PowerShell remoting works similar to Telnet and other age-old remote control technologies. When you run a command, it's actually running on the remote computer. All that comes back to your computer are the results of that command. Rather than using Telnet or Secure Shell (SSH), however, PowerShell uses a new communications protocol called Web Services for Management (WS-Management). The protocol operates entirely over HTTP or HTTP Secure (HTTPS), making it easy to route through firewalls if necessary because each protocol uses a single port to communicate. Microsoft's implementation of WS-Management comes in the form of a background service named Windows Remote Management. WinRM is installed along with PowerShell 2.0 and is started by default on server OSs like Windows Server 2008 R2. It's installed on Windows 7 by default, but the service is disabled. You only need to enable WinRM on those computers that you want to send commands to. The computer you're physically sitting in front of doesn't need WinRM running.

PowerShell cmdlets all produce objects as their output. When you run a command remotely, its output objects need to be put into a form that can be easily transmitted over a network using the HTTP or HTTPS protocol. So, PowerShell automatically serializes output objects into XML files. The XML files are transmitted across the network. When they reach your computer, they're deserialized back into objects that PowerShell can work with. Why should you care? Because those serialized objects are really just snapshots. They don't update themselves continually. That is, if you were to get the objects that represent the processes running on a remote computer, what you get back will only be accurate for the exact point in time at which those objects were generated. Values such as memory usage and CPU utilization won't change. In addition, you can't tell the deserialized objects to do anything. For example, you can't instruct one to stop itself. That's a basic limitation of remoting, but it doesn't really stop you from doing some pretty amazing stuff.

There are just a few basic requirements to use remoting:

  • Both your computer (aka local computer) and the one you want to send commands to (aka remote computer) must be running Windows PowerShell 2.0. Windows XP is the oldest version of Windows on which you can install PowerShell 2.0, so it's the oldest version that can participate in a remote session.
  • Ideally, both the local and remote computers need to be members of the same domain or members of trusted/trusting domains. It's possible to get remoting to work outside of a domain, but it's tricky, so I won't be covering it here. To learn more about that scenario, see the PowerShell Help topic about_Remote_Troubleshooting.

WinRM Overview

Let's talk just a bit about WinRM because you're going to have to configure this service to start using remoting. Once again, you only need to configure WinRM and PowerShell remoting on the remote computers. In most of the environments I've worked in, the administrators have enabled remoting on every computer running XP or later. Doing so gives you the ability to remote into desktop and laptop computers in the background (meaning the user of those computers won't know you're doing so), which can be useful.

WinRM isn't unique to PowerShell. WinRM can route traffic to multiple administrative applications. WinRM essentially acts as a dispatcher. When traffic comes in, WinRM decides which application needs to deal with that traffic and tags that traffic with the name of the recipient application. Recipient applications must register with WinRM so that WinRM can listen for incoming traffic on their behalf. In other words, you not only need to enable WinRM but also register PowerShell as an endpoint with WinRM.

The easiest way to do both tasks is to open PowerShell as an administrator and run the Enable-PSRemoting cmdlet. You might see references to a different cmdlet named Set-WSManQuickConfig. There's no need to run that cmdlet. Enable-PSRemoting calls it for you and performs a few extra steps that are necessary to get remoting up and running. All told, the Enable-PSRemoting cmdlet starts the WinRM service, configures it to start automatically, registers PowerShell as an endpoint, and even sets up a Windows Firewall exception to permit incoming WinRM traffic.

If you're not excited about having to visit every computer to enable remoting, you can use a Group Policy Object (GPO) instead. The necessary GPO settings are built into Windows Server 2008 R2 domain controllers (DCs). Just open a GPO and navigate to Computer Configuration\Administrative Templates\Windows Components. Near the bottom of the list you'll find both Remote Shell and Windows Remote Management (WRM), which you need to configure. The Help topic about_Remote_Troubleshooting provides detailed instructions on how to do so. Just look for the "How to Enable Remoting in an Enterprise" and "How to Enable Listeners by Using a Group Policy" sections within that Help topic.

WinRM 2.0 (which is what PowerShell uses) defaults to using TCP port 5985 for HTTP and 5986 for HTTPS. These ports help ensure WinRM won't conflict with any locally installed Web servers, which tend to listen to ports 80 and 443. You can configure WinRM to use alternate ports, but I don't recommend doing so. If you leave those ports alone, then all of PowerShell's remoting commands will run normally. If you change the ports, you'll have to always specify an alternate port when you run a remoting command, which means more typing for you. If you absolutely must change the port, you can do so with the command:

Winrm set winrm/config/listener?Address=*
+Transport=HTTP @{Port="1234"}

Where 1234 is the port you want. (Although this command wraps here, you'd enter it all on one line. The same holds true for the other commands that wrap.) If you need to use HTTPS instead of HTTP, you can modify this command to set the new HTTPS port. I should admit that there is a way to configure WinRM on local computers to use alternate default ports, so that you don't have to constantly specify an alternate port when running remoting commands. But for now let's just stick with the defaults Microsoft came up with.

If you browse around in the GPO's Remote Shell settings, you'll notice that you can configure settings such as how long a remote session can sit idle before the server kills it, how many concurrent users can remote into a server at once, how much memory and how many processes each remote shell can utilize, and the maximum number of remote shells a given user can open at once. These settings help ensure that your servers don't get overly burdened by forgetful administrators. By default, however, you have to be an administrator to use remoting, so you don't need to worry about ordinary users clogging up your servers.

1:1 Remoting

With 1:1 remoting, you're basically accessing a shell prompt on a single remote computer. Any commands you run will run directly on that computer, and you'll see results in the shell window. This is vaguely similar to using Remote Desktop Connection, except that you're limited to PowerShell's command-line environment. PowerShell remoting uses a fraction of the resources that Remote Desktop requires, so it imposes much less overhead on your servers.

To establish a 1:1 connect with a remote computer named Server-R2, you'd run

Enter-PSSession -ComputerName Server-R2

Assuming that you enabled remoting on that computer, the computer is in the same domain, and your network is functioning correctly, you should get a connection going. PowerShell lets you know that you've succeeded by changing the shell prompt to

[server-r2] PS C:\>

The [server-r2] portion tells you that everything you're doing is taking place on Server-R2. You can then run whatever commands you like. You can even import any modules and add PowerShell snap-ins (PSSnapins) that happen to reside on that remote computer.

Even permissions are the same. Your copy of PowerShell will pass along whatever security token it's running under. (PowerShell does this with Kerberos, so it doesn't pass your username or password across the network.) Any command you run on the remote computer will run under your credentials, so anything you have permission to do, you'll be able to do. It's really just like logging directly into that computer's console and using its copy of PowerShell directly. Well, almost. There are a couple of differences:

  • If you have a PowerShell profile script on the remote computer, it won't run when you connect using remoting. Simply put, profiles are a batch of commands that automatically run each time you open the shell. People use them to automatically load shell extensions, modules, and so forth.
  • You're restricted by the remote computer's Execution Policy. Let's say your computer's policy is set to RemoteSigned so that you can run local unsigned scripts. If the remote computer's policy is set to Restricted (the default setting), it won't be running any scripts for you when you're remoting into it.

Many PowerShell cmdlets come in pairs, with one cmdlet doing something and the other doing the opposite. In this case, Enter-PSSession connects you to the remote computer and Exit-PSSession closes that connection. The Exit-PSSession cmdlet doesn't need any parameters. After it runs, the remote connection closes and your shell prompt changes back to the normal prompt. What if you forget to run Exit-PSSession? Don't worry. PowerShell and WinRM are smart enough to figure out what you did and will close the remote connection.

I do have one caution to offer. When you're connecting to a remote computer, don't run Enter-PSSession from the remote computer unless you fully understand what you're doing. For example, let's say you work on Computer A. You connect to Server-R2. At the PowerShell prompt, you run

[server-r2] PS C:\> Enter-PSSession

Server-R2 is now maintaining an open connection to Server-DC4. This creates a "remoting chain" that's hard to keep track of. It also imposes unnecessary overhead on your servers. There might be times when you have to do this (e.g., Server-DC4 sits behind a firewall and you can't access it directly, so you need to use Server-R2 as a middleman). But, as a general rule, try to avoid remote chaining.

                              1:n Remoting

One of the coolest things in PowerShell is 1:n remoting. It lets you send a command to multiple remote computers at the same time—that's right, full-scale distributed computing. Each computer will independently execute the command and send the results back to you. It's all done with the Invoke-Command cmdlet in a command such as

Invoke-Command -ComputerName
  -Command { Get-EventLog Security
  -Newest 200 |
  Where { $_.EventID -eq 1212 } }

The command in the outermost braces gets transmitted to all three remote computers. By default, PowerShell talks with up to 32 computers at once. If you specify more than 32 computers, it will queue them up. Then, as one computer completes, the next one in line begins. If you have a really awesome network and powerful computers, you could raise that number by using the cmdlet's -ThrottleLimit parameter. You can read about how to use this parameter in the Invoke-Command cmdlet's Help page.

One item you won't see in that cmdlet's Help page is the -Command parameter, yet the command I just showed you works fine. The -Command parameter is actually an alias, or nickname, for the
-ScriptBlock parameter that's listed in the Help page. I have an easier time remembering -Command, so I tend to use it instead of -ScriptBlock, but they both work the same way.

If you read the Help page for Invoke-Command carefully, you'll also notice a parameter that lets you specify a script file rather than a command. The -FilePath parameter lets you send an entire script to remote computers, which means you can automate some pretty complex tasks and have each computer do its own share of the work.

I want to circle back to the -ComputerName parameter for just a bit. In the sample Invoke-Command code, I used a comma-separated list of computer names. If you have a lot of computers, you might not want to type all their names every time you want to connect to them. Instead, you can create a text file that contains one computer name per line, with no commas, no quotes, or anything else. For example, if your text file is named webservers.txt, you'd use the code

Invoke-Command -Command { dir }
-ComputerName (Get-Content webservers.txt)

The parentheses force PowerShell to execute the Get-Content cmdlet first—pretty much the same way parentheses work in math. The Get-Content cmdlet's results are then put into the -ComputerName parameter.

Querying computer names in Active Directory (AD) is also possible, but it's a bit trickier. You can use the Get-ADComputer cmdlet to retrieve the computers, but you can't stick that command in parentheses like you do with Get-Content. Why not? Get-Content produces simple strings of text, whereas Get-ADComputer produces computer objects. The -ComputerName parameter is expecting strings. If it were to receive computer objects, it wouldn't know what to do with them. So, if you want to use Get-ADComputer, you need to get the values from the computer objects' Name properties. Here's how

Invoke-Command -Command { dir }
  -ComputerName (Get-ADComputer -Filter *
  -SearchBase "ou=Sales,dc=company,dc=pri" |
  Select-Object -Expand Name)

Within the parentheses, the computer objects are piped to the Select-Object cmdlet and its -Expand parameter is used to expand the Name property of those computer objects. The result of the parenthetical expression is a bunch of computer names, not computer objects—and computer names are exactly what the -ComputerName parameter wants.

In case you're unfamiliar with Get-ADComputer, let's take a look at what this cmdlet is doing. The -Filter parameter is specifying that all computers should be included in the output and the -SearchBase parameter is telling PowerShell to start looking for computers in the Sales organizational unit (OU) of the company.pri domain. Get-ADComputer is available only on Windows Server 2008 R2 and on Windows 7 after installing the Remote Server Administration Tools. On those OSs, you have to run

Import-Module ActiveDirectory

to load the AD cmdlets into the shell so that they can be used.

But Wait, There's More

These examples have all been for ad-hoc remote sessions. If you're going to be reconnecting to the same computer (or computers) several times within a short period of time, you can create reusable, persistent sessions instead. That's especially helpful if the connection requires alternate credentials, a nondefault port number, or something else that requires additional parameters.

To create persistent sessions, you use the New-PSSession cmdlet, then save them in a variable for easy access. For example, the following code creates remote sessions with three computers, then stores them in the $sessions variable

$sessions = New-PSSession
  -ComputerName One,Two,Three
  -Port 5555
  -Credential DOMAIN\Administrator

The remote sessions close automatically when you close the shell, but until then they can take up memory and a tiny bit of CPU on both the local and remote machines. To explicitly close them, you can use the Remove-PSSession cmdlet

$sessions | Remove-PSSession

When you need to re-open the sessions, you can use the Invoke-Command cmdlet

Invoke-Command -Command { dir }
 -Session $sessions

or the Enter-PSSession cmdlet

 -Session $session[1]

Note that in the Enter-PSSession code, only one remote session is being re-opened. The index number 1 tells PowerShell to re-open the session with the computer named Two. (It's a zero-based index.)

Extend Your Reach

PowerShell remoting has a lot of power and utility. If you use it, you'll find that it really extends your reach.