使用Invoke-Command远程枚举本地组成员时遇网络路径错误
这个问题的核心在于延迟加载的PrincipalCollection和PowerShell远程的双跳认证限制,咱们一步步拆解并解决:
问题原因分析
你的脚本里返回的$lg.Members是一个PrincipalCollection对象,它采用延迟加载机制——也就是说,当你在本地会话中尝试遍历这个集合时,才会实际去查询域资源。但Invoke-Command的远程会话默认存在双跳限制:远程机器上的进程无法将你的管理员凭据传递给域控制器(这是出于安全考虑的约束委派限制)。这就导致本地枚举时,没有有效凭据去访问域,从而抛出"网络路径未找到"的错误。
哪怕加上-EnableNetworkAccess也没用,因为这个参数只是允许远程会话访问本地网络资源,并不解决跨域的双跳认证问题。
解决方案:在远程机器上提前完成成员枚举与属性提取
最直接的解决办法是在远程脚本块内部就完成所有成员的枚举和属性提取,把需要的信息转换成普通的PowerShell对象返回,避免将延迟加载的集合带回本地。这样所有的域查询操作都在远程机器上完成,远程进程可以直接使用你提供的管理员凭据访问域控制器。
方案1:修改原有的DirectoryServices.AccountManagement代码
调整你的脚本块,在远程端就遍历成员并提取关键属性:
$computers = "blah" $creds = Get-Credential $sb = { param($c) Add-Type -AssemblyName System.DirectoryServices.AccountManagement $ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine $context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype,$c $idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName $lg = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context,$idtype,"administrators") # 在远程端枚举所有成员并转换为自定义对象,避免延迟加载 $members = $lg.Members | ForEach-Object { [PSCustomObject]@{ SamAccountName = $_.SamAccountName DisplayName = $_.DisplayName ObjectClass = $_.StructuralObjectClass IsLocal = ($_.ContextType -eq [System.DirectoryServices.AccountManagement.ContextType]::Machine) } } return $members } foreach ($c in $computers) { if ($c -eq $env:COMPUTERNAME) { & $sb -c $c } else { Invoke-Command -ComputerName $c -Credential $creds -ScriptBlock $sb -ArgumentList $c } }
方案2:使用CIM/WMI查询(无需加载额外程序集)
如果你不想依赖System.DirectoryServices.AccountManagement程序集,可以用Win32_GroupUser这个WMI类来直接查询本地组的成员,同样在远程端完成所有处理:
$computers = "blah" $creds = Get-Credential $sb = { param($c) # 获取本地管理员组的信息 $adminGroup = Get-CimInstance -ClassName Win32_Group -Filter "Name='administrators'" # 查询组的所有成员 Get-CimInstance -ClassName Win32_GroupUser -Filter "GroupComponent=`"Win32_Group.Domain='$($adminGroup.Domain)',Name='$($adminGroup.Name)'`"" | ForEach-Object { # 解析成员的Domain和Name $partComponent = $_.PartComponent -split '=',3 | Select-Object -Skip 1 | ForEach-Object { $_.Trim('"') } [PSCustomObject]@{ Domain = $partComponent[0] UserName = $partComponent[1] IsLocal = ($partComponent[0] -eq $env:COMPUTERNAME) } } } foreach ($c in $computers) { if ($c -eq $env:COMPUTERNAME) { & $sb -c $c } else { Invoke-Command -ComputerName $c -Credential $creds -ScriptBlock $sb -ArgumentList $c } }
关于"无需全程使用管理员凭据"的补充
如果你的场景允许,你可以考虑为目标机器配置读取本地组的最低权限,比如给域用户授予本地的"读取组信息"权限,但这个需要在每台目标机器上配置,操作成本较高。上面的两种方案已经在尽量减少管理员凭据的使用范围——仅在远程会话中使用,本地会话不需要管理员权限。
内容的提问来源于stack exchange,提问作者Ash

