Executing Remote Powershell Commands

When you begin administering multiple computers, you’ll eventually want to automate the tasks you repeat more than once. Instead of logging into each computer, starting powershell and running the same command, you can use one powershell console locally and send commands to multiple remote computers from that single local console.

In addition to running locally, Powershell can run remotely using WinRM (Windows Remote Management) which uses a management protocol called WSMan. When your local computer talks to another via Powershell remoting (WinRM over WSMan), a remote session is created, the command is executed, then the remote session is normally terminated. Remote sessions can be opened at any time, and a remote session can be persisted if necessary – more about that later.

If you want to execute a command on another machine, you have a couple of options:

  • Start a new Powershell session to the remote machine, execute your code, then close the connection
  • Use Invoke-Command, which runs script in a temporary session
  • Use one of the few Powershell commands that does not run over WinRM and specify the -ComputerName argument
    According to “Get-Help about_Remote”, these commands do not need WinRM to execute:

    • Get-Counter
    • Clear-EventLog
    • Limit-EventLog
    • New-EventLog
    • Get-EventLog
    • Remove-EventLog
    • Get-HotFix
    • Restart-Computer
    • Get-Process
    • Show-EventLog
    • Get-Service
    • Stop-Computer
    • Get-WinEvent
    • Test-Connection
    • Get-WmiObject
    • Write-EventLog

Powershell Sessions

Powershell uses the concept of sessions within which code is executed and acts as the outer scope for variables.

To execute code on a remote machine, you must first start a remote session then enter that remote session. Sessions can be entered and created using Enter-PSSession, and exited using Exit-PSSession. See New-PSSession, Remote-PSSession and Get-PSSession and Get-Command *pssession* for further commands.

To enter a new session, run Enter-PSSession and specify the name of the remote machine. This immediately puts the current powershell console into the execution context of the remote machine:

PS K:\powershell> Enter-PSSession computername
[computername]: PS C:\Users\AlexanderW\Documents>

Note how the [computername] shows we are running in the context of the remote machine.

To leave a session, use Exit-PSSession – “exit” would also work here.

[ltn25039]: PS C:\Users\AlexanderW\Documents> Exit-PSSession
 PS K:\powershell>

Note how we are now back to our local session – there is no [computername] at the last prompt.

PS Sessions can be created and entered as necessary, and can also be stored in variables, letting you switch between sessions too.

PS> $computerName = computername
PS> $session1 = New-PSSession $computerName
PS> $session2 = New-PSSession $computerName
PS> Get-PSSession | Format-List

ComputerName           : computername
ConfigurationName      : Microsoft.PowerShell
InstanceId             : 876b8836-2325-43e5-8ba9-9c1ca25764af
Id                     : 7
Name                   : Session7
Availability           : Available
ApplicationPrivateData : {PSVersionTable}
Runspace               : System.Management.Automation.RemoteRunspace
State                  : Opened
IdleTimeout            : 7200000
OutputBufferingMode    : Block

ComputerName           : computername
ConfigurationName      : Microsoft.PowerShell
InstanceId             : 792d71f8-b4f3-4488-9b35-b2952aa21e90
Id                     : 6
Name                   : Session6
Availability           : Available
ApplicationPrivateData : {PSVersionTable}
Runspace               : System.Management.Automation.RemoteRunspace
State                  : Opened
IdleTimeout            : 7200000
OutputBufferingMode    : Block

PS C:\Users\AlexanderW> (Get-PSSession).length
2

PS> Get-PSSession | Remove-PSSession

PS> (Get-PSSession).length
0

Powershell Remoting with Invoke-Command

Check out the documentation for Invoke-Command by using the Get-Help command. Use the -Online switch to open the related MSDN web page.

Get-Help Invoke-Command -Online

To get a list of logged on users locally, we’d use the following command to call WMI and query the list of logged on users. We then pipe this through a Select-Object filter and fetch the Name.

Get-WmiObject Win32_LoggedOnUser | Select-Object { "$($_.Antecedent)".Split(",")[1].Substring(5) }

We can then use the -ScriptBlock argument with Invoke-Command to get a execute the powershell command to get the list of logged on users for that remote machine.

Invoke-Command -ComputerName <computername> { Get-WmiObject Win32_LoggedOnUser | Select-Object { "$($_.Antecedent)".Split(",")[1].Substring(5) } }

Executing local functions and variables on the remote machine

