Powershell Remoting
Remarks#
Enabling PowerShell Remoting
PowerShell remoting must first be enabled on the server to which you wish to remotely connect.
Enable-PSRemoting -Force
This command does the following:
- Runs the Set-WSManQuickConfig cmdlet, which performs the following tasks:
- Starts the WinRM service.
- Sets the startup type on the WinRM service to Automatic.
- Creates a listener to accept requests on any IP address, if one does not already exist.
- Enables a firewall exception for WS-Management communications.
- Registers the Microsoft.PowerShell and Microsoft.PowerShell.Workflow session configurations, if it they are not already registered.
- Registers the Microsoft.PowerShell32 session configuration on 64-bit computers, if it is not already registered.
- Enables all session configurations.
- Changes the security descriptor of all session configurations to allow remote access.
- Restarts the WinRM service to make the preceding changes effective.
Only for non-domain environments
For servers in an AD Domain the PS remoting authentication is done through Kerberos (‘Default’), or NTLM (‘Negotiate’). If you want to allow remoting to a non-domain server you have two options.
Either set up WSMan communication over HTTPS (which requires certificate generation) or enable basic authentication which sends your credentials across the wire base64-encoded (that’s basically the same as plain-text so be careful with this).
In either case you’ll have to add the remote systems to your WSMan trusted hosts list.
Enabling Basic Authentication
Set-Item WSMan:\localhost\Service\AllowUnencrypted $true
Then on the computer you wish to connect from, you must tell it to trust the computer you’re connecting to.
Set-Item WSMan:\localhost\Client\TrustedHosts '192.168.1.1,192.168.1.2'
Set-Item WSMan:\localhost\Client\TrustedHosts *.contoso.com
Set-Item WSMan:\localhost\Client\TrustedHosts *
Important: You must tell your client to trust the computer addressed in the way you want to connect (e.g. if you connect via IP, it must trust the IP not the hostname)
Connecting to a Remote Server via PowerShell
Using credentials from your local computer:
Enter-PSSession 192.168.1.1
Prompting for credentials on the remote computer
Enter-PSSession 192.168.1.1 -Credential $(Get-Credential)
Run commands on a Remote Computer
Once Powershell remoting is enabled (Enable-PSRemoting) You can run commands on the remote computer like this:
Invoke-Command -ComputerName "RemoteComputerName" -ScriptBlock {
Write host "Remote Computer Name: $ENV:ComputerName"
}
The above method creates a temporary session and closes it right after the command or scriptblock ends.
To leave the session open and run other command in it later, you need to create a remote session first:
$Session = New-PSSession -ComputerName "RemoteComputerName"
Then you can use this session each time you invoke commands on the remote computer:
Invoke-Command -Session $Session -ScriptBlock {
Write host "Remote Computer Name: $ENV:ComputerName"
}
Invoke-Command -Session $Session -ScriptBlock {
Get-Date
}
If you need to use different Credentials, you can add them with the -Credential
Parameter:
$Cred = Get-Credential
Invoke-Command -Session $Session -Credential $Cred -ScriptBlock {...}
Remoting serialization warning
Note:
It is important to know that remoting serializes PowerShell objects on the remote system and deserializes them on your end of the remoting session, i.e. they are converted to XML during transport and lose all of their methods.
$output = Invoke-Command -Session $Session -ScriptBlock {
Get-WmiObject -Class win32_printer
}
$output | Get-Member -MemberType Method
TypeName: Deserialized.System.Management.ManagementObject#root\cimv2\Win32_Printer
Name MemberType Definition
---- ---------- ----------
GetType Method type GetType()
ToString Method string ToString(), string ToString(string format, System.IFormatProvi...
Whereas you have the methods on the regular PS object:
Get-WmiObject -Class win32_printer | Get-Member -MemberType Method
TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer
Name MemberType Definition
---- ---------- ----------
CancelAllJobs Method System.Management.ManagementBaseObject CancelAllJobs()
GetSecurityDescriptor Method System.Management.ManagementBaseObject GetSecurityDescriptor()
Pause Method System.Management.ManagementBaseObject Pause()
PrintTestPage Method System.Management.ManagementBaseObject PrintTestPage()
RenamePrinter Method System.Management.ManagementBaseObject RenamePrinter(System.String NewPrinterName)
Reset Method System.Management.ManagementBaseObject Reset()
Resume Method System.Management.ManagementBaseObject Resume()
SetDefaultPrinter Method System.Management.ManagementBaseObject SetDefaultPrinter()
SetPowerState Method System.Management.ManagementBaseObject SetPowerState(System.UInt16 PowerState, System.String Time)
SetSecurityDescriptor Method System.Management.ManagementBaseObject SetSecurityDescriptor(System.Management.ManagementObject#Win32_SecurityDescriptor Descriptor)
Argument Usage
To use arguments as parameters for the remote scripting block, one might either use the ArgumentList
parameter of Invoke-Command
, or use the $Using:
syntax.
Using ArgumentList
with unnamed parameters (i.e. in the order they are passed to the scriptblock):
$servicesToShow = "service1"
$fileName = "C:\temp\servicestatus.csv"
Invoke-Command -Session $session -ArgumentList $servicesToShow,$fileName -ScriptBlock {
Write-Host "Calling script block remotely with $($Args.Count)"
Get-Service -Name $args[0]
Remove-Item -Path $args[1] -ErrorAction SilentlyContinue -Force
}
Using ArgumentList
with named parameters:
$servicesToShow = "service1"
$fileName = "C:\temp\servicestatus.csv"
Invoke-Command -Session $session -ArgumentList $servicesToShow,$fileName -ScriptBlock {
Param($serviceToShowInRemoteSession,$fileToDelete)
Write-Host "Calling script block remotely with $($Args.Count)"
Get-Service -Name $serviceToShowInRemoteSession
Remove-Item -Path $fileToDelete -ErrorAction SilentlyContinue -Force
}
Using $Using:
syntax:
$servicesToShow = "service1"
$fileName = "C:\temp\servicestatus.csv"
Invoke-Command -Session $session -ScriptBlock {
Get-Service $Using:servicesToShow
Remove-Item -Path $fileName -ErrorAction SilentlyContinue -Force
}
A best practise for automatically cleaning-up PSSessions
When a remote session is created via the New-PSsession
cmdlet, the PSSession persists until the current PowerShell session ends.
Meaning that, by default, the PSSession
and all associated resources will continue to be used until the current PowerShell session ends.
Multiple active PSSessions
can become a strain on resources, particularly for long running or interlinked scripts that create hundreds of PSSessions
in a single PowerShell session.
It is best practise to explicitly remove each PSSession
after it is finished being used. [1]
The following code template utilises try-catch-finally
in order to achieve the above, combining error handling with a secure way to ensure all created PSSessions
are removed when they are finished being used:
try
{
$session = New-PSsession -Computername "RemoteMachineName"
Invoke-Command -Session $session -ScriptBlock {write-host "This is running on $ENV:ComputerName"}
}
catch
{
Write-Output "ERROR: $_"
}
finally
{
if ($session)
{
Remove-PSSession $session
}
}
References: [1] https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/new-pssession