在远程会话 returns 本地值上调用命令

Invoke-Command on remote session returns local values

问题

当 运行 使用 PSSession 时,Invoke-Command 的脚本是否应该始终在远程计算机上 运行?

上下文

我运行 下面的 powershell 针对服务器列表:

Clear-Host
$cred = get-credential 'myDomain\myUsername'
$psSessions = New-PSSession -ComputerName @(1..10 | %{'myServer{0:00}' -f $_}) -Credential $cred
Invoke-Command -Session $psSessions -ScriptBlock {
    Get-Item -Path 'HKLM:\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters' 
} | Sort-Object PSComputerName
# $psSessions  | Remove-PSSession

这返回:

    Hive: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos


Name                           Property                   PSComputerName
----                           --------                   --------------
Parameters                     MaxPacketSize : 1          myServer01
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer02
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer03
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer04
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer05
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer06
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer07
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer08
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer09
                               MaxTokenSize  : 65535
Parameters                     MaxPacketSize : 1          myServer10
                               MaxTokenSize  : 65535

一切看起来都不错;只有我没想到会看到这些值/我 运行 在这些服务器上设置值之前,我将此作为快速检查以确保我没有覆盖任何内容。

我使用 regedit 快速浏览了其中一台服务器;发现 MaxTokenSizeMaxPacketSize 不存在。

然后我修改了命令以使用 Get-ItemProperty 而不是 Get-Item:

Invoke-Command -Session $psSessions -ScriptBlock {
    Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters' -Name 'MaxTokenSize'
} | Sort-Object PSComputerName

这次我得到了 10 个错误:

Property MaxTokenSize does not exist at path HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\Kerberos\Parameters.
    + CategoryInfo          : InvalidArgument: (MaxTokenSize:String) [Get-ItemProperty], PSArgumentException
    + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
    + PSComputerName        : myServer01
# ... (and the same for the other 9 servers, with only PSComputerName changing)

关于返回值的来源...它们来自我的本地计算机。修改我的本地注册表项并重新 运行 原始命令显示所有 "servers" 具有新值。

我猜这是一个错误;但是因为到目前为止我还没有玩过 PSSessions 想在这里检查以防我对这些命令的理解/使用有问题,或者在使用 [=20 时是否有已知的陷阱需要注意=]s.

将其通过管道传输到 fl * 或 ft *,这样它就不会使用格式文件来显示注册表项。格式文件在本地运行 get-itemproperty 以尝试显示属性。

从 $PSHOME\Registry.format.ps1xml 的底部开始,类型为 Microsoft。Win32.RegistryKey:

<ScriptBlock>
  $result = (Get-ItemProperty -LiteralPath $_.PSPath |
      Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider |
      Format-List | Out-String | Sort).Trim()
  $result = $result.Substring(0, [Math]::Min($result.Length, 5000) )
  if($result.Length -eq 5000) { $result += "..." }
  $result
</ScriptBlock>

tl;dr:

  • 根本原因是 bug formatting instructions for registry keys (as of Windows PowerShell 5.1.18362.125 and PowerShell Core 7.0.0-preview.2) leading to the unexpected mix of remote and local information - see this GitHub issue.

  • 最好的 解决方法 是简单地使用 Get-ItemProperty(没有 -Name 参数)而不是 Get-Item


Mathias R. Jessen has provided the crucial pointer in a comment on the question, and 提供了有限的解决方法和指向根本原因的指针,但值得提供更多背景信息:

PowerShell 附带 formatting instructions 类型 Microsoft.Win32.RegistryKey,由 Get-Item 输出,带有 registry 路径。

这些格式化说明为默认(表格)视图定义了一个名为 Property 计算列 ,这有助于显示输出注册表项的摘要 values,这涉及使用 Get-ItemProperty 再次访问注册表 ,如 js2010 的答案所示。

但是,幕后 Get-ItemProperty 调用 总是 访问 local 注册表 - 即使键是通过 PowerShell 远程处理从另一台机器检索,因此你最终会得到远程和本地信息的虚假混合

请注意,从技术上讲,当 Get-Item 是 运行 远程 时,您收到的 本地 是由于远程处理中涉及的序列化和反序列化,原始 Microsoft.Win32.RegistryKey 对象的 近似值 。此近似值是一个自定义对象,具有原始对象 属性 值的静态副本,其(模拟)类型名称为 Deserialized.Microsoft.Win32.RegistryKey - 请注意前缀。

PowerShell 根据输出对象的 完整类型名称 应用格式化指令,但在没有特定指令或给定 Deserialized.<originalTypeName> 类型的情况下,PowerShell 应用<originalTypeName> 的说明,这就是导致此处出现问题的原因。

A - 麻烦,但与版本无关[1] - 查看有问题的格式化指令的方法是 运行 以下命令:

(Get-FormatData Microsoft.Win32.RegistryKey -PowerShellVersion $PSVersionTable.PSVersion).FormatViewDefinition.Control | % {
  $colNames = $_.Headers.Label
  $colValues = $_.Rows.Columns.DisplayEntry.Value
  foreach ($i in 0..($colNames.Count-1)) {
     [pscustomobject] @{
       ColumnName = $colNames[$i]
       ColumnValue = $colValues[$i]
     }
  }
} | Format-Table -Wrap

这会生成 table 视图的列名和定义:


ColumnName ColumnValue                                                                                                                   
---------- -----------                                                                                                                   
Name       PSChildName                                                                                                                   
Property                                                                                                                                 
                $result = (Get-ItemProperty -LiteralPath $_.PSPath |                                        
                    Select * -Exclude PSPath,PSParentPath,PSChildName,PSDrive,PsProvider |                  
                    Format-List | Out-String | Sort).Trim()                                                 
                $result = $result.Substring(0, [Math]::Min($result.Length, 5000) )                          
                if($result.Length -eq 5000) { $result += "..." }                                            
                $result                                                                                     


js2010 的回答中建议的变通方法 - 管道传输到 Format-Table *Format-List * 是有效的,因为它可以防止显示不适用的 local 信息: 通过显式指定属性(甚至通过通配符模式 *),只有 那些 属性会显示在输出上 - 而不是有缺陷的计算列。

但是,虽然输出对象的真实 Property 属性 提供对值 names 的访问在手头的注册表项中,它不提供实际的 data,计算的 Property column 的方式。

相比之下,使用不带 -Name 参数的 Get-ItemProperty 代替 Get-Item 作为解决方法 returns both 值名称和数据(即使在远程处理时也是正确的)甚至没有限制(而 Get-Item 将输出限制为 5000 个字符。)

输出格式会略有不同,但所有信息都在那里。


[1] 也就是说,该命令也适用于 PowerShell Core,其中内置格式化指令不再作为外部 [=34] =] files 而被编译到 executable.