PowerShell 技能连载 - WinRM 高级配置与排错

适用于 PowerShell 5.1 及以上版本

WinRM 为什么总是连不上?

WinRM(Windows Remote Management)是 PowerShell Remoting 的底层协议,基于 WS-Management 标准。它允许管理员在远程计算机上执行命令、传输文件和管理配置。在企业环境中,WinRM 是批量运维的核心基础设施——从 Ansible 的 Windows 模块到 Azure Arc 的本地代理,都依赖它正常工作。

然而在实际部署中,WinRM 的”能连上”往往需要多个条件同时满足:服务运行、监听器配置正确、防火墙放行、认证协议匹配、权限授予。其中任何一个环节出问题,都会导致连接失败,而错误信息往往晦涩难懂,比如 Access is deniedThe WinRM client cannot process the requestWS-Man could not connect

本文将从配置加固、连接排错、受限端点三个方面,系统讲解 WinRM 的高级管理技巧,帮助你快速定位和解决远程管理中的常见故障。

WinRM 配置与安全加固

在正式使用 WinRM 之前,合理的初始配置至关重要。默认的 Enable-PSRemoting -Force 虽然能快速启用,但在生产环境中还需要关注监听器类型、传输加密和服务账户限制等方面。

以下脚本展示了 WinRM 的一次性安全配置流程,包括检查服务状态、配置 HTTPS 监听器以及限制远程访问的服务账户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 第一步:确保 WinRM 服务运行并设为自动启动
$service = Get-Service -Name WinRM -ErrorAction SilentlyContinue
if ($service.Status -ne 'Running') {
Start-Service -Name WinRM
Write-Host "WinRM 服务已启动"
}
Set-Service -Name WinRM -StartupType Automatic

# 第二步:检查现有监听器
$listeners = Get-ChildItem -Path WSMan:\localhost\Listener
Write-Host "当前监听器数量: $($listeners.Count)"

foreach ($listener in $listeners) {
$protocol = Get-Item -Path "$($listener.PSPath)\Transport" |
Select-Object -ExpandProperty Value
$port = Get-Item -Path "$($listener.PSPath)\Port" |
Select-Object -ExpandProperty Value
$addr = Get-Item -Path "$($listener.PSPath)\Address" |
Select-Object -ExpandProperty Value
Write-Host " 协议: $protocol 端口: $port 地址: $addr"
}

# 第三步:创建 HTTPS 监听器(需要先准备证书)
$cert = Get-ChildItem -Path Cert:\LocalMachine\My |
Where-Object {
$_.EnhancedKeyUsageList.FriendlyName -contains 'Server Authentication' -and
$_.NotAfter -gt (Get-Date)
} |
Sort-Object -Property NotAfter -Descending |
Select-Object -First 1

if ($cert) {
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS `
-Address * -CertificateThumbprint $cert.Thumbprint -Force
Write-Host "HTTPS 监听器已创建,证书指纹: $($cert.Thumbprint)"

# 配置防火墙规则放行 5986 端口
$fwRule = Get-NetFirewallRule -DisplayName 'WinRM HTTPS' -ErrorAction SilentlyContinue
if (-not $fwRule) {
New-NetFirewallRule -DisplayName 'WinRM HTTPS' `
-Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow
Write-Host "防火墙规则已添加 (TCP 5986)"
}
} else {
Write-Warning "未找到有效的服务器认证证书,跳过 HTTPS 监听器创建"
}

# 第四步:限制允许远程连接的用户组
Set-Item -Path WSMan:\localhost\Config\MaxConcurrentOperationsPerUser -Value 50
Set-Item -Path WSMan:\localhost\Service\MaxConnections -Value 100
Write-Host "并发限制已配置(每用户 50,总计 100)"

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
WinRM 服务已启动
当前监听器数量: 1
协议: HTTP 端口: 5985 地址: *
Directory: WSMan:\localhost\Listener

Type Keys Name
---- ---- ----
Container {Transport=HTTPS, Address=*} Listener_2026309012345

HTTPS 监听器已创建,证书指纹: A1B2C3D4E5F6789012345678901234ABCD...
防火墙规则已添加 (TCP 5986)
并发限制已配置(每用户 50,总计 100)

连接排错工具集

