The Invoke-Command cmdlet is one way to leverage PowerShell Remoting. In today’s post, I will give you an overview of Invoke-Command.
Note: To follow this guide, you have first have to enable PowerShell remoting. Read our guide that describes all the different ways of how you can enable remoting locally and remotely. You will learn how to enable remoting remotely with various tools, how to allow non-admins to use remoting, and to configure remoting in workgroup environments.
Michael Pietroforte

Introducing PowerShell Remoting

When it comes to managing remote computers with PowerShell, you have essentially three options. You can open an interactive session with the Enter-PSSession cmdlet (One-to-One Remoting). An alternative is the Invoke-Command cmdlet, which allows you to run remote commands on multiple computers (which is why it is called One-to-Many Remoting). The third option is to use one of those cmdlets that offer a ComputerName parameter. In most cases, PowerShell Remoting isn’t involved then. To list those cmdlets, you can use this command:

Get-Command -ParameterName ComputerName

Cmdlets with the ComputerName parameter

Cmdlets with the ComputerName parameter

In addition to the built-in cmdlets, quite a few PowerShell modules exist that support remote management. For instance, you can manage Active Directory from your workstation after installing the Active Directory module. Whenever one of these options exists, I would avoid enabling PowerShell Remoting on the remote machine.

Because PowerShell Remoting presents a security risk, it has to be enabled even within an Active Directory domain. You can also use Remoting on workgroup computers, but you will have to take care of a few extra configurations. If you don’t configure Remoting properly, you will run into errors such as WinRM cannot process the request or Access denied. In the latter case, you probably failed to provide admin credentials.

PowerShell Remoting depends on Windows Remote Management (WinRM), which is Microsoft’s implementation of the WS-Management (WS-Man) protocol. The protocol relies on HTTP or HTTPS and uses the TCP ports 5985 and 5986, respectively.

WS-Management encrypts all PowerShell communication even if you only work with HTTP. However, this kind of encryption is vulnerable to man-in-the middle attacks. I therefore recommended to use HTTPS instead of HTTP in insecure networks. I will say more about this topic in a follow-up post.

Executing cmdlets with Invoke-Command

Perhaps the most interesting form of PowerShell remote management is One-to-Many Remoting with the Invoke-Command cmdlet. This cmdlet allows you to execute PowerShell commands on multiple remote computers that contain cmdlets that don’t feature the -ComputerName parameter.

Two ways exist to connect to remote computers with Invoke-Command. You usually use the ‑ComputerName parameter to manage Windows machines. Alternatively, you can pass the ‑ConnectionUri parameter to manage backend applications, such as Exchange, or cloud services such as Azure.

In the remainder of this article, I will focus on managing remote Windows machines that are Active Directory domain members. The command below executes the Get-ChildItem cmdlet on the machine whose computer name is stored in the $RemoteComputer variable:

Invoke-Command -Computername $RemoteComputer -ScriptBlock { Get-ChildItem "C:\Program Files" }

PowerShell automatically creates a so-called PSSession on the remote computer, which essentially is a user-created PowerShell session as opposed to a session that a PowerShell host such as the PowerShell console or PowerShell ISE creates. The output of the executed commands in the script block is automatically relayed to your local session. As usual, you can interrupt the displaying of the output with CTRL+C.

As with the Enter-PSSession cmdlet, you have to be an administrator on the remote machine. Otherwise, you will receive an Access denied error message. If you are not logged on as domain administrator on your local machine, you have to provide sufficient credentials with the -Credential parameter.

You can pass multiple computers to the -ComputerName parameter by separating their names with commas. If you also want to include the local computer, you add “localhost” or simply “.” to the list.

To run multiple commands, you can separate them with semicolons in the script block. However, a better option is to use the -FilePath parameter instead of the -ScriptBlock parameter, which allows you to execute an entire PowerShell script:

Invoke-Command -ComputerName PC1,PC2,PC3 -FilePath C:\myFolder\myScript.ps1

By default, PowerShell will send the script immediately to all computers if 32 or fewer computer names are passed. If more than 32 computer names are passed, PowerShell will queue the surplus computers until the script completes in one of the first 32 PSSessions. You can change this default behavior with the -ThrottleLimit parameter. However, you should be aware of the implications on the load of your computer and the network when the results come pouring in.

Testing if Remoting is enabled

In particular, if you plan to execute commands on many machines, you might want to test their availability with the Test-Connection cmdlet first. This cmdlet sends ICMP echo request packets (pings, that is) to the remote computers:

If (Test-Connection -ComputerName $RemoteComputers -Quiet)
{
     Invoke-Command -ComputerName $RemoteComputers -ScriptBlock {Get-ChildItem “C:\Program Files”}
}

