cheat sheet

PowerShell Remoting

Execute PowerShell against one or many remote hosts using WinRM or SSH transport, persistent sessions, credential management, and JEA.

PowerShell Remoting — Running Commands on Other Machines

What it is

PowerShell Remoting is the built-in transport layer in PowerShell that lets you execute commands against one or many remote machines as if you were sitting at their console, returning structured .NET objects rather than text. Microsoft ships it with every modern Windows host using WinRM (Windows Remote Management) as the default transport, and PowerShell 7+ adds SSH as an alternative — useful for Linux targets or any environment where WinRM is blocked. Reach for it whenever you need to run the same script across a fleet, gather inventory from many servers in parallel, or open a long-running interactive session without screen-sharing. Both transports remain fully supported in PowerShell 7.6 LTS (March 2026) — there are no current deprecation plans for WinRM, though the 2026 community recommendation is SSH-over-PowerShell-7 for new automation in cross-platform environments.

Install

PowerShell Remoting is a built-in feature on Windows. WinRM-based remoting just needs to be enabled; SSH-based remoting (PowerShell 7+) requires OpenSSH and a small sshd_config edit on the target.

powershell
# Enable WinRM listener on the target (run elevated)
Enable-PSRemoting -Force

# (Optional) install PowerShell 7 if not present — needed for SSH transport
winget install --id Microsoft.PowerShell -e

# (SSH transport, on Linux target) install pwsh and update sshd
sudo apt install -y openssh-server powershell

Output: (none — exits 0 on success)

Syntax

The two most common cmdlets are Invoke-Command (one-shot, fire-and-forget against one or many hosts) and Enter-PSSession (interactive REPL against a single host).

powershell
Invoke-Command -ComputerName <Host[]> -ScriptBlock { <code> } [-Credential <PSCredential>]
Enter-PSSession  -ComputerName <Host>   [-Credential <PSCredential>]
New-PSSession    -ComputerName <Host[]> [-Credential <PSCredential>]

Output: (none — exits 0 on success)

Essential parameters

ParameterMeaning
-ComputerNameOne or more WinRM target hostnames/IPs
-HostNameOne or more SSH target hostnames (PowerShell 7+)
-UserNameUsername for SSH transport (no -Credential on SSH)
-CredentialPSCredential object for WinRM auth
-AuthenticationDefault, Negotiate, Kerberos, CredSSP, Basic
-UseSSLEncrypt WinRM over port 5986 (HTTPS)
-PortOverride default port (5985 WinRM, 5986 WinRM-HTTPS, 22 SSH)
-ScriptBlockCode to execute remotely
-FilePathLocal .ps1 to copy and run on the remote
-ArgumentListPositional arguments passed to the script block / file
-SessionReuse an existing PSSession instead of opening a new one
-AsJobRun asynchronously; returns a job handle
-ThrottleLimitMax concurrent connections (default 32)
-ConfigurationNameEndpoint name on the remote (default Microsoft.PowerShell)

Enable WinRM on a target

WinRM is the original PowerShell remoting transport, listening on TCP 5985 (HTTP) and 5986 (HTTPS). Enable-PSRemoting starts the service, opens the firewall, registers the default Microsoft.PowerShell session configuration, and trusts the loopback. Server SKUs have remoting on by default; desktop SKUs do not.

powershell
# Run from an elevated PowerShell on the target machine
Enable-PSRemoting -Force

# Verify the listener
winrm enumerate winrm/config/Listener

# On the calling machine, trust the target (workgroup or cross-domain)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "myhost,fileserver01" -Force

# View what we trust right now
Get-Item WSMan:\localhost\Client\TrustedHosts

# Reset to empty (clears trust list)
Clear-Item WSMan:\localhost\Client\TrustedHosts -Force

Output (winrm enumerate winrm/config/Listener):

text
Listener
    Address = *
    Transport = HTTP
    Port = 5985
    Hostname
    Enabled = true
    URLPrefix = wsman
    CertificateThumbprint
    ListeningOn = 127.0.0.1, 192.168.1.42, ::1

Output (Get-Item WSMan:\localhost\Client\TrustedHosts):

text
   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client

Type            Name                           SourceOfValue   Value
----            ----                           -------------   -----
System.String   TrustedHosts                                   myhost,fileserver01