当 WinRM 连接失败时,手动逐项排查非常耗时。下面这个诊断脚本将常见的检查步骤整合在一起,能够快速定位问题所在——从网络连通性到认证协议,再到 WinRM 服务配置,一目了然。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
function Test-WinRMConnection {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$ComputerName,

[ValidateSet('HTTP', 'HTTPS')]
[string]$Transport = 'HTTPS',

[System.Management.Automation.PSCredential]$Credential
)

$port = if ($Transport -eq 'HTTPS') { 5986 } else { 5985 }
$results = [ordered]@{}

# 检查 1:DNS 解析
try {
$ip = [System.Net.Dns]::GetHostAddresses($ComputerName) |
Select-Object -First 1 -ExpandProperty IPAddressToString
$results['DNS 解析'] = "成功 ($ip)"
} catch {
$results['DNS 解析'] = "失败: $($_.Exception.Message)"
}

# 检查 2:TCP 端口连通性
$tcp = New-Object System.Net.Sockets.TcpClient
try {
$tcp.Connect($ComputerName, $port)
$results['TCP 端口'] = "成功 ($port 开放)"
} catch {
$results['TCP 端口'] = "失败: 端口 $port 不可达"
} finally {
$tcp.Close()
}

# 检查 3:TrustedHosts 配置
$trustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts).Value
if ($trustedHosts -eq '*' -or $trustedHosts -split ',' -contains $ComputerName) {
$results['信任主机'] = "已信任 (当前值: $trustedHosts)"
} else {
$results['信任主机'] = "未信任 (当前值: $trustedHosts)"
}

# 检查 4:WinRM 测试连接
$sessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck
$testParams = @{
ComputerName = $ComputerName
SessionOption = $sessionOption
ErrorAction = 'Stop'
}
if ($Transport -eq 'HTTPS') {
$testParams['UseSSL'] = $true
}
if ($Credential) {
$testParams['Credential'] = $Credential
}

try {
$session = New-PSSession @testParams
$results['远程会话'] = '成功'
Remove-PSSession -Session $session
} catch {
$results['远程会话'] = "失败: $($_.Exception.Message)"
}

# 输出诊断报告
Write-Host "`n========== WinRM 连接诊断报告 ==========" -ForegroundColor Cyan
Write-Host "目标: $ComputerName`n传输: $Transport`n端口: $port`n"
foreach ($key in $results.Keys) {
$status = $results[$key]
$icon = if ($status -match '成功|已信任') { '[OK]' } else { '[!!]' }
$color = if ($status -match '成功|已信任') { 'Green' } else { 'Red' }
Write-Host ("{0,-12} {1} {2}" -f "[$key]", $icon, $status) -ForegroundColor $color
}
Write-Host "========================================" -ForegroundColor Cyan

return $results
}

# 使用示例
Test-WinRMConnection -ComputerName 'SRV01.contoso.com' -Transport HTTPS

执行结果示例:

1
2
3
4
5
6
7
8
9
10
========== WinRM 连接诊断报告 ==========
目标: SRV01.contoso.com
传输: HTTPS
端口: 5986

[DNS 解析] [OK] 成功 (10.0.1.50)
[TCP 端口] [OK] 成功 (5986 开放)
[信任主机] [!!] 未信任 (当前值: )
[远程会话] [!!] 失败: The WinRM client cannot process the request because the server name is not in the TrustedHosts list.
========================================

上面的诊断结果清楚地指出了问题:目标服务器不在 TrustedHosts 列表中。对于非域环境,需要将远程主机名加入信任列表,或者使用 HTTPS 配合正确的证书验证。

常见的错误码及其含义如下:

错误码 含义 解决方向
0x80070005 Access Denied 检查凭据、用户组权限
0x80338012 WinRM 服务未运行 远程启动 WinRM 服务
0x80338125 证书验证失败 检查证书有效性或使用 SkipCACheck
0x80338104 WinRM 无法处理请求 检查 TrustedHosts 和认证协议

Constrained Endpoint:受限会话配置

