Multi Hop Windows Remote Management

Summary: There are cases where it’s necessary to use Windows Remote Management (WinRM), also known as WS-Management (WS-Man) to automate Windows Servers (especially Windows Server that are behind a Windows hop server). This is handy when there is no direct network access to the Windows server that need to be reached (typically for security reasons).

In this example, the following command is executed on the ThirdServer (through the FirstServer and then the SecondServer) in order to update a firewall rule to allow the WinRM service to respond to any source computer request (rather than just the local subnet).

Set-NetFirewallRule -Name WINRM-HTTP-In-TCP-PUBLIC -Action "Allow" -Direction "Inbound" -RemoteAddress "Any"

The default configuration for the WinRM firewall rule in Windows Server 2012+ is to only allow WinRM requests that originate from the local subnet of that server. This command changes a firewall rule to open WinRM to respond to requests from any source IP address.


In addition, for environments that require multi-hop access over and to Windows Servers, RDP can be problematic if there are any network bandwidth or latency issues. For actions that don’t require access to the Windows desktop, WinRM is ideal since it is much more efficient and faster.

Note: The authentication token for the session on the ThirdServer may be reduced compared to the access available for the FirstServer. Specifically for access to external resources like network shares. 


# Version:: 0.1.0 (1/13/2016)
# Script Description:: Expands WinRM scope.
# Author(s):: Otto Helweg

Write-Host "Configuring WinRM for remote access..."
# Get the necessary credentials for WinRM (usually Administrator level creds)
$creds = Get-Credential
$serverName = "FirstServer"
$secondServerName = "SecondServer"
$thirdServerName = "ThirdServer"

