<# .SYNOPSIS Checks whether a device will accept remote PowerShell, and optionally fixes it. .DESCRIPTION Looks at the pieces remote management actually needs: the WinRM service, a listener, the inbound firewall rule, and a local Test-WSMan round trip. It also flags a Public network profile, which is the usual reason quick config refuses to run. With -Repair it starts WinRM and runs Set-WSManQuickConfig. With -ComplianceOutput it returns a single Compliant/NonCompliant string, which is what a ConfigMgr configuration item discovery script wants to see. .PARAMETER Repair Fix what's broken. Honors -WhatIf. .PARAMETER SkipNetworkProfileCheck Let the repair run even when a network adapter is on the Public profile. .PARAMETER ComplianceOutput Return only 'Compliant' or 'NonCompliant' instead of the full object. .EXAMPLE .\Test-WinRMReadiness.ps1 .EXAMPLE .\Test-WinRMReadiness.ps1 -Repair -WhatIf #> [CmdletBinding(SupportsShouldProcess)] param( [switch]$Repair, [switch]$SkipNetworkProfileCheck, [switch]$ComplianceOutput ) function Get-WinRMState { $issues = [System.Collections.Generic.List[string]]::new() $service = Get-Service -Name WinRM -ErrorAction SilentlyContinue if (-not $service) { $issues.Add('WinRM service not found') } else { if ($service.Status -ne 'Running') { $issues.Add("WinRM service is $($service.Status)") } if ("$($service.StartType)" -eq 'Disabled') { $issues.Add('WinRM service is disabled') } } # Listener and WS-Man checks only work while the service is running. $listeners = @() $wsman = $false if ($service -and $service.Status -eq 'Running') { $listeners = @(Get-ChildItem -Path WSMan:\localhost\Listener -ErrorAction SilentlyContinue) if ($listeners.Count -eq 0) { $issues.Add('No WinRM listener configured') } try { $null = Test-WSMan -ComputerName localhost -ErrorAction Stop; $wsman = $true } catch { $issues.Add("Test-WSMan failed: $($_.Exception.Message)") } } $rules = @(Get-NetFirewallRule -Name 'WINRM-HTTP-In-TCP*' -ErrorAction SilentlyContinue | Where-Object { "$($_.Enabled)" -eq 'True' }) if ($rules.Count -eq 0) { $issues.Add('No enabled WinRM inbound firewall rule') } $public = @(Get-NetConnectionProfile -ErrorAction SilentlyContinue | Where-Object { "$($_.NetworkCategory)" -eq 'Public' }) [pscustomobject]@{ ComputerName = $env:COMPUTERNAME Ready = ($issues.Count -eq 0) ServiceStatus = if ($service) { "$($service.Status)" } else { 'Missing' } StartType = if ($service) { "$($service.StartType)" } else { $null } Listeners = ($listeners | ForEach-Object { $_.Keys -join ' ' }) -join '; ' FirewallRules = $rules.Count WSManResponds = $wsman PublicNetwork = ($public.Count -gt 0) Issues = $issues -join '; ' Action = 'None' } } $state = Get-WinRMState if ($Repair -and -not $state.Ready) { if ($state.PublicNetwork -and -not $SkipNetworkProfileCheck) { Write-Warning 'A network adapter is on the Public profile. Quick config will refuse to run. Use -SkipNetworkProfileCheck if that is expected.' $state.Action = 'Skipped (Public network)' } elseif ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Enable WinRM (service, listener, firewall rule)')) { try { if ($state.StartType -eq 'Disabled') { Set-Service -Name WinRM -StartupType Automatic -ErrorAction Stop } if ($state.ServiceStatus -ne 'Running') { Start-Service -Name WinRM -ErrorAction Stop } Set-WSManQuickConfig -Force -SkipNetworkProfileCheck:$SkipNetworkProfileCheck -ErrorAction Stop $state = Get-WinRMState $state.Action = 'Repaired' } catch { Write-Error "Repair failed: $($_.Exception.Message)" $state.Action = 'Failed' } } else { $state.Action = 'WhatIf' } } if ($ComplianceOutput) { if ($state.Ready) { 'Compliant' } else { 'NonCompliant' } } else { $state }