在多人协作的运维团队中,直接给所有成员完整的远程 PowerShell 权限风险很高。Constrained Endpoint(受限端点)允许你创建自定义的会话配置,精确控制远程用户可以执行哪些命令——这既满足了权限最小化原则,又为审计提供了完整的操作日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 定义受限会话的角色能力(Role Capability)
$roleCapDir = "$env:ProgramFiles\WindowsPowerShell\RoleCapabilities"
if (-not (Test-Path $roleCapDir)) {
New-Item -Path $roleCapDir -ItemType Directory -Force
}

# 创建角色能力文件
$roleCapParams = @{
Path = Join-Path $roleCapDir 'Maintenance.psrc'
VisibleCmdlets = @(
'Get-Process', 'Get-Service', 'Restart-Service',
'Get-EventLog', 'Get-WinEvent',
'Get-Volume', 'Get-Partition'
)
VisibleFunctions = @('Get-SystemUptime', 'Get-DiskHealth')
VisibleProviders = @('FileSystem')
VisibleExternalCommands = @('whoami.exe', 'hostname.exe')
ScriptsToProcess = @()
AliasDefinitions = @(
@{ Name = 'gsv'; Value = 'Get-Service' }
)
FunctionDefinitions = @(
@{
Name = 'Get-SystemUptime'
ScriptBlock = {
$os = Get-CimInstance -ClassName Win32_OperatingSystem
$uptime = (Get-Date) - $os.LastBootUpTime
[PSCustomObject]@{
LastBoot = $os.LastBootUpTime
UptimeDays = [math]::Round($uptime.TotalDays, 2)
}
}
}
)
}
New-PSRoleCapabilityFile @roleCapParams
Write-Host "角色能力文件已创建: Maintenance.psrc"

# 创建受限会话配置
$cred = Get-Credential -Message '输入受限端点的运行账户'
$sessionParams = @{
Name = 'Maintenance'
RunAsCredential = $cred
RoleDefinitions = @{ 'CONTOSO\ServerOps' = @{ RoleCapabilities = 'Maintenance' } }
TranscriptDirectory = 'C:\Transcripts'
RunAsVirtualAccount = $true
MaximumReceivedDataSizePerCommandMB = 10
MaximumReceivedObjectSizeMB = 5
SessionType = 'RestrictedRemoteServer'
}
try {
Register-PSSessionConfiguration @sessionParams -Force
Write-Host "受限端点 'Maintenance' 注册成功"

# 验证配置
$config = Get-PSSessionConfiguration -Name Maintenance
Write-Host " 运行账户: $($config.RunAsUser)"
Write-Host " 虚拟账户: $($config.RunAsVirtualAccount)"
Write-Host " 审计目录: $($config.TranscriptDirectory)"

# 重启 WinRM 使配置生效
Restart-Service -Name WinRM -Force
Write-Host "WinRM 服务已重启,配置生效"
} catch {
Write-Error "注册受限端点失败: $($_.Exception.Message)"
}

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
角色能力文件已创建: Maintenance.psrc

Location: C:\Program Files\WindowsPowerShell\RoleCapabilities

Name Value
---- -----
VisibleCmdlets {Get-Process, Get-Service, Restart-Service, Get-EventLog...}
VisibleFunctions {Get-SystemUptime, Get-DiskHealth}
VisibleProviders {FileSystem}

cmdlet Register-PSSessionConfiguration at command pipeline position 1
Supply values for the following parameters:

受限端点 'Maintenance' 注册成功
运行账户: CONTOSO\SvcMaintenance
虚拟账户: True
审计目录: C:\Transcripts
WinRM 服务已重启,配置生效

注册完成后,团队成员可以通过以下方式连接受限端点:

1
Enter-PSSession -ComputerName SRV01 -ConfigurationName Maintenance

连接后只能使用预定义的命令集,所有操作都会被记录到 C:\Transcripts 目录下的转录文件中,方便事后审计。