Write-Host "Running command from $serverName"
Invoke-Command -ComputerName $serverName -Credential $creds -ScriptBlock {
  Write-Host "Running command from $secondServerName"
  Invoke-Command -ComputerName $secondServerName -Credential $creds -ScriptBlock {
    Write-Host "Running command from $thirdServerName"
    Invoke-Command -ComputerName $thirdServerName -Credential $creds -ScriptBlock {
      Set-NetFirewallRule -Name WINRM-HTTP-In-TCP-PUBLIC -Action "Allow" -Direction "Inbound" -RemoteAddress "Any"
  } -ArgumentList $thirdServerName,$creds
} -ArgumentList $secondServerName,$thirdServerName,$creds

Note: The username for the credentials, needs to include the domain or server prefix. If this is a local account, use the ‘local\’ prefix. Therefore a local ‘Administrator’ account should be entered as ‘local\Administrator’.


A Few Good Windows Remote Management Commands

In Vista and beyond, a lot of instrumentation, configuration, and utilization information is exposed via WS-Man. WS-Man (aka: WS-Management, Windows Remote Management, and WinRM) incorporates many features, but I like to think of it as the management protocol/framework of the future (look out SNMP!). What makes WS-Man so great is the fact that it’s all standards based, rides on HTTP/HTTPS (very firewall/NAT friendly), and packages its data in SOAP/XML packets (easy to shove into a database or use with a script).

Out of the box; Vista WS-Man exposes WMI information as well as Windows Remote Shell capabilities. What this means is that with WS-Man it’s much easier to get instrumentation from remote machines as well as use that info in scripts.

Here are some sample commands to play with. If you cannot get the ‘Test WS-Man…’ step to work, none of the steps following will work either (you’re probably not using the right credentials to access the remote machine). One more caveat, the remote commands work best on domain joined machines. For workgroup machines, the WinRM service needs additional configuration.

Quickly configure the WS-Man service (Run from an Elevated Command prompt):

winrm QuickConfig

Quickly delete the WS-Man listener (Run from an Elevated Command prompt):

winrm invoke Restore winrm/Config @{}

Display your machine’s basic hardware info:

winrm enumerate wmicimv2/Win32_ComputerSystem

Display your operating system properties:

winrm get wmicimv2/Win32_OperatingSystem

Output your OS info in XML:

winrm get wmicimv2/Win32_OperatingSystem -format:pretty

Ping WS-Man on a remote machine:

winrm id -auth:none -remote:<some machine>

Test WS-Man access to a remote machine**:

winrm id -remote:<some machine>

Grab a remote machine’s WS-Man config:

winrm get winrm/Config -r:<some machine>

Grab a remote machine’s CPU load:

winrm g wmicimv2/Win32_Processor?DeviceID=CPU0 -fragment:LoadPercentage -r:<some computer>

Grab a remote machine’s free memory:

winrm g wmicimv2/Win32_OperatingSystem -fragment:FreePhysicalMemory -r:<some computer>

Stop a service on a remote machine:

winrm invoke stopservice wmicimv2/Win32_Service?name=w32time -r:<some computer>

Start a service on a remote machine:

winrm invoke startservice wmicimv2/Win32_Service?name=w32time -r:<some computer>

Reboot a remote machine:

winrm invoke reboot wmicimv2/Win32_OperatingSystem -r:<some computer>

Run a command on a remote machine (this uses winrS, not winrM):

winrs -r:<some computer> ipconfig /all

Use PowerShell to grab the WS-Man Win32_OperatingSystem XML output (Run from PowerShell):

[xml]$osInfo = winrm get wmicimv2/Win32_OperatingSystem /format:pretty

Display the OS version property:


Display the last boot time:


Put free memory metric into an XML variable:

[xml]$freemem = cmd /c "winrm get wmicimv2/Win32_OperatingSystem -fragment:FreePhysicalMemory -f:pretty -r:<some computer>"

Display the free memory value:


Note: This step verifies that you have good connectivity to the remote machine, WS-Man is running and properly configured on the remote machine, AND you have the correct permissions to fully leverage WS-Man on the remote machine. If this step fails, it’s probably a permissions issue.


WS-Man (WinRM) Architecture

The following diagram shows a high-level overview of the WS-Man (WinRM) architecture. In the diagram the ‘Client’ is querying the ‘Server’ for WS-Man information. Note that HTTP.sys and WinHTTP support the HTTP(s) transport for WS-Man, not IIS. In addition, IIS (or another web publishing service) can co-exist with WS-Man and share port 80 (granted, in Windows 7 and beyond, the default ports are 5985 and 5986 for http and https respectively, although I still prefer to use 80 and 443).


Quickly configure the WS-Man service

Remember, this needs to be run from an ‘Elevated’ Command Prompt.

As you can see, this simple command does quite a bit. Please note every modification (hightlighted) since this might increase the attack surface of your computer. For example, Quick Config configures a listener that accepts connections from every network interface. This is probably not ideal for edge machines that connect to unsecure networks (like the Internet). In addition, this command only needs to be run once.


Quickly delete the WS-Man listener

Although this command only deletes all WinRM listeners, it effectively turns off any WS-Man communication to a machine.


Display your machine’s basic hardware info and operating system properties

Win32_ComputerSystem and Win32_OperatingSystem are common WMI classes and useful for asset information and configuration information as well as some utilization metrics.


Output your OS info in XML

XML output makes the data much easier for storing in a database or dealing with programmatically (like with a script). PowerShell makes this that much easier since it works VERY well with XML (see below for a sample PowerShell script).


Ping WS-Man on a remote machine and test authorization credentials

Testing WS-Man on a remote machine is very useful in troubleshooting lots of connectivity and configuration issues. When pinging WS-Man without ‘auth’, it allows for the testing of the connectivity and basic service configuration. Using the ‘auth’ parameter tests the necessary authorization. Generally the credentials need to be in the ‘Administrators’ group for ‘auth’ to work. In this case no credentials are provided so the current credentials are used (this can be over-ridden). Notice that the OS version is included when successfully using ‘auth’ to test WS-Man.


Grab a remote machine’s WS-Man config

Successfully completing this step pretty much insures that one has complete access to WS-Man on the remote computer.

Grab a remote machine’s CPU load, free memory, and restart a service

WS-Man allows for gathering WMI properties (reading and writing although we’re only reading in this example) as well as invoking methods (starting and stopping a service; as well as rebooting!). Notice the minimal return code for the invoke commands (0 = success).


Run a command on a remote machine (this uses winrS, not winrM)

WinRS is another utility that leverages WS-Man. WinRS allows for the execution of local, non-interactive command-line commands on a remote machine and returns the output. In other words, if the command can be run at the CMD prompt without any required input and it only accesses local resource (no network shares for example), then it will most likely work. There are ways to get around the ‘local resource’ issue, but that is out of scope for this blog.


Use PowerShell to grab instrumentation via WS-Man

PowerShell is great for consuming data from WS-Man since it works very well with XML. Notice that in the following example it’s easy to ‘surf’ an object in PowerShell. In this case the XML object ‘osInfo’ is displayed on its own, and then expanded to the ‘Win32_OperatingSystem’ branch.


A simple PowerShell script

The following script shows how easy it is to automate the collection of WS-Man information using PowerShell.

$machines = ("machine1","machine2","machine3")
foreach ($machine in $machines) {
  [xml]$osInfo = winrm get wmicimv2/Win32_OperatingSystem /format:pretty /r:$machine
  $machine + ": " + $osInfo.Win32_OperatingSystem.LastBootupTime.DateTime

More Info

For more information on WS-Man, please see the following articles: