Remoting and PSSessions
Reviewed & published by Brayan K
By the end of this lesson you'll run commands on remote machines with Invoke-Command, drop into an interactive remote prompt with Enter-PSSession, reuse persistent connections via New-PSSession, fan a command out across many servers at once, and pass local data into remote blocks with $using: — the foundation of real infrastructure automation.
Part of the free PowerShell course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Run remote commands with Invoke-Command -ComputerName -ScriptBlock
- Open interactive remote prompts with Enter-PSSession
- Create and reuse persistent connections with New-PSSession / Remove-PSSession
- Understand WinRM versus SSH transport
- Authenticate with -Credential
- Fan out across many machines and pass local data with $using:
💡 Real-World Analogy
Remoting is like having remote control of other computers. Enter-PSSession is a video call — you're "in the room" on one machine, typing live. Invoke-Command is sending a task by courier — you hand over a sealed instruction (the script block), it's carried out on the far end, and the results are couriered back to you. New-PSSession is keeping the phone line open so you don't redial for every message, and $using: is writing your local notes into the instruction so the remote worker has the details they need.
1. Running Commands Remotely with Invoke-Command
Invoke-Command is the workhorse of remoting. You give it -ComputerName (where to run) and a -ScriptBlock (what to run); the block executes on the remote machine and the results come back to you as objects, each tagged with a PSComputerName property. Read this worked example, then run it against a host you control.
# PowerShell Remoting runs commands on OTHER machines from your session.
# Invoke-Command runs a script block remotely and brings the RESULTS back
# as objects. -ComputerName says where; -ScriptBlock says what.
Invoke-Command -ComputerName 'Server01' -ScriptBlock {
# This code runs ON Server01, not locally:
Get-Process | Sort-Object WS -Descending | Select-Object -First 2 Name, Id
}
# The output gains a PSComputerName property so you know which host it came from.2. Interactive Sessions with Enter-PSSession
When you want to "go there and look around" one machine, Enter-PSSession drops you into an interactive remote prompt. Your prompt changes to show the remote host, every command you type runs there, and Exit-PSSession brings you home. Supply credentials with -Credential (Get-Credential). This is ideal for single-machine troubleshooting; for automation across many hosts, reach for Invoke-Command instead.
# Enter-PSSession gives you an INTERACTIVE prompt ON the remote machine.
# Everything you type now runs there until you Exit-PSSession.
Enter-PSSession -ComputerName 'Server01' -Credential (Get-Credential)
# Notice the prompt changes to show the remote host:
# [Server01]: PS C:\> hostname
# Server01
# [Server01]: PS C:\> Exit-PSSession # back to your local prompt
# Enter-PSSession is great for ad-hoc, one-machine troubleshooting.
# For automation across MANY machines, use Invoke-Command instead.Now you try. Fill in the one blank using the hint in the comment, then run it.
# 🎯 YOUR TURN — replace each ___ then run it.
# Goal: get the OS version from a remote machine called 'Web01'.
___ -ComputerName 'Web01' -ScriptBlock { # 👉 the cmdlet is: Invoke-Command
# This runs on Web01:
[System.Environment]::OSVersion.Version
}
# ✅ Expected (example):
# Major Minor Build PSComputerName
# ----- ----- ----- --------------
# 10 0 19045 Web013. Persistent Connections with New-PSSession
Passing -ComputerName opens a throwaway connection per call. For repeated work, create a persistent session with New-PSSession and pass it to Invoke-Command -Session. Reuse is faster and keeps state between calls — a variable set in one call is still there in the next. Always Remove-PSSession when you finish to free resources on both ends.
# A PSSession is a PERSISTENT connection you can reuse for many commands.
# Create once, run several Invoke-Commands, then clean up.
# 1) Open reusable sessions to two servers:
$sessions = New-PSSession -ComputerName 'Web01', 'Web02' -Credential (Get-Credential)
# 2) Reuse them — state persists between calls on the same session:
Invoke-Command -Session $sessions -ScriptBlock { $svc = (Get-Service).Count }
Invoke-Command -Session $sessions -ScriptBlock { "$env:COMPUTERNAME has $svc services" }
# 3) ALWAYS clean up when finished:
Remove-PSSession -Session $sessionsPro Tip
On Windows, remoting rides on WinRM (enable it once with Enable-PSRemoting). In PowerShell 7 you can also remote over SSH with -HostName instead of -ComputerName, which works cross-platform to Linux and macOS. Pick WinRM inside a Windows domain; pick SSH when you need to cross operating systems.
Putting It Together: fan-out + $using:
The two features that make remoting scale: give -ComputerName a list and the block runs on every machine in parallel; and use the $using: scope modifier to push a local variable into the remote block (a remote session can't see your local variables otherwise).
# Two power features: running on MANY machines, and passing LOCAL data in.
$targets = 'Web01', 'Web02', 'Web03'
$logFolder = 'C:\Logs' # a LOCAL variable
# Invoke-Command fans out to all three machines IN PARALLEL.
# To use the local $logFolder inside the remote block, prefix it $using:
Invoke-Command -ComputerName $targets -ScriptBlock {
$files = Get-ChildItem $using:logFolder -ErrorAction SilentlyContinue
[pscustomobject]@{
Host = $env:COMPUTERNAME
Logs = $files.Count
}
}Each returned object carries PSComputerName, so even though all three ran at once you always know which host produced which row.
Common Errors (and the fix)
- "WinRM cannot complete the operation … check that the computer is accessible" — remoting isn't enabled or a firewall blocks it. Run Enable-PSRemoting on the target and confirm connectivity with Test-WSMan.
- A local variable is empty inside the remote block — the remote session can't see your locals. Prefix it with $using:, e.g. $using:logFolder.
- Leaking sessions / "maximum number of concurrent shells" — you forgot to close sessions. Always Remove-PSSession (or use them in a try/finally).
- "Access is denied" — wrong identity. Pass explicit credentials with -Credential (Get-Credential), and check the account has rights on the target.
- Expecting live objects back but getting "deserialized" ones — results returned over remoting are serialized snapshots, so their methods don't run locally. Do method calls inside the remote script block, then return plain data.
📋 Quick Reference — Remoting
| Cmdlet | Purpose | Example |
|---|---|---|
| Invoke-Command | Run a block remotely | Invoke-Command -ComputerName S1 -ScriptBlock {...} |
| Enter-PSSession | Interactive remote prompt | Enter-PSSession -ComputerName S1 |
| New-PSSession | Persistent connection | $s = New-PSSession -ComputerName S1 |
| Remove-PSSession | Close a session | Remove-PSSession $s |
| -Credential | Authenticate | -Credential (Get-Credential) |
| $using: | Pass a local variable in | $using:path |
Mini-Challenge: multi-server uptime report
No blanks this time — just a brief and an outline to keep you on track. Build it, run it (against lab hosts), and check your output against the example in the comments. Fanning a check out across servers is everyday infrastructure work.
# 🎯 MINI-CHALLENGE: a multi-server uptime report
# No code is filled in — just an outline. Build it and check the output.
#
# 1. Put three computer names in a $servers array.
# 2. Define a LOCAL variable $threshold = 7 (days).
# 3. Use Invoke-Command -ComputerName $servers -ScriptBlock { ... } to,
# on EACH server:
# - compute uptime from (Get-Date) minus the last boot time
# ( (Get-CimInstance Win32_OperatingSystem).LastBootUpTime )
# - compare uptime in days against $using:threshold
# - output a [pscustomobject] with Host, UptimeDays, NeedsReboot.
# 4. Sort the combined results by UptimeDays descending.
#
# ✅ Example output:
# Host UptimeDays NeedsReboot PSComputerName
# ---- ---------- ----------- --------------
# Web03 31 True Web03
# Web01 4 False Web01
# your code here🎉 Lesson Complete
- ✅ Invoke-Command -ComputerName -ScriptBlock runs code remotely and returns objects
- ✅ Enter-PSSession gives an interactive one-machine prompt
- ✅ New-PSSession / Remove-PSSession reuse and clean up persistent connections
- ✅ Remoting rides WinRM on Windows or SSH cross-platform; authenticate with -Credential
- ✅ Fan out across many machines and pass local data in with $using:
Practice quiz
What does PowerShell Remoting let you do?
- Run commands on remote computers
- Compress archives
- Format strings
- Edit local files faster
Answer: Run commands on remote computers. Remoting runs PowerShell commands and scripts on one or many remote machines from your local session.
Which cmdlet runs a script block on a remote computer?
- Get-Process
- Enter-Session
- Invoke-Command
- Start-Process
Answer: Invoke-Command. Invoke-Command -ComputerName <name> -ScriptBlock { ... } runs the block remotely and returns the results.
Which cmdlet opens an interactive one-to-one remote session?
- Invoke-Command
- Enter-PSSession
- Connect-Host
- New-PSSession
Answer: Enter-PSSession. Enter-PSSession starts an interactive session so your prompt operates on the remote machine until you Exit-PSSession.
What does New-PSSession create?
- A new local window
- A scheduled task
- A log file
- A persistent reusable connection to a remote host
Answer: A persistent reusable connection to a remote host. New-PSSession creates a persistent PSSession you can reuse across multiple Invoke-Command calls.
Which cmdlet closes and cleans up a PSSession?
- Remove-PSSession
- Stop-Session
- Close-PSSession
- Exit-Session
Answer: Remove-PSSession. Remove-PSSession disconnects and removes a session, freeing resources on both ends.
On Windows, which protocol is the traditional remoting transport?
- FTP
- WinRM
- SMTP
- Telnet
Answer: WinRM. WinRM (Windows Remote Management) is the default transport for PowerShell remoting on Windows.
Which transport enables cross-platform PowerShell remoting?
- HTTP only
- RDP
- SMB
- SSH
Answer: SSH. PowerShell 7 supports SSH-based remoting, letting you connect across Windows, Linux, and macOS.
How do you supply alternate credentials to a remote command?
- -Login
- -User
- -Credential
- -Password
Answer: -Credential. The -Credential parameter accepts a PSCredential so you can authenticate as a different user.
What does Invoke-Command do when given many computer names?
- Errors out
- Runs the command on all of them in parallel
- Runs them one at a time slowly
- Picks one at random
Answer: Runs the command on all of them in parallel. Invoke-Command fans out and runs the script block on all listed computers concurrently.
Inside a remote script block, how do you use a LOCAL variable?
- Prefix it with $using:
- Use $remote:
- It is impossible
- Just type $var
Answer: Prefix it with $using:. The $using: scope modifier passes a local variable into the remote script block, e.g. $using:path.
Continue this course
- Previous: Modules & the PowerShell Gallery
- Next: Strings, Formatting & Regex — Format with -f, here-strings, and -match/-replace/-split regex operators
Frequently asked questions
What is the difference between Invoke-Command and Enter-PSSession?
Enter-PSSession is INTERACTIVE: it drops you into a live prompt on one remote machine, so every command you type runs there until you Exit-PSSession. It's perfect for ad-hoc troubleshooting on a single host. Invoke-Command is for AUTOMATION: it runs a script block (or script file) on one or many machines, returns the results as objects, and finishes. Use Enter-PSSession to 'go there and look around'; use Invoke-Command to 'run this everywhere and bring back the answers'.
When should I use New-PSSession instead of just -ComputerName?
Passing -ComputerName to Invoke-Command opens a fresh, temporary connection for that single call and tears it down afterward. New-PSSession creates a PERSISTENT PSSession you can reuse across many Invoke-Command calls. Reuse matters for two reasons: it's faster (no repeated connection setup), and STATE persists between calls on the same session, so a variable you set in one Invoke-Command is still there in the next. Always Remove-PSSession when you're done to free resources.
What is the difference between WinRM and SSH remoting?
WinRM (Windows Remote Management) is the traditional Windows transport for PowerShell remoting; it's enabled with Enable-PSRemoting and uses the WS-Management protocol. SSH-based remoting, added in PowerShell 7, lets you remote across platforms — Windows to Linux, Linux to Windows, macOS — using the same SSH you'd use for a normal shell, specified with -HostName and -SSHTransport (or -UserName). Choose WinRM inside a Windows/Active Directory environment; choose SSH for cross-platform or where SSH is already the standard.
Why do I need the $using: scope modifier?
A remote script block runs in a separate session on the remote machine, so it can't see your LOCAL variables by default. The $using: modifier captures a local variable's value and ships it into the remote block: $using:path becomes the local $path's value on the remote side. Without it, $path inside the block would be empty (or the remote machine's own $path). Use $using: whenever a remote block needs data computed locally.
How does Invoke-Command run on many machines at once?
Give -ComputerName a comma-separated list (or an array), and Invoke-Command fans the script block out to all of them IN PARALLEL, then collects every machine's results into one stream. Each result object carries a PSComputerName property so you can tell which host produced it. There's a built-in throttle limit (default 32 concurrent connections) you can tune with -ThrottleLimit. This fan-out is what makes remoting scale from one server to hundreds.