注意事项

  1. HTTPS 监听器必须有有效证书。如果使用自签名证书,客户端需要通过 SkipCACheck 选项跳过 CA 验证,但这会降低安全性。在生产环境中应使用企业 CA 或公共 CA 签发的证书,确保证书的主题名称(或 SAN)与服务器主机名匹配。

  2. TrustedHosts 是安全风险点。将 TrustedHosts 设为 * 等于信任所有远程主机,仅适用于测试环境。在域环境中应依赖 Kerberos 认证,避免修改 TrustedHosts;在工作组环境中至少明确指定目标主机名。

  3. Constrained Endpoint 使用虚拟账户RunAsVirtualAccount = $true 会让会话使用临时创建的虚拟账户运行,该账户在会话结束后自动销毁,比直接使用真实服务账户更安全。但虚拟账户无法访问网络资源,如需跨机器操作需配合 gMSA(组托管服务账户)。

  4. 防火墙规则需双向检查。WinRM 使用 HTTP(5985)和 HTTPS(5986)两个端口。确保远程主机的入站规则放行了对应端口,同时检查本地网络出站策略和中间链路的防火墙设备。

  5. 转录文件会持续增长。配置 TranscriptDirectory 后,所有远程会话的操作都会被完整记录。建议配合定期归档脚本清理过期的转录文件,避免磁盘空间耗尽。

  6. WinRM 配置修改后需重启服务。无论是修改监听器、注册新的会话配置还是更改服务参数,都需要执行 Restart-Service -Name WinRM -Force 才能生效。注意这会断开所有现有的远程会话,应在维护窗口操作。

PowerShell 技能连载 - 远程管理进阶

适用于 PowerShell 5.1 及以上版本(Windows)

在前面的文章中,我们介绍了 SSH 远程管理的基础用法。本文将深入 PowerShell 远程管理的进阶技巧,包括 WinRM 配置优化、会话管理、 constrained endpoints(受限端点)、远程调试,以及大规模并行远程操作的策略。

WinRM 配置优化

WinRM 是 PowerShell 远程管理的默认传输协议。了解其配置项对排查连接问题和优化性能至关重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 查看 WinRM 服务状态
Get-Service WinRM

# 快速配置 WinRM(启用远程管理)
winrm quickconfig

# 查看 WinRM 完整配置
winrm get winrm/config

# 查看 WinRM 监听器
winrm enumerate winrm/config/listener

# 常用配置优化
# 增加最大内存限制(默认 150MB,处理大量数据时可能不够)
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024

# 增加并发 shell 数
Set-Item WSMan:\localhost\Shell\MaxShellsPerUser 50

# 增加最大接收数据大小
Set-Item WSMan:\localhost\Shell\MaxEnvelopeSizeKB 8192

# 配置可信主机(用于工作组环境)
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "SRV01,SRV02,192.168.1.*" -Force

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
Status   Name               DisplayName
------ ---- -----------
Running WinRM Windows Remote Management (WS-Management)

WinRM service is already running on this machine.
WinRM is already set up for remote management on this computer.

Config
MaxEnvelopeSizekb = 8192
MaxTimeoutms = 60000
MaxBatchItems = 32000

注意:在域环境中,WinRM 默认使用 Kerberos 认证,无需额外配置。在工作组环境中,需要配置 TrustedHosts 或使用 HTTPS 端点。

会话管理最佳实践

PSSession 是远程操作的核心对象。正确管理会话可以避免资源泄漏和连接超时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 创建持久会话(可复用)
$session = New-PSSession -ComputerName SRV01 -Name "AdminSession"

# 查看所有活动会话
Get-PSSession | Format-Table Name, ComputerName, State, Availability -AutoSize

# 在会话中执行多个命令(保持状态)
Invoke-Command -Session $session -ScriptBlock {
# 第一个命令设置变量
$script:logPath = "C:\Logs\app.log"
$script:logContent = Get-Content $script:logPath -Tail 100
Write-Host "已加载 $($script:logContent.Count) 行日志"
}

Invoke-Command -Session $session -ScriptBlock {
# 第二个命令使用之前的变量
$errors = $script:logContent | Select-String "ERROR"
$errors | Group-Object | Sort-Object Count -Descending |
Select-Object Count, Name | Format-Table -AutoSize
}

# 使用完成后释放会话
Remove-PSSession -Session $session

# 批量清理所有断开的会话
Get-PSSession | Where-Object State -eq 'Disconnected' | Remove-PSSession

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
Name          ComputerName State       Availability
---- ------------ ----- ------------
AdminSession SRV01 Opened Available

已加载 100 行日志

Count Name
----- ----
15 ERROR Database connection timeout
8 ERROR Failed to authenticate user
3 ERROR Disk space critically low

断开与重连会话