Invoke-Command — one-shot remoting

Invoke-Command opens a connection, runs the script block, returns its output, and closes the connection — the workhorse cmdlet for ad-hoc fleet operations. The block is executed on the remote machine, so any cmdlets and modules referenced must exist there, not locally.

powershell
# Single host
Invoke-Command -ComputerName myhost -ScriptBlock { Get-Service spooler }

# Fleet (parallel by default, up to -ThrottleLimit)
Invoke-Command -ComputerName myhost,fileserver01,web01 -ScriptBlock {
  [pscustomobject]@{
    Host    = $env:COMPUTERNAME
    Uptime  = (Get-Date) - (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
    PSVer   = $PSVersionTable.PSVersion.ToString()
  }
}

# Throttle (max 5 concurrent)
$hosts = 1..50 | ForEach-Object { "web{0:00}" -f $_ }
Invoke-Command -ComputerName $hosts -ScriptBlock { hostname } -ThrottleLimit 5

# Run a local script on the remote (no need to copy it manually)
Invoke-Command -ComputerName myhost -FilePath C:\scripts\inventory.ps1

Output (Invoke-Command -ComputerName myhost -ScriptBlock { Get-Service spooler }):

text
Status   Name               DisplayName                            PSComputerName
------   ----               -----------                            --------------
Running  spooler            Print Spooler                          myhost

Output (fleet uptime):

text
Host          Uptime              PSVer    PSComputerName
----          ------              -----    --------------
MYHOST        2.04:32:18.4731234  5.1.22621.4391 myhost
FILESERVER01  41.18:09:22.0011235 7.4.6    fileserver01
WEB01         0.03:51:07.6512344  7.4.6    web01

Passing local data — $using:

Remote script blocks run in a fresh scope on the target and cannot see your local variables. The $using: prefix snapshots a local variable at invocation time and serializes it to the remote — the idiomatic way to pass data into the block without resorting to -ArgumentList.

powershell
$serviceName = "spooler"
$threshold   = 100MB

Invoke-Command -ComputerName myhost -ScriptBlock {
  $svc = Get-Service $using:serviceName
  $mem = (Get-Process | Measure-Object WorkingSet -Sum).Sum

  [pscustomobject]@{
    Service = $svc.Name
    Status  = $svc.Status
    OverThreshold = $mem -gt $using:threshold
  }
}

# Equivalent with -ArgumentList (positional)
Invoke-Command -ComputerName myhost `
  -ScriptBlock { param($name,$bytes) Get-Service $name; $bytes } `
  -ArgumentList $serviceName, $threshold

Output:

text
Service Status  OverThreshold PSComputerName
------- ------  ------------- --------------
spooler Running         True  myhost

Persistent sessions — New-PSSession

A PSSession is a long-lived connection you can reuse across many Invoke-Command or Enter-PSSession calls, avoiding the connect-disconnect overhead and preserving in-session state (variables, loaded modules, current location). Always Remove-PSSession when finished to release server-side resources.

powershell
# Open a session
$s = New-PSSession -ComputerName myhost -Credential (Get-Credential)

# Reuse it
Invoke-Command -Session $s -ScriptBlock { $procs = Get-Process; $procs.Count }
Invoke-Command -Session $s -ScriptBlock { $procs | Sort-Object CPU -Descending | Select-Object -First 5 }

# Open a session against multiple hosts
$fleet = New-PSSession -ComputerName web01,web02,web03

# Run the same command across all of them
Invoke-Command -Session $fleet -ScriptBlock { (Get-CimInstance Win32_OperatingSystem).LastBootUpTime }

# Inspect open sessions
Get-PSSession

# Clean up
Remove-PSSession $s
Get-PSSession | Remove-PSSession

Output (Get-PSSession):

text
 Id Name            ComputerName    ComputerType    State         ConfigurationName     Availability
 -- ----            ------------    ------------    -----         -----------------     ------------
  1 Runspace1       myhost          RemoteMachine   Opened        Microsoft.PowerShell     Available
  2 Runspace2       web01           RemoteMachine   Opened        Microsoft.PowerShell     Available
  3 Runspace3       web02           RemoteMachine   Opened        Microsoft.PowerShell     Available
  4 Runspace4       web03           RemoteMachine   Opened        Microsoft.PowerShell     Available

Interactive remoting — Enter-PSSession

Enter-PSSession drops you into an interactive shell on the remote host, mirroring the ssh experience but with PowerShell semantics — your prompt is rewritten to show the target, and every command runs there until you Exit-PSSession. Useful for exploration; for automation always prefer Invoke-Command so the script is reproducible.

powershell
# Open an interactive shell
Enter-PSSession -ComputerName myhost -Credential (Get-Credential)

# Prompt changes to:  [myhost]: PS C:\Users\alicedev\Documents>
hostname
Get-Service spooler
Exit-PSSession

# Enter an existing session
$s = New-PSSession -ComputerName myhost
Enter-PSSession -Session $s

Output (interactive session prompt):

text
[myhost]: PS C:\Users\alicedev\Documents> hostname
myhost
[myhost]: PS C:\Users\alicedev\Documents> Get-Service spooler

Status   Name               DisplayName
------   ----               -----------
Running  spooler            Print Spooler

[myhost]: PS C:\Users\alicedev\Documents> Exit-PSSession
PS C:\Users\alicedev\Documents>

SSH transport — PowerShell 7+

PowerShell 7 introduces SSH as an alternative remoting transport — useful for cross-platform Windows-to-Linux scenarios, or when WinRM is unavailable. Targets need OpenSSH plus a Subsystem powershell entry in sshd_config. Authentication uses standard SSH key pairs (no Get-Credential prompt) and you use -HostName / -UserName instead of -ComputerName.

powershell
# On the SSH target (Linux), edit /etc/ssh/sshd_config:
#   Subsystem powershell /usr/bin/pwsh -sshs -NoLogo
# Then restart sshd:
#   sudo systemctl restart sshd

# Connect from Windows / macOS / Linux client
Enter-PSSession -HostName myhost.example.com -UserName alicedev

# One-shot via SSH
Invoke-Command -HostName myhost.example.com -UserName alicedev -ScriptBlock {
  Get-Process | Sort-Object CPU -Descending | Select-Object -First 5
}

# Persistent SSH session
$s = New-PSSession -HostName myhost.example.com -UserName alicedev -KeyFilePath ~/.ssh/id_ed25519
Invoke-Command -Session $s -ScriptBlock { (Get-Date), $PSVersionTable.OS }
Remove-PSSession $s

Output:

text
Friday, May 22, 2026 9:14:02 AM
Linux 6.5.0-26-generic #26-Ubuntu SMP PREEMPT_DYNAMIC

Credentials

The cleanest way to authenticate non-interactively is to capture a PSCredential once and reuse it. Get-Credential prompts for a password, returning an object whose Password field is a SecureString — encrypted in memory with DPAPI on Windows. Persist credentials with Export-Clixml; only the user that exported them on the same machine can decrypt the file.

powershell
# Prompt and stash for the duration of the session
$cred = Get-Credential alicedev

# Or build one programmatically (avoid plaintext in real scripts)
$secure = ConvertTo-SecureString "S3cret!" -AsPlainText -Force
$cred   = [pscredential]::new("alicedev", $secure)

# Persist to disk (DPAPI — only this user on this machine can decrypt)
$cred | Export-Clixml -Path "$env:USERPROFILE\.creds\myhost.xml"

# Reload later
$cred = Import-Clixml -Path "$env:USERPROFILE\.creds\myhost.xml"

# Use the SecretManagement module for a vault-backed approach
Install-Module Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Scope CurrentUser
Register-SecretVault -Name MyVault -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
Set-Secret -Name myhost-admin -Secret $cred
$cred = Get-Secret -Name myhost-admin

Output (Get-Credential):

text
PowerShell credential request
Enter your credentials.
User: alicedev
Password for user alicedev: ********

UserName                     Password
--------                     --------
alicedev System.Security.SecureString

Double-hop and CredSSP

The "double-hop" problem: by default a remote session cannot delegate your credentials to a second remote machine, so things like Invoke-Command -ComputerName A -ScriptBlock { Get-ChildItem \\B\share } fail with access-denied even when you would have access locally. CredSSP authentication explicitly delegates credentials and resolves the issue, at the cost of allowing the first hop to act fully on your behalf — enable it only on machines you trust.

powershell
# On the client (your machine)
Enable-WSManCredSSP -Role Client -DelegateComputer "myhost" -Force

# On the first hop (target machine)
Enable-WSManCredSSP -Role Server -Force

# Now the double hop works
Invoke-Command -ComputerName myhost -Authentication CredSSP -Credential $cred -ScriptBlock {
  Get-ChildItem \\fileserver01\share
}

# Audit who's delegated
Get-WSManCredSSP

# Disable when no longer needed
Disable-WSManCredSSP -Role Client
Disable-WSManCredSSP -Role Server

Output (Get-WSManCredSSP):

text
The machine is configured to allow delegating fresh credentials to the following target(s): wsman/myhost
This computer is not configured to receive credentials from a remote client computer.

Background jobs and async fleets

Invoke-Command -AsJob returns immediately with a job handle while the remote work runs in the background. Combined with Wait-Job, Receive-Job, and Get-Job, this scales to hundreds of targets without blocking the prompt and lets you collect output as each host reports back. PowerShell 7's ForEach-Object -Parallel is a lighter alternative when you don't need the full job lifecycle.

powershell
$hosts = "web01","web02","web03","web04","web05"

# Kick off async
$job = Invoke-Command -ComputerName $hosts -ScriptBlock {
  Start-Sleep -Seconds (Get-Random -Min 1 -Max 5)
  [pscustomobject]@{
    Host  = $env:COMPUTERNAME
    Procs = (Get-Process).Count
    Mem   = [Math]::Round(((Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory)/1MB, 2)
  }
} -AsJob

# Poll until done
Wait-Job $job | Out-Null

# Collect results, preserving PSComputerName origin
$results = Receive-Job $job
$results | Sort-Object Procs -Descending | Format-Table -AutoSize

# PowerShell 7+ parallel without the Job machinery
$hosts | ForEach-Object -Parallel {
  Invoke-Command -ComputerName $_ -ScriptBlock { hostname }
} -ThrottleLimit 10

Output:

text
Host    Procs   Mem    PSComputerName
----    -----   ---    --------------
WEB03     412   3.21   web03
WEB01     387   2.98   web01
WEB05     376   4.07   web05
WEB02     341   2.55   web02
WEB04     299   3.74   web04

Session configuration and JEA

Every WinRM connection lands in a "session configuration" (an endpoint) that defines which cmdlets, modules, and providers are available. The default endpoint (Microsoft.PowerShell) exposes everything. Just Enough Administration (JEA) lets you publish a constrained endpoint that runs as a privileged virtual account but only permits an allowlisted set of commands — the canonical way to grant help-desk staff "restart this service" rights without giving them admin.

powershell
# List endpoints exposed by the local machine
Get-PSSessionConfiguration

# Define a role capability (which cmdlets a role may run)
New-Item -ItemType Directory C:\JEA\Roles -Force | Out-Null
$role = @{
  Path = "C:\JEA\Roles\HelpDesk.psrc"
  VisibleCmdlets   = "Restart-Service","Get-Service"
  VisibleFunctions = "Get-Uptime"
}
New-PSRoleCapabilityFile @role

# Define a session config that maps users to that role
$session = @{
  Path = "C:\JEA\HelpDesk.pssc"
  SessionType = "RestrictedRemoteServer"
  RunAsVirtualAccount = $true
  RoleDefinitions = @{ "MYHOST\HelpDesk" = @{ RoleCapabilities = "HelpDesk" } }
}
New-PSSessionConfigurationFile @session

# Register the endpoint
Register-PSSessionConfiguration -Name JEA_HelpDesk -Path C:\JEA\HelpDesk.pssc -Force

# Connect to the constrained endpoint
Enter-PSSession -ComputerName myhost -ConfigurationName JEA_HelpDesk -Credential $cred

Output (Get-PSSessionConfiguration):

text
Name          : microsoft.powershell
PSVersion     : 5.1
StartupScript :
RunAsUser     :
Permission    : NT AUTHORITY\INTERACTIVE AccessAllowed, BUILTIN\Administrators AccessAllowed, BUILTIN\Remote Management Users AccessAllowed

Name          : JEA_HelpDesk
PSVersion     : 5.1
StartupScript :
RunAsUser     :
RunAsVirtualAccount : True
Permission    : MYHOST\HelpDesk AccessAllowed

Copying files across a session

Copy-Item accepts -ToSession and -FromSession parameters to transfer files using an open PSSession — no need to set up an SMB share or SCP. Files are streamed over the same WinRM channel and decrypted/encrypted with the session's transport security.

powershell
$s = New-PSSession -ComputerName myhost

# Push a file from local to remote
Copy-Item C:\src\config.json -Destination C:\app\config.json -ToSession $s

# Pull a file from remote to local
Copy-Item C:\Logs\app.log   -Destination C:\local\backup\ -FromSession $s

# Recursive directory push
Copy-Item C:\src\scripts -Destination C:\Tools\scripts -Recurse -ToSession $s

# Clean up
Remove-PSSession $s

Output: (none — exits 0 on success)

Disconnect and reconnect long jobs

A PSSession can be disconnected from the client while keeping its state alive on the server (idle timeout default: 7 days). Useful for kicking off a long-running task, closing your laptop, and reconnecting from another machine to grab the results. Not supported on SSH transport.

powershell
# Start a long task in a session
$s = New-PSSession -ComputerName myhost -Name LongImport
Invoke-Command -Session $s -ScriptBlock {
  Start-Sleep 600
  "imported"
} -AsJob

# Disconnect — task keeps running
Disconnect-PSSession $s

# Later, from any machine (same user identity)
$s = Get-PSSession -ComputerName myhost -Name LongImport | Connect-PSSession
Receive-PSSession $s

# Inspect disconnected sessions on the remote
Get-PSSession -ComputerName myhost -State Disconnected

Output (Get-PSSession -ComputerName myhost -State Disconnected):

text
 Id Name        ComputerName  ComputerType    State         ConfigurationName     Availability
 -- ----        ------------  ------------    -----         -----------------     ------------
  3 LongImport  myhost        RemoteMachine   Disconnected  Microsoft.PowerShell      None

Common pitfalls

  1. Plaintext passwords in scripts — never ConvertTo-SecureString "..." -AsPlainText in committed code. Use Get-Credential, Import-Clixml, or the SecretManagement module.
  2. Variable not seen on remote — wrap with $using:varName inside the script block, or pass via -ArgumentList with a matching param().
  3. Trusted hosts wildcard everythingSet-Item WSMan:\localhost\Client\TrustedHosts -Value '*' works but disables host validation. Scope it to specific names.
  4. HTTP transport on the public Internet — WinRM HTTP encrypts auth but not always the body for non-Kerberos auth. Always use -UseSSL over untrusted networks.
  5. Double-hop access denied — first hop cannot reach a second remote resource. Solve with CredSSP, Kerberos delegation, or a New-PSSession from inside the script block.
  6. Enter-PSSession in a script — interactive only; scripts must use Invoke-Command.
  7. Forgetting to clean up PSSession objects — they consume slots on the target. Always Remove-PSSession or Get-PSSession | Remove-PSSession at the end of a script.
  8. SSH transport on PS 5.1 — only PowerShell 7+ supports -HostName/-UserName. Older targets need WinRM.
  9. Module not loaded remotely — the remote endpoint may not have the modules you have locally. Use Import-Module inside the script block, or use Invoke-Command -FilePath with a self-bootstrapping script.
  10. Profile not loaded$PROFILE is not sourced in remote sessions by default. Initialize aliases or Set-Location calls explicitly inside the script block.

Real-world recipes

Run a health check across 10 servers in parallel

Gather uptime, free memory, and the number of running services from every server in a list, then sort by uptime to find the longest-running box.

powershell
$hosts = "web01","web02","web03","web04","web05",
         "db01","db02","cache01","queue01","monitor01"

$cred = Get-Credential alicedev

$report = Invoke-Command -ComputerName $hosts -Credential $cred -ScriptBlock {
  $os = Get-CimInstance Win32_OperatingSystem
  [pscustomobject]@{
    Host         = $env:COMPUTERNAME
    UptimeDays   = [Math]::Round(((Get-Date) - $os.LastBootUpTime).TotalDays, 1)
    FreeMemGB    = [Math]::Round($os.FreePhysicalMemory / 1MB, 2)
    RunningSvcs  = (Get-Service | Where-Object Status -eq Running).Count
  }
} -ThrottleLimit 10 -ErrorAction Continue

$report |
  Sort-Object UptimeDays -Descending |
  Format-Table -AutoSize

Output:

text
Host       UptimeDays FreeMemGB RunningSvcs PSComputerName
----       ---------- --------- ----------- --------------
MONITOR01       82.4       6.71         147 monitor01
DB02            54.1       4.20         132 db02
DB01            54.1       4.18         133 db01
WEB03           18.2       3.91         128 web03
WEB02           18.2       3.88         127 web02
WEB01           18.2       3.85         128 web01
QUEUE01         12.6       5.10         121 queue01
CACHE01         12.6       2.95         119 cache01
WEB05            3.0       4.07         128 web05
WEB04            3.0       3.74         128 web04

Patch and restart a service across a fleet

Push a new config file, restart the service, and verify it came back up — failing fast for any host that doesn't recover.

powershell
$hosts = "web01","web02","web03"
$cred  = Import-Clixml "$env:USERPROFILE\.creds\webfleet.xml"

$sessions = New-PSSession -ComputerName $hosts -Credential $cred

# 1. Push config
$sessions | ForEach-Object {
  Copy-Item .\nginx.conf -Destination 'C:\nginx\conf\nginx.conf' -ToSession $_
}

# 2. Restart service and check status
$results = Invoke-Command -Session $sessions -ScriptBlock {
  Restart-Service nginx -Force
  Start-Sleep 2
  [pscustomobject]@{
    Host   = $env:COMPUTERNAME
    Status = (Get-Service nginx).Status
  }
}

# 3. Tear down
Remove-PSSession $sessions

# 4. Report
$results | Format-Table -AutoSize
if ($results.Status -contains 'Stopped') { throw "One or more hosts failed to restart" }

Output:

text
Host    Status   PSComputerName
----    ------   --------------
WEB01   Running  web01
WEB02   Running  web02
WEB03   Running  web03

Collect Windows event-log errors from every domain controller

Query each DC for System log errors in the last hour and produce a single sorted table — useful as a quick triage when something is going wrong.

powershell
$dcs = (Get-ADDomainController -Filter *).HostName

Invoke-Command -ComputerName $dcs -ScriptBlock {
  $cutoff = (Get-Date).AddHours(-1)
  Get-WinEvent -FilterHashtable @{ LogName='System'; Level=1,2; StartTime=$cutoff } |
    Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message
} -ThrottleLimit 20 -ErrorAction SilentlyContinue |
  Sort-Object TimeCreated -Descending |
  Select-Object PSComputerName, TimeCreated, Id, LevelDisplayName, ProviderName, Message |
  Out-GridView -Title "DC errors in the last hour"

Output (sample — opens an interactive grid):

text
PSComputerName TimeCreated         Id LevelDisplayName ProviderName Message
-------------- -----------         -- ---------------- ------------ -------
DC02           5/24/2026 9:12:03  1014 Warning          DNS Client …
DC01           5/24/2026 9:05:47  4625 Error            Microsoft-Win… An account failed to log on.

Bootstrap a fresh server via SSH-PSRemoting

Use PowerShell 7's SSH transport to apply a baseline configuration to a brand-new Linux host without ever opening a separate ssh session.

powershell
$h = "myhost.example.com"

$session = New-PSSession -HostName $h -UserName alicedev -KeyFilePath ~/.ssh/id_ed25519

Invoke-Command -Session $session -ScriptBlock {
  if (-not (Test-Path '/opt/app')) { sudo mkdir -p /opt/app }
  Set-Location /opt/app

  # Drop a config file
  @"
listen 0.0.0.0:8080
log-level info
"@ | Out-File config.cfg -Encoding utf8
}

# Push the binary
Copy-Item .\app -Destination /opt/app/app -ToSession $session

# Mark executable and start it under systemd
Invoke-Command -Session $session -ScriptBlock {
  sudo chmod +x /opt/app/app
  sudo systemctl enable --now app.service
  systemctl status app.service --no-pager
}

Remove-PSSession $session

Output:

text
* app.service - app worker
     Loaded: loaded (/etc/systemd/system/app.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-05-26 09:18:11 UTC; 2s ago
   Main PID: 14823 (app)
      Tasks: 4 (limit: 4567)
     Memory: 5.6M
        CPU: 12ms

Sources