Just in case you have a computer or two that isn’t located in your office.


According to the old joke, the three most important things in real estate are location, location, location. And in Windows PowerShell 2.0 the three most important things are –


Well, to tell, you the truth, we don’t actually know what the three most important things are in Windows PowerShell 2.0. What we do know is that the three most important things in Windows PowerShell 2.0 are not location, location, location; that’s because location doesn’t matter in Windows PowerShell 2.0. Not in the least. (Well, except with profiles. But we’re not talking about profiles right now.)


Admittedly, that wasn’t the case with the original release of Windows PowerShell; in PowerShell 1.0 location – or, to be more specific – your location – was extremely important. That’s because Windows PowerShell 1.0 did not support (except in one or two minor instances) the concept of remoting, the ability for you to sit at computer A and retrieve information from, or make changes to, computer B. The only way for you to retrieve information from computer B was to physically sit at computer B. The idea of sitting at your desk and managing all your computers simply by transmitting commands across the network? As they say in New York, fuhhgeddaboutit.


Note. If you’re not from New York, that can be translated like this: forget about it.


Now, that might not have been all that bad except for one thing: Windows PowerShell was being promoted as the tool for system administration. System administration that could only be done locally, and only on one computer at a time. As they say in New York –


Well, you’re right: they say a lot of things in New York, don’t they? Never mind.


But that was then and this is, um, not then. With Windows PowerShell 2.0 you can now sit at computer A and manage computer B; for that matter, you can also manage computers C, D, E, F, and G. Remoting is now a real and viable part of Windows PowerShell; in fact, PowerShell now features three different ways to manage remote computers:


        By using .NET remoting. A handful of Windows PowerShell cmdlets (including Get- Service and Get-Event) harness the remoting capabilities built into the .NET Framework; that means that you can use these cmdlets to directly return data from a remote computer. (As opposed to making a transient or persistent connection.)

        By making a transient connection to a remote computer. With a transient connection you make a temporary connection to a remote computer. How temporary is this connection? Very temporary: you make the connection, run a single command, and then the connection is automatically terminated.

        By making a persistent connection to a remote computer. With a persistent connection you make a connection to a remote computer and then that connection remains open until you explicitly close it. That means you can run as many commands as you like, without ever having to open a new connection.


In this article, we’ll give you a quick introduction to all three methods of managing remote computers.


What do I need in order to make these remote connections?


To tell you the truth, not much. Obviously you need Windows PowerShell 2.0. Or to be more precise, you need the Windows Management Framework, which includes Windows PowerShell 2.0, Windows Remote Management (WinRM) 2.0, and Background Intelligent Transfer Service (BITS) 4.0.


In addition, you’ll need version 2.0 of the .NET Framework; there’s a pretty good chance you already have this installed in your computer.


And keep in mind that this software not only needs to be installed on your computer, but it also needs to be installed on every computer that you want to be able to manage remotely. Do you want to be able to manage all your client computers using PowerShell 2.0? Then PowerShell 2.0, .NET 2.0, and WinRM must be installed on each of those client computers.


Oh, and did we mention that you need to be a local administrator on your computer and on each remote computer you try to connect to? Well, we should have, because that’s absolutely crucial.


How does remoting work?


In a nutshell, here’s how remoting works in Windows PowerShell 2.0. To begin with, you make a connection between your computer and a remote computer. You then type a command on your computer and that command is transmitted across the network to the remote computer. (In case you’re worried, don’t be: all these transmissions are encrypted and secure.) The command is then executed on the remote computer. Note, however, that the command runs “invisibly;” nothing happens on the remote computer to indicate that the computer is running a Windows PowerShell command. When the command completes, the output is converted to XML format and transmitted back to your computer. Your computer then converts that XML packet back into a Windows PowerShell object.


The fact that PowerShell remoting transmits XML (or, to be more specific, SOAP packets) explains why this type of remoting is considered firewall-friendly, and can be carried out across the Internet. Previous system administration tools relied on DCOM (distributed COM) in order to do remoting; with DCOM, objects are transmitted across the network. That’s a problem: by default, most firewalls are designed to block objects. However, most firewalls are configured to allow XML packets; thus PowerShell remoting can typically be used without additional firewall configuration, and without allowing potentially-unsafe objects onto your network.


Can we get started now?


OK, good point, maybe it is time to actually start doing something. Let’s start by taking a minute to discuss .NET remoting, a type of remoting that – as we noted – applies to only a few cmdlets.


Note. Which cmdlets? In general, cmdlets that support the -ComputerName parameter support .NET remoting. You can retrieve a list of cmdlets that support the -ComputerName parameter by typing the following command at the Windows PowerShell prompt and then pressing ENTER:


Get-Help * -Parameter ComputerName