PowerShell 3.0 及以上版本支持会话断开与重连,这在网络不稳定时特别有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 创建会话
$session = New-PSSession -ComputerName SRV01

# 启动长时间任务
Invoke-Command -Session $session -ScriptBlock {
# 模拟长时间任务
1..100 | ForEach-Object {
Start-Sleep -Seconds 1
"处理进度:$_/100"
}
} -AsJob

# 断开会话(任务继续在远程运行)
Disconnect-PSSession -Session $session

# 查看断开的会话
Get-PSSession | Where-Object State -eq 'Disconnected'

# 稍后重连
Connect-PSSession -Session $session

# 检查任务状态
Invoke-Command -Session $session -ScriptBlock {
Get-Job | Receive-Job -Keep
}

执行结果示例:

1
2
3
4
5
6
7
8
Id Name            ComputerName    State         ConfigurationName     Availability
-- ---- ------------ ----- ----------------- ------------
1 Session1 SRV01 Disconnected Microsoft.PowerShell None

处理进度:1/100
处理进度:2/100
...
处理进度:42/100

受限端点(Constrained Endpoint)

在企业环境中,可能需要给非管理员提供受限的远程管理能力。通过 JEA(Just Enough Administration)或自定义端点配置,可以精确控制远程用户能执行哪些命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 创建受限会话配置文件
$sessionConfig = @{
SchemaVersion = '2.0.0.0'
GUID = (New-Guid).ToString()
Author = 'Admin'
Description = '受限运维端点'
LanguageMode = 'NoLanguage'
ExecutionPolicy = 'Restricted'
SessionType = 'RestrictedRemoteServer'
VisibleCmdlets = @(
@{ Name = 'Get-Service'; Parameters = @{ Name = 'Name' } },
@{ Name = 'Get-Process' },
@{ Name = 'Get-EventLog'; Parameters = @{ Name = 'LogName' }, @{ Name = 'Newest' } }
)
VisibleFunctions = @('Get-SystemInfo')
RoleDefinitions = @{
'CONTOSO\OpsTeam' = @{ RoleCapabilities = 'MaintenanceRole' }
}
}

$configPath = "C:\Configs\ConstrainedEndpoint.pssc"
New-PSSessionConfigurationFile -Path $configPath @sessionConfig

# 注册端点
Register-PSSessionConfiguration -Name "OpsEndpoint" -Path $configPath -Force

# 使用受限端点连接
$session = New-PSSession -ComputerName SRV01 -ConfigurationName "OpsEndpoint"
Invoke-Command -Session $session -ScriptBlock { Get-Service -Name WinRM }

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
Status   Name               DisplayName
------ ---- -----------
Running WinRM Windows Remote Management (WS-Management)

Name Description
---- -----------
OpsEndpoint 受限运维端点

Status Name DisplayName
------ ---- -----------
Running WinRM Windows Remote Management (WS-Management)

大规模并行远程操作