The -Quiet parameter suppresses the output of the cmdlet and returns $True if the remote machine answers the pings.

This method is fine if you can be sure that Remoting is enabled on the remote machines. If you want a more bullet-proof test, you could use the Test-WSMan cmdlet to check whether the WinRM service can be accessed on the remote machine. However, a downside of this method is that the cmdlet’s timeout can be several seconds and can’t be configured. If you have many computers in your list, this can slow down your control script on the local machine significantly.

An alternative is to work with a try-catch block:

$RemoteComputers = @("PC1","PC2")
ForEach ($Computer in $RemoteComputers)
{
     Try
         {
             Invoke-Command -ComputerName $Computer -ScriptBlock {Get-ChildItem "C:\Program Files"} -ErrorAction Stop
         }
     Catch
         {
             Add-Content Unavailable-Computers.txt $Computer
         }
}

If Invoke-Command fails to execute the command on the computer, the corresponding computer name is added to a text file (line 10). This enables you to repeat the command later.

Passing local variables to the remote session

If you want to pass variables from your control script on the local computer to the commands that run on the remote computers, you can work with the -ArgumentList parameter:

$Name = "svchost"
$CPU = 100
Invoke-Command –ComputerName $RemoteComputer –ScriptBlock { param($RName,$RCPU) Get-Process -Name $RName | Where -Property CPU -gt $RCPU } -ArgumentList $Name,$CPU

In the example, the values of the $Name and $CPU variables are passed to the $RName and $RCPU variables, which the Get-Process cmdlet will use on the remote computer to determine all processes with the name “svchost” where the CPU usage (CPU property of Get-Process) is greater than 100.

By the way, this example also demonstrates that PowerShell Remoting is sometimes your only option even if the cmdlet you want to use offers a -ComputerName parameter. The Get-Process cmdlet cannot retrieve the CPU usage from a remote computer if you run it in a local session. However, if you use the Invoke-Command cmdlet, Get-Process runs in a remote session and you can then read the CPU usage.

Accessing methods in PowerShell Remoting

Using Invoke-Command also has a downside. The objects that PowerShell returns as output differ depending on whether you run the command in a PSSession or in a session that the PowerShell host creates. The objects even differ if you run Invoke-Command on the local computer.

The first thing you notice is that the returned objects have an additional property: the PSComputerName property. You guessed it—it contains the name of the computer where the corresponding PSSession ran.

Invoke-Command -ComputerName . -Scriptblock {Get-Process}

The PSComputerName property

The PSComputerName property

You need this additional property to ascribe the results to computers if you executed commands on multiple machines. Okay, an additional property is not really a downside, but the result objects also lack a crucial feature. Try this:

Get-Process | Get-Member -MemberType Method

The methods of the Process object

The methods of the Process object

And then this:

Invoke-Command -ComputerName localhost -Scriptblock {Get-Process} | Get-Member -MemberType Method

The methods of a deserialized object

The methods of a deserialized object

Obviously, PowerShell stripped off all the interesting methods. In this case, this means that you can’t use the object that Invoke-Command returns to kill a remote process because the Kill() method is unavailable.

Another thing you notice is that the TypeNames are different. In the first case, it is System.Diagnostics.Process, and the second command returns Deserialized.System.Diagnostics.Process. If you execute a command with Invoke-Command, it packs the result object in XML, transmits it to the calling session, and creates a so-called static object that lacks all methods except GetType() and ToString().

The conversion to XML is called serialization; the opposite process is deserialization. This is why TypeName is “Deserialized.” The lack of methods makes sense because the objects to which you could apply the methods (in our example, the processes) are only available in the remote session, and you therefore can’t manipulate them in the local session.

The one remaining question is how you can use methods of objects on remote machines. Let’s stay with our example from above. Because the Kill() method is unavailable in the deserialized static object, you have to kill the process in the remote session before the result object is transmitted to your local session. The following command kills Notepad on the remote computer:

Invoke-Command -ComputerName $RemoteComputer -ScriptBlock {(Get-Process | Where -Property ProcessName -eq notepad).Kill()}

Thus, you can access methods of PSSessions objects. All you have to do is build the logic that needs the methods, not in your control script on the local computer but in the script that runs on the remote machine.

In one of my next posts, I will talk about running PowerShell commands remotely in disconnected sessions.

avataravataravatar

57 Comments

Leave a reply

Please enclose code in pre tags: <pre></pre>

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2024

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending
WindowsUpdatePreventer

Log in with your credentials

or    

Forgot your details?

Create Account