By default, when you run a cmdlet like Get-Service you simply, well, run the cmdlet, like so:




Do that, and Get-Service will retrieve information about all the services running on the local computer:


Status   Name               DisplayName

------   ----               -----------

Stopped  Adobe LM Service   Adobe LM Service

Stopped  AdobeActiveFile... Adobe Active File Monitor V4

Stopped  Alerter            Alerter

Running  ALG                Application Layer Gateway Service

Running  Apple Mobile De... Apple Mobile Device

Stopped  AppMgmt            Application Management

Running  ASChannel          Local Communication Channel

Stopped  aspnet_state       ASP.NET State Service

Stopped  Ati HotKey Poller  Ati HotKey Poller

Running  AudioSrv           Windows Audio


That’s simple enough. But what if you want to run that command against a remote computer? As it turns out, that’s simple enough, too: you just add the -ComputerName parameter followed by the name (or the IP address, or the fully qualified domain name) of the remote computer. For example, this command returns information from the remote computer atl-ocs-001:


Get-Service –ComputerName atl-ocs-001


Run this command, and you’ll get back something similar to this:


Status   Name               DisplayName

------   ----               -----------

Stopped Adobe LM Service    Adobe LM Service

Stopped AdobeActiveFile...  Adobe Active File Monitor V4

Stopped Alerter             Alerter

Running ALG                 Application Layer Gateway Service

Running Apple Mobile De...  Apple Mobile Device

Stopped AppMgmt             Application Management

Running ASChannel           Local Communication Channel

Stopped aspnet_state        ASP.NET State Service

Stopped Ati HotKey Poller   Ati HotKey Poller

Running AudioSrv            Windows Audio


As you can see, the output – well, now that you mention it, the output does look a lot like the output we got when we ran Get-Service without the-ComputerName parameter, doesn’t it? There’s actually two reasons for that. For one, we are retrieving service information from the remote computer; needless to say, service information from one computer will always resemble service information from another computer. In addition to that, Get-Service typically suppresses the value of the MachineName property, a property that indicates which computer the service is running on. But that’s OK; we can use a command like this to ask Get-Service to show us just the values of the DisplayName and the MachineName properties:


Get-Service –ComputerName atl-ocs-001 | Select-Object DisplayName, MachineName


Now take a peek at what we get back:


DisplayName                             MachineName

-----------                             -----------

Adobe LM Service                        atl-ocs-001

Adobe Active File Monitor V4            atl-ocs-001

Alerter                                 atl-ocs-001

Application Layer Gateway Service       atl-ocs-001

Apple Mobile Device                     atl-ocs-001

Application Management                  atl-ocs-001

Local Communication Channel             atl-ocs-001

ASP.NET State Service                   atl-ocs-001

Ati HotKey Poller                       atl-ocs-001

Windows Audio                           atl-ocs-001

Background Intelligent Transfer Ser...  atl-ocs-001


Not bad, huh? Incidentally, you aren’t limited to passing just one machine name to the –ComputerName parameter. Want to retrieve service information from computers atl-ocs-001, atl-ocs-002, and atl-ocs-003? Then just ask Get-Service to retrieve service information from each of these three machines, separating the computer names with commas:


Get-Service –ComputerName atl-ocs-001,atl-ocs-002,atl-ocs-003


Give that a try and see what happens.


Making a transient connection


As we noted, .NET remoting is supported on only a handful of cmdlets. That’s great, but what if you need to use a cmdlet that doesn’t support .NET remoting? Are you just plain out of luck?


Let’s hope not; otherwise this is going to a very disappointing article. (Of course, it might be anyway. But that has nothing to do with .NET remoting.) Fortunately for all of us, the answer to that question is: no, you are not out of luck. As long as you can make a connection to a remote computer you can run pretty much any cmdlet you want against that remote computer.


Which leads to a very obvious follow-up question: so how do you make a connection to a remote computer? Remember awhile back when we described transient connections, a temporary connection that enables you to run a single command against a remote computer? Well, here’s how you make a transient connection to a remote computer:


Invoke-Command -ComputerName atl-ocs-001 -ScriptBlock {Get-PSDrive}


Let’s see if we can figure out how this command works. We start by calling Invoke-Command, the cmdlet that enables us to make a connection to a remote computer and then run a single command against that computer. To indicate the name of that remote computer, we add the -ComputerName parameter followed by the name (or, again, the IP address or the fully qualified domain name) of the remote computer. As you can see, in this example that’s atl-ocs-001.


As for which command we’re going to run against atl-ocs-001, we indicate that by including the -ScriptBlock parameter followed by the command to be run; in this case, that’s the Get-PSDrive cmdlet. Note that the command must be enclosed in curly braces; in Windows PowerShell, the curly braces tell the system that the item inside the braces is a command to be executed rather than a text value.


