Load this when troubleshooting a Windows box you can't sit at — the family member's PC, a server, a colleague's machine across the office. PowerShell remoting (WS-Management or SSH) lets every script in this skill run against a remote target with no installation on the target side.
Companion to net-ops's SSH bootstrap — same pattern, different transport layer.
When everything is already set up (same domain, PSRemoting enabled), this works directly:
# Single command, single result
Invoke-Command -ComputerName REMOTE-PC -ScriptBlock { Get-Disk } -Credential (Get-Credential)
# Interactive session
Enter-PSSession -ComputerName REMOTE-PC -Credential (Get-Credential)
# Persistent session for repeated commands (saves auth overhead)
$s = New-PSSession -ComputerName REMOTE-PC -Credential (Get-Credential)
Invoke-Command -Session $s -ScriptBlock { Get-Service WinDefend }
Invoke-Command -Session $s -ScriptBlock { Get-Process | Sort CPU -Descending | Select -First 5 }
Remove-PSSession $s
If this doesn't work, the setup steps below address every common reason it fails.
PowerShell remoting runs over either WS-Management (the original) or SSH (modern alternative on Win10 1809+ / Server 2019+).
| Transport | Port | When |
|---|---|---|
| WS-Man (HTTP) | 5985 | Default. Same domain, internal network. |
| WS-Man (HTTPS) | 5986 | Across network boundaries, untrusted networks. Needs certificate. |
| SSH | 22 | Cross-OS (Linux ↔ Windows). Authentication you already have. Use this when target also runs OpenSSH Server. |
For everything in this doc the default WS-Man (5985, HTTP) path is assumed. SSH transport is covered in SSH transport section near the end.
What credentials work depend on the target's domain situation:
| Target | Caller | Auth that works |
|---|---|---|
| Domain member | Domain user | Kerberos (default, automatic) |
| Domain member | Local admin of target | Negotiate→NTLM (after TrustedHosts) |
| Workgroup | Local admin of target | Negotiate→NTLM (after TrustedHosts) |
| Workgroup | Same local username + password on both | Negotiate→NTLM, sometimes auto |
| Internet-reachable | Anyone | Basic over HTTPS (NEVER over HTTP) |
| Anything | Anyone with SSH key | SSH transport |
If a fresh Enter-PSSession fails with "Access is denied" or "Kerberos authentication error", the auth model probably doesn't match.
This runs on the target (the machine to be controlled), as Administrator:
Enable-PSRemoting -Force
# That does all of:
# - Starts WinRM service, sets to auto-start
# - Configures HTTP listener on port 5985
# - Adds Windows Firewall exception for WinRM (private/domain networks only by default)
# - Registers default endpoints
Verification (from caller or target):
Test-WSMan -ComputerName REMOTE-PC
# Returns wsmid + ProductVendor on success
If the target is on a public network (e.g. a laptop on a coffee-shop Wi-Fi), Enable-PSRemoting won't open the firewall for public profiles by default. Either change the network profile to private, or:
Set-NetFirewallRule -Name 'WINRM-HTTP-In-TCP-PUBLIC' -RemoteAddress Any -Profile Public -Enabled True
(Be deliberate about exposing WinRM to public networks — usually not what you want.)
Without Kerberos (i.e. not on a domain), the caller has to declare which targets it trusts for NTLM auth. On the caller:
# Add a single host
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'REMOTE-PC' -Force
# Or multiple, comma-separated
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'REMOTE-PC,SERVER1,LAPTOP4' -Force
# Or all hosts (lazy, use only for short troubleshooting windows)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
# Inspect current setting
Get-Item WSMan:\localhost\Client\TrustedHosts
Then auth with local credentials of the target:
$cred = Get-Credential REMOTE-PC\Mack # username = TARGET\user, password = TARGET's user password
Enter-PSSession -ComputerName REMOTE-PC -Credential $cred
The TARGET\user format is what makes Windows look up the user against the target's local SAM database instead of trying domain auth.
Three patterns, picking by use case:
When the script is short or you've embedded the logic:
$cred = Get-Credential
Invoke-Command -ComputerName REMOTE-PC -Credential $cred -ScriptBlock {
Get-WinEvent -FilterHashtable @{LogName='System'; Id=41; StartTime=(Get-Date).AddDays(-30)} -ErrorAction SilentlyContinue |
Select-Object TimeCreated,
@{N='BugCheck';E={'0x{0:X}' -f $_.Properties[0].Value}},
@{N='Param1';E={'0x{0:X}' -f $_.Properties[1].Value}}
}
When the script lives in this skill's scripts/ dir, Invoke-Command -FilePath ships it to the target and executes there:
$cred = Get-Credential
Invoke-Command -ComputerName REMOTE-PC -Credential $cred `
-FilePath "C:\Users\Me\.claude\skills\windows-ops\scripts\health-audit.ps1" `
-ArgumentList @('-Days', 30)
Limitation: -FilePath doesn't ship dependent files. If the script dot-sources _lib/common.ps1 (which all of windows-ops's scripts do), this fails. Use Pattern 3 instead.
For scripts with file dependencies, copy the skill folder to the target first:
$s = New-PSSession -ComputerName REMOTE-PC -Credential (Get-Credential)
# Push the entire skill (one-time setup)
Copy-Item -ToSession $s `
-Path "$HOME\.claude\skills\windows-ops" `
-Destination "C:\Temp\windows-ops" `
-Recurse -Force
# Then run any script
Invoke-Command -Session $s -ScriptBlock {
& C:\Temp\windows-ops\scripts\health-audit.ps1 -Days 30 -Json
} | ConvertFrom-Json
# Or grab raw text
$report = Invoke-Command -Session $s -ScriptBlock {
& C:\Temp\windows-ops\scripts\health-audit.ps1 -Days 30 2>&1
}
$report
Remove-PSSession $s
This is the canonical pattern for serious remote troubleshooting: stage once, run many scripts.
For single-purpose log queries, Get-WinEvent -ComputerName works without setting up sessions:
# Pull System log entries from a remote box
Get-WinEvent -ComputerName REMOTE-PC -Credential $cred -FilterHashtable @{
LogName='System'
ProviderName='storahci'
Id=129
StartTime=(Get-Date).AddDays(-7)
}
This uses RPC under the hood (different port: 135 + dynamic high ports), not WinRM. It works without Enable-PSRemoting but the Remote Event Log Management firewall rule must be enabled on the target:
# On the target, one-time
Set-NetFirewallRule -DisplayGroup 'Remote Event Log Management' -Enabled True
When this works, it's the fastest path for "just give me the events from that machine". When WinRM and RPC are both available, prefer WinRM for cleaner auth semantics.
If you enter a session on REMOTE-A and try to access a network resource (file share, another machine) from inside, you'll get Access is denied because credentials don't forward by default.
Three solutions:
On the caller:
Enable-WSManCredSSP -Role Client -DelegateComputer 'REMOTE-A'
On the target REMOTE-A:
Enable-WSManCredSSP -Role Server
Then use -Authentication CredSSP:
Enter-PSSession -ComputerName REMOTE-A -Credential $cred -Authentication CredSSP
# From inside, can now hop to REMOTE-B with same credentials
Security note: CredSSP exposes credentials to the target. Not for use against untrusted machines.
Modern domain alternative — set on REMOTE-B (the second hop), allowing REMOTE-A to delegate to it:
Set-ADComputer REMOTE-B -PrincipalsAllowedToDelegateToAccount (Get-ADComputer REMOTE-A)
Per-resource, doesn't expose credentials to REMOTE-A.
The simplest: design the diagnostic to not need to hop. Every script in this skill reads local-only system state — no network resources needed.
Modern alternative on Win10 1809+ / Server 2019+. PowerShell remoting over OpenSSH instead of WinRM.
Target side (one-time):
# Install OpenSSH Server (if not already)
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service -Name sshd -StartupType 'Automatic'
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
# Configure pwsh as a remoting subsystem
$sshdConfig = 'C:\ProgramData\ssh\sshd_config'
Add-Content $sshdConfig 'Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo'
Restart-Service sshd
Caller side:
Enter-PSSession -HostName REMOTE-PC -UserName Mack
# Same as SSH — uses ~\.ssh\id_rsa, ~/.ssh/config, known_hosts
Advantages over WS-Man:
Enter-PSSession -HostName against a Windows target with this configured| Error message | Cause | Fix |
|---|---|---|
| "Cannot find the computer X" | DNS or netbios resolution | Use FQDN or IP; nslookup X to check |
| "The WinRM client cannot process the request" | TrustedHosts not configured | Set-Item WSMan:\localhost\Client\TrustedHosts -Value 'X' -Force |
| "Access is denied" | Wrong credentials or wrong auth model | Use TARGET\user format for local; Get-Credential to retype |
| "The user name or password is incorrect" | Same as above usually | Verify by logging into target directly first |
| "Kerberos error" workgroup target | Trying Kerberos against non-domain target | Use -Authentication Negotiate explicitly, plus TrustedHosts |
| "The client cannot connect to the destination" | WinRM not enabled or firewall blocks | On target: Enable-PSRemoting -Force; check firewall |
| "Connecting to remote server failed with HTTP status 401" | Auth handshake failed | Wrong cred, wrong auth scheme, or no listener |
| "The PowerShell version is not allowed" | Target language mode locked down (corp environment) | Use constrained-endpoint flow or skip remoting |
| Hangs forever, no error | Listener port blocked at network level | Test-NetConnection -ComputerName X -Port 5985 |
Scenario: your colleague's PC across town is crashing. They give you Administrator credentials on it. WinRM was enabled by IT.
# 1. Verify connectivity
$target = 'COLLEAGUE-PC.evolution7.local'
Test-WSMan -ComputerName $target
# Expect: wsmid + ProductVendor lines
# 2. Auth
$cred = Get-Credential -Message "Admin on $target"
# Type: evolution7\admin (domain) or COLLEAGUE-PC\admin (local)
# 3. Stage the skill on target
$s = New-PSSession -ComputerName $target -Credential $cred
Copy-Item -ToSession $s -Path "$HOME\.claude\skills\windows-ops" -Destination 'C:\Temp\windows-ops' -Recurse -Force
# 4. Run the audit, capture JSON
$json = Invoke-Command -Session $s -ScriptBlock {
& C:\Temp\windows-ops\scripts\health-audit.ps1 -Days 30 -Json
} -Verbose
$audit = $json | ConvertFrom-Json
# 5. Pull crash triage for any recent Event 41
$triage = Invoke-Command -Session $s -ScriptBlock {
& C:\Temp\windows-ops\scripts\crash-triage.ps1 -Json
} | ConvertFrom-Json
$triage.bugcheckName
# 6. If failing drive detected, check dependencies before recommending disconnect
if ($audit.findings | Where-Object level -eq 'fail' | Where-Object subject -like '*Disk*') {
$diskNum = ($audit.findings | Where-Object level -eq 'fail' | Select -First 1).data.diskNumber
$deps = Invoke-Command -Session $s -ScriptBlock {
& C:\Temp\windows-ops\scripts\drive-dependencies.ps1 -DiskNumber $using:diskNum -Json
} | ConvertFrom-Json
$deps.verdict
}
# 7. Cleanup
Invoke-Command -Session $s -ScriptBlock { Remove-Item C:\Temp\windows-ops -Recurse -Force }
Remove-PSSession $s
The same pattern works for: a server in a datacenter, a kiosk PC, a remote tunnel-accessed home machine. Anything you can Enter-PSSession into, this skill works against.
When the remote box is unreachable at all:
net-opsThis skill is for "I can shell into it but Windows isn't healthy". For "I can't shell into it", that's a different problem domain.