remote-diagnostics.md 14 KB

Remote Windows Diagnostics

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.

Contents

  1. Quick start — single-shot command
  2. The two transports — WS-Man vs SSH
  3. Authentication models
  4. Enabling PSRemoting on the target
  5. Workgroup setup — TrustedHosts
  6. Running this skill's scripts remotely
  7. Reading event logs without entering a session
  8. The double-hop problem
  9. Common errors and fixes
  10. Worked example: full audit on a remote box

Quick start — single-shot command

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.

The two transports

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.

Authentication models

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.

Enabling PSRemoting on the target

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.)

Workgroup setup — TrustedHosts

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.

Running this skill's scripts remotely

Three patterns, picking by use case:

Pattern 1: Inline script block (no file transfer)

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}}
}

Pattern 2: Send a script file (preferred for windows-ops scripts)

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.

Pattern 3: Stage the skill on the target, run via Invoke-Command

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.

Reading event logs without entering a session

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.

The double-hop problem

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:

Solution 1: CredSSP delegation (full credential forwarding)

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.

Solution 2: Resource-Based Constrained Delegation (Kerberos, domain-only)

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.

Solution 3: Run scripts that don't need a hop

The simplest: design the diagnostic to not need to hop. Every script in this skill reads local-only system state — no network resources needed.

SSH transport

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:

  • Single port (22), works through more firewalls
  • Same auth as ssh proper (keys, ssh-agent, jump hosts via ProxyJump)
  • Cross-OS: from macOS/Linux, just use Enter-PSSession -HostName against a Windows target with this configured
  • No TrustedHosts gymnastics

Common errors and fixes

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

Worked example: full audit on a remote box

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.

Cross-reference

When the remote box is unreachable at all:

  • Networking diagnostics → net-ops
  • The target has SSH but not WinRM → use Pattern 3 with SSH transport
  • The target won't even POST / boot → no remote help; needs physical access for Windows RE

This 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.