And, believe it or not, that’s all there is to it. (OK, there are additional parameters that do things such as enable you to run the command under alternate credentials, but we’re going to skip those parameters for now.) When we run this command we should get back something similar to this:


Name             Provider   Root  CurrentLocation  PSComputerName

----             --------   ----  ---------------  --------------

A                           A:\                    atl-ocs-001

Alias                                              atl-ocs-001

C                           C:\                    atl-ocs-001

cert                        \                      atl-ocs-001

Env                                                atl-ocs-001

Function                                           atl-ocs-001

HKCU HKEY_CU...                                    atl-ocs-001

HKLM HKEY_LO...                                    atl-ocs-001

Variable                                           atl-ocs-001

WSMan                                              atl-ocs-001


There are two things of particular to note in this output. First, take a peek at the PSComputerName property. This is a property that Invoke-Command automatically adds to returned data; as you might have guessed, it tells us where that data came from. As you can see, all the retrieved data originated on the computer atl-ocs-001.


Second, take a look at the values returned for the CurrentLocation property. CurrentLocation tells us, well, the current location within the PowerShell drive; for example, if you were working in the PowerShell console and your current folder was C:\Scripts then the CurrentLocation for drive C would be C:\Scripts. So then why are all the CurrentLocation fields blank in our output? That’s easy: that’s because this data came from a remote computer, and our command was running in an invisible window rather than in the Windows PowerShell console. PowerShell wasn’t up and running, so there weren’t any current locations.


See? We told you this data came from a remote computer.


Even better, you can make transient connections against multiple computers. All you have to do is add all the computer names to the –ComputerName parameter, just like we did with .NET remoting:


Invoke-Command -ComputerName atl-ocs-001,atl-ocs-002,atl-ocs-003 `

-ScriptBlock {Get-PSDrive}


Here’s another cool little trick. What if you’d like to get information from the local computer as well as from a remote computer (or computers)? Well, that’s no problem; you can always include the name of the local computer as one of the computer names supplied to the –ComputerName parameter. But if you want to be cool (and, let’s face it, who doesn’t want to be cool?) you could do one of these two things instead;


        Use a dot (.) instead of the local computer name.

        Use localhost instead of the local computer name.


In other words, this command runs Get-PSDrive against the local computer:


Invoke-Command -ComputerName . -ScriptBlock {Get-PSDrive}


Oh, and so does this one:


Invoke-Command -ComputerName localhost -ScriptBlock {Get-PSDrive}


Making a persistent connection


The one problem with a transient connection is that it’s transient: as soon as your one command finishes executing your connection is closed. Need to run another command? Then you need to open a second connection. Need to run a third command? Then you need to open a third – well, you get the idea. Transient connections work for simple tasks, but they’re obviously less useful for more complex tasks. For something like that you need a connection that’s a bit more persistent. You know, like a, say, persistent connection ….


To explain how a persistent connection works let’s walk you through a simple example. In order to make a persistent connection you need to create a new PowerShell (PS) session. The easiest way to do that is to use a command similar to this (in case you’re wondering why we left in the PowerShell prompt – PS C:\Scripts> – well, you’ll find out in just a second):


PS C:\Scripts> Enter-PSSession -ComputerName atl-ocs-001


As you can see, all we’ve done here is call the Enter-PSSession cmdlet along with the –ComputerName parameter; the value passed to –ComputerName is – oh, you guessed it. Well, you’re right: it’s the name of the remote computer.


So what’s going to happen when we run this command? This is going to happen:


[atl-ocs-001]: PS C:\Users\Administrator\Documents>


As you can see, our prompt has changed, and in two very important ways. First, we’re now working on the remote computer atl-ocs-001; notice how the prompt now begins with [atl-ocs-001] to let us know which computer we’re on. Second, note that we’re now working in the C:\Users\Administrator\Documents folder. But remember, that’s the C:\Users\Administrator\Documents folder on the remote computer atl-ocs-001. From now on (at least until we end our session) everything we type, and every command we run, will execute on atl-ocs-001.


Here’s an example. The following command retrieves the value of the environment variable ComputerName, an environment variable that reports back the name of the local computer:


[atl-ocs-001]: PS C:\Users\Administrator\Documents> $env:computername


When we run this command we should get back the following:



[atl-ocs-001]: PS C:\Users\Administrator\Documents>


Notice the prompt? As you can see, even though we just ran a command, we’re still working on the remote computer atl-ocs-001; that’s because we have a persistent connection. Let’s try another command and see what happens:


[atl-ocs-001]: PS C:\Users\Administrator\Documents> Get-Process


Handles NPM(K) PM(K) WS(K) VM(M) CPU(s)   Id   ProcessName

------- ------ ----- ----- ----- ------   --   -----------

     47      5  1852  4352    62   0.08  340   notepad

     39      5  1852  4332    43   0.11  1128  calc

     87      5  1848  4288   118   0.08  1260  winword


[atl-ocs-001]: PS C:\Users\Administrator\Documents>


Again, take a look at the prompt: we’re still working on the remote computer atl-ocs-001. And that will last until the end of time or until we decide to terminate our session.


Whichever comes first.


Speaking of which, what if we have decided to terminate our session? That’s fine; all we have to do is type the exit command:


[atl-ocs-001]: PS C:\Users\Administrator\Documents> exit


PS C:\Scripts


As you can see (hint: look at the prompt) we’re no longer working on the computer atl-ocs-001. Instead, we’re back on our local computer, and back to the good old C:\Scripts folder to boot. You know what they say: be it ever so humble, there’s no place like home.


And yes, now that you mention it, we can create a remote session that runs on multiple computers at the same time. In fact, you already know how to do that, don’t you?


PS C:\Scripts> Enter-PSSession -ComputerName atl-ocs-001,atl-ocs-002,



You can also take a different tack: you can open a session to computer A, open a separate session to computer B, and then switch back and forth between those sessions. Take a look at these commands:


PS C:\Scripts> $a = New-PSSession -ComputerName atl-ocs-001

PS C:\Scripts> $b = New-PSSession -ComputerName atl-ocs-002

PS C:\Scripts>


As you can see, in the first command we used the New-PSSession cmdlet to open a new PowerShell session on the remote computer atl-ocs-001; that session was then saved in a variable named $a. In the second command, we opened a new PowerShell session on the remote computer atl-ocs-002; that session was saved in a variable named $b. And after we execute that second command we – uh-oh, it looks like we’re still on the local computer, and still in the C:\Scripts folder, aren’t we? Apparently our two new commands didn’t work, did they?


Oh ye of little faith. Our commands did work. New-PSSession did just what it was supposed to do: it created two new PowerShell sessions. We can verify that by using Get-PSSession to list the remote sessions currently running on our computer:


Id Name     ComputerName State  Configuration

-- ----     ------------ -----  -------------

1  Session1 atl-ocs-001  Opened Microsoft.PowerShell

2  Session2 atl-ocs-002  Opened Microsoft.PowerShell


As you can see, we have two open connections. The only reason our prompt still reflects the local computer is because all we’ve done so far is open those connections; we haven’t actually entered those connections yet. To do that, we need to call the Enter-PSSession cmdlet followed by the appropriate variable. For example, to enter session 2 (the one running on atl-ocs-002) we’d use this command:


PS C:\scripts> Enter-PSSession $b

[atl-ocs-002]: PS C:\Users\Administrator\Documents>


As usual, look at the prompt: we’re now working on the computer atl-ocs-002.


Now, suppose we want to exit this session. As you might expect, all you have to do is type the exit command:


[atl-ocs-002]: PS C:\Users\Administrator\Documents> exit

PS C:\Scripts>


From this point we could now enter session 1 (Enter-PSSession $a), or we could re-enter session 2. In fact we – what’s that? You know, now that you mention it, session 2 is still running, isn’t it? Take a look at the results of running Get-PSSession:


Id Name     ComputerName State  Configuration

-- ----     ------------ -----  -------------

1  Session1 atl-ocs-001  Opened Microsoft.PowerShell

2  Session2 atl-ocs-002  Opened Microsoft.PowerShell


But, you ask, didn’t we just run the exit command? Yes, we did, and PowerShell dutifully exited the session. However, when you are running multiple PowerShell sessions exiting does not terminate the connection; it merely returns you back to the local computer. If you truly want to terminate the connection you need to exit and then run the Remove-PSSession cmdlet:


PS C:\Scripts> Remove-PSSession $b


Now look at what we get back when we run Get-PSSession:


Id Name     ComputerName State  Configuration

-- ----     ------------ -----  -------------

1  Session1 atl-ocs-001  Opened Microsoft.PowerShell

2  Session2 atl-ocs-002  Closed Microsoft.PowerShell


Session 2 is now shown as closed. And, trust us, it’s really closed. Look what happens if we try to re-enter the session:


PS C:\Scripts> Enter-PSSession $b

Enter-PSSession : Session must be open.

At line:1 char:16

+ Enter-PSSession <<<< $b

+ CategoryInfo : InvalidArgument: (:) [Enter-PSSession], ArgumentException

+ FullyQualifiedErrorId : PushedRunspaceMustBeOpen,Microsoft.PowerShell.Commands.



We can’t enter the session, because the session is no longer open.


Note. Although the session is closed, $b will still contain an object reference to that session. To remove that object reference you can simply delete the variable:


Remove-Variable b