当需要同时管理数十或数百台服务器时,需要合理的并行策略来平衡效率和资源消耗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function Invoke-ParallelRemoting {
<#
.SYNOPSIS
批量并行远程执行命令
#>
param(
[Parameter(Mandatory)]
[string[]]$ComputerName,

[Parameter(Mandatory)]
[scriptblock]$ScriptBlock,

[int]$ThrottleLimit = 16,
[int]$TimeoutMinutes = 10
)

# 读取服务器列表
$totalServers = $ComputerName.Count
Write-Host "共 $totalServers 台服务器需要处理,并发数:$ThrottleLimit" -ForegroundColor Cyan

$results = @()
$successCount = 0
$failCount = 0
$sw = [System.Diagnostics.Stopwatch]::StartNew()

# 分批处理
$batches = [Math]::Ceiling($totalServers / $ThrottleLimit)

for ($batch = 0; $batch -lt $batches; $batch++) {
$batchServers = $ComputerName | Select-Object -Skip ($batch * $ThrottleLimit) -First $ThrottleLimit

Write-Host "`n批次 $($batch + 1)/$batches ($($batchServers.Count) 台)..." -ForegroundColor Yellow

$batchResults = Invoke-Command -ComputerName $batchServers `
-ScriptBlock $ScriptBlock `
-ThrottleLimit $ThrottleLimit `
-ErrorAction SilentlyContinue

foreach ($r in $batchResults) {
if ($r) {
$successCount++
$results += $r
} else {
$failCount++
}
}
}

$sw.Stop()

Write-Host "`n========== 执行摘要 ==========" -ForegroundColor Green
Write-Host "总服务器数:$totalServers"
Write-Host "成功:$successCount"
Write-Host "失败:$failCount"
Write-Host "总耗时:$($sw.Elapsed.TotalSeconds) 秒"
Write-Host "平均每台:$([math]::Round($sw.Elapsed.TotalSeconds / $totalServers, 2)) 秒"

return $results
}

# 示例:批量检查服务器补丁状态
$computers = Get-Content "C:\Servers.txt"
$results = Invoke-ParallelRemoting -ComputerName $computers -ScriptBlock {
$os = Get-CimInstance Win32_OperatingSystem
$latestPatch = Get-CimInstance Win32_QuickFixEngineering |
Sort-Object InstalledOn -Descending | Select-Object -First 1

[PSCustomObject]@{
Computer = $env:COMPUTERNAME
OS = $os.Caption
LastPatch = $latestPatch.InstalledOn.ToString('yyyy-MM-dd')
HotFixID = $latestPatch.HotFixID
UptimeDays = [math]::Round(((Get-Date) - $os.LastBootUpTime).TotalDays, 1)
}
}

$results | Sort-Computer | Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
50 台服务器需要处理,并发数:16

批次 1/4 (16 台)...
批次 2/4 (16 台)...
批次 3/4 (16 台)...
批次 4/4 (2 台)...

========== 执行摘要 ==========
总服务器数:50
成功:48
失败:2
总耗时:23.45
平均每台:0.47

Computer OS LastPatch HotFixID UptimeDays
-------- -- --------- -------- ----------
SRV01 Microsoft Windows Server ... 2025-05-10 KB5036908 2.1
SRV02 Microsoft Windows Server ... 2025-04-28 KB5034441 14.3
SRV03 Microsoft Windows Server ... 2025-05-05 KB5035849 7.0

远程文件传输

通过 PowerShell 远程会话,可以在本地和远程机器之间传输文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 方法一:使用 Copy-Item 与会话
$session = New-PSSession -ComputerName SRV01

# 上传文件到远程
Copy-Item -Path "C:\Scripts\Deploy-App.ps1" `
-Destination "C:\Scripts\" `
-ToSession $session

# 从远程下载文件
Copy-Item -Path "C:\Logs\app.log" `
-Destination "C:\Downloads\SRV01-app.log" `
-FromSession $session

# 方法二:传输整个目录
Copy-Item -Path "C:\Projects\MyApp\" `
-Destination "C:\Apps\MyApp\" `
-ToSession $session -Recurse

# 方法三:传输前压缩(适合大量小文件)
Compress-Archive -Path "C:\Projects\MyApp\*" -DestinationPath "C:\Temp\app.zip"
Copy-Item -Path "C:\Temp\app.zip" -Destination "C:\Temp\" -ToSession $session
Invoke-Command -Session $session -ScriptBlock {
Expand-Archive -Path "C:\Temp\app.zip" -DestinationPath "C:\Apps\MyApp" -Force
Remove-Item "C:\Temp\app.zip"
}

Remove-PSSession $session

执行结果示例:

1
# 文件传输无输出,成功即完成

注意事项

  1. 双跃点认证:从 A 机器远程到 B 机器,再从 B 访问 C 机器的资源时,默认凭据不会传递。需要配置 CredSSP 或使用 Kerberos 委派
  2. WinRM 端口:HTTP 默认端口 5985,HTTPS 默认端口 5986。生产环境建议使用 HTTPS
  3. 会话清理:长时间运行的脚本应确保在 finally 块中清理 PSSession,避免会话泄漏
  4. 序列化限制:远程命令返回的对象会被序列化/反序列化,某些类型(如 FileStream)无法直接返回,需要在远程端处理
  5. 并发限制:默认 WinRM 每个用户最多 5 个并发 shell,可通过 MaxShellsPerUser 调整
  6. 网络超时New-PSSession 默认操作超时 3 分钟,可通过 -OperationTimeoutSeconds 调整