问题
当与 PSSession 一起运行时,Invoke-Command
的脚本 block 是否应该始终在远程计算机上运行?
上下文
我针对服务器列表运行了以下 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 快速浏览了其中一台服务器;发现MaxTokenSize
和MaxPacketSize
不存在。
然后我修改了命令以使用 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)
关于返回的值来自哪里......它们来 self 的本地计算机。修改我的本地注册表项并重新运行原始命令显示所有“服务器”都具有新值。
我猜这是一个错误;但因为到目前为止我还没有玩过 PSSession ,所以想在这里检查一下,以防我对这些命令的理解/使用存在问题,或者在使用时是否有需要注意的已知问题PSSession
s。
最佳答案
tl;博士:
根本原因是 formatting instructions 中的错误对于导致远程和本地信息意外混合的注册表项(从 Windows PowerShell 5.1.18362.125 和 PowerShell Core 7.0.0-preview.2 开始) - 请参阅 GitHub issue #10341 .
- 顺便说一句:同样,PowerShell 的 ETS (extended type system)也会带来问题,如马蒂亚斯的回答 here 所示。 .
最好的解决方法就是简单地使用
Get-ItemProperty
(没有-Name
参数)而不是Get-Item
.
Mathias R. Jessen在对该问题的评论中提供了关键指针,并且 js2010's answer提供了有限的解决方法和指向根本原因的指针,但值得提供更多背景信息:
PowerShell 附带 formatting instructions对于类型Microsoft.Win32.RegistryKey
,如 Get-Item
的输出带有注册表路径。
这些格式化指令定义了一个名为 Property
的计算列对于默认(表格) View ,该 View 有助于显示输出注册表项的值的摘要,其中涉及使用 Get-ItemProperty
再次访问注册表。如js2010的答案所示。
然而,幕后Get-ItemProperty
调用始终访问本地注册表 - 即使 key 是通过PowerShell远程处理从另一台计算机检索的,因此您将最终得到远程和本地信息的虚假混合。
请注意,从技术上讲,当 Get-Item
远程运行,您在本地收到的内容是原始 Microsoft.Win32.RegistryKey
的近似值对象,由于远程处理涉及序列化和反序列化。此近似值是一个自定义对象,具有原始对象属性值的静态副本,其(模拟)类型名称为 Deserialized.Microsoft.Win32.RegistryKey
- 注意前缀。
PowerShell 根据输出对象的完整类型名称应用格式化指令,但没有特定指令或给定 Deserialized.<originalTypeName>
类型,PowerShell 应用 <originalTypeName>
的指令,这就是导致这里问题的原因。
一个 - 麻烦,但与版本无关[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
这会生成 TableView 的列名称和定义:
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 *
是有效的,因为它可以防止显示不适用的本地信息:通过显式指定属性(甚至通过通配符模式 *
),只有那些属性显示在输出 - 也不是有缺陷的计算列。
然而,虽然真实的Property
输出对象的属性提供对当前注册表项中的值名称的访问,它不提供实际的数据,方式计算出Property
列确实如此。
相比之下,使用 Get-ItemProperty
没有-Name
参数代替 Get-Item
作为解决方法,返回值名称和数据(即使在远程处理时也正确),甚至没有限制(而 Get-Item
将输出限制为 5000 个字符。)
输出格式会略有不同,但所有信息都在那里。
[1] 也就是说,该命令也适用于 PowerShell Core,其中内置格式化指令不再维护为外部 *.format.ps1xl
文件并被编译成可执行文件。
关于powershell - 远程 session 上的 Invoke-Command 返回本地值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57400948/