Functions cannot be passed as references so you must redeclare them in the scope of the command you wish to execute. You can pass variables, and I’ll show you how to do that below.

The following will not work because the -ScriptBlock is executing in a new context on the remote machine, and has no idea what the SayHello function is.

PS K:\powershell> function SayHello() { Write-Host "hello world" }
PS K:\powershell> Invoke-Command -ComputerName <computer name> -ScriptBlock { sayHello }
The term 'sayHello' is not recognized as the name of a cmdlet, function, 
script file, or operable program. Check the spelling of the name, or if a path 
was included, verify that the path is correct and try again.
 + CategoryInfo : ObjectNotFound: (sayHello:String) [], CommandNot 
 FoundException
 + FullyQualifiedErrorId : CommandNotFoundException
 + PSComputerName : ltn25039

To fix this, we re-declare the function inside the ScriptBlock and then we can call it.

PS K:\powershell> Invoke-Command -ComputerName <computername> -ScriptBlock { function SayHello() { Write-Host "hello world" }; sayHello; }
hello world

To prove that was working in the context of the remote machine, we’ll add the hostname to the output:

PS K:\powershell> Invoke-Command -ComputerName <computername> -ScriptBlock { function SayHello() { Write-Host "hello world from" (hostname) }; sayHello; }
hello world from <computername>

Variables declared locally can be passed through to the ScriptBlock via the $Using:<variable name> accessor.

PS K:\powershell> $exampleVariable = "hello world";
PS K:\powershell> Invoke-Command -ComputerName <computername> -ScriptBlock { Write-Host $Using:exampleVariable; }
hello world

Windows Services in Powershell

List Windows Services

To get a list of services, run the following command:

Get-Service

Here’s a list of my first 10 services:

PS K:\powershell> Get-Service | Select-Object -First 10
Status Name DisplayName
 ------ ---- -----------
 Running AdobeARMservice Adobe Acrobat Update Service
 Stopped AdobeFlashPlaye... Adobe Flash Player Update Service
 Stopped AeLookupSvc Application Experience
 Stopped ALG Application Layer Gateway Service
 Stopped ANTS Memory Pro... ANTS Memory Profiler 7 Service
 Stopped ANTS Performanc... ANTS Performance Profiler 7 Service
 Running AppHostSvc Application Host Helper Service
 Stopped AppIDSvc Application Identity
 Stopped Appinfo Application Information
 Stopped AppMgmt Application Management

Getting a list of services from a remote machine:

Invoke-Command -ComputerName <computername> -ScriptBlock { Get-Service }

Filtering Services

You have a few options when trying to filter the list of services. You can use Get-Service arguments or send the output to be filtered – note we’re filtering the service Name, not the service DisplayName:

PS K:\powershell> Get-Service *wsear*
Status Name DisplayName 
------ ---- ----------- 
Running WSearch Windows Search

To filter on display name with wildcards, specify the -DisplayName argument explicitly:

PS K:\powershell> Get-Service -DisplayName *Search*
Status Name DisplayName 
------ ---- ----------- 
Running WSearch Windows Search

Each service is a powershell object, so you can always pipe the content to a filter. Below we’re looking for any items that have the Name of Dhcp or WinRM. Note we’re using the Where-Object and evaluating the Name against a Regex collection by using -match command.

PS K:\powershell> Get-Service | Where-Object { $_.DisplayName -match "live|error" }
Status Name DisplayName 
------ ---- ----------- 
Stopped WerSvc Windows Error Reporting Service 
Running wlidsvc Windows Live ID Sign-in Assistant

Starting and Stopping Services

To start or stop a service use the Start-Service and Stop-Service cmdlets.

Find the Services you wish to start. Use the filtering to find a single instance. If there are multiple services and you pipe them to Start-Service or Stop-Service, they’ll all be started or stopped. If an error occurs for one service (e.g. it cannot start), the error will be reported to the console, but the rest of the services will be sent and attempted to be started.

Finding all services with *web* in the name:

PS C:\WINDOWS\system32> Get-Service *web*
Status Name DisplayName
------ ---- -----------
Running WebClient WebClient

Piping the services to Stop-Service:

PS C:\WINDOWS\system32> Get-Service *web* | Stop-Service

Chaining up more filtering and passing the services though the pipeline to Start-Service:

PS K:\powershell> Get-Service | Where-Object { $_.DisplayName -match "live|error" } | Start-Service