【问题标题】:Why does Get-WmiObject fail when run remotely via Invoke-Command?为什么通过 Invoke-Command 远程运行 Get-WmiObject 失败?
【发布时间】:2019-07-12 21:52:16
【问题描述】:

我正在尝试构建一个简单的“get-ProcessInfo.ps1”模块,以用于名为 Kansa 的 PowerShell(取证/IR)框架。该命令是一个简单的单行程序,它调用 Get-WMIObject win32_process 并将其通过管道传递给 Select-Object。然后 Kansa 应该通过 Export-Csv cmdlet 将数据导出到 Csv。该脚本在我的本地主机上运行没有问题,但是在通过 Kansa 中的 Invoke-Command cmdlet 远程运行(在 Windows 上)时失败。对于每个 processID,我的错误日志显示 get-ProcessInfo.ps1“找不到具有进程标识符 #### 的进程”。其他模块在远程主机上运行没有问题,所以我知道我正在以管理员身份进行身份验证。因此,我认为我遇到了权限错误,或者可能是 Wmi 的身份验证问题。我通过域管理员帐户从 Windows 框在 Windows 域中运行它。

堪萨斯 GitHub:https://github.com/davehull/Kansa 堪萨话:https://www.youtube.com/watch?v=cQmsT9D0kKI

我已尝试复制在另一个 Kansa 模块中看到的 WmiObject 调用,但这仍然没有从远程主机生成数据。 - https://github.com/davehull/Kansa/blob/master/Modules/Process/Get-ProcsWMI.ps1

我试图了解 InjectedThreads.ps1 脚本中发生了什么,因为它远程使用 WmiObject 没有问题,但它有点超出我的想象。据我所知,听起来 WmiObject 是“非托管的”(未经身份验证?/没有从 PowerShell 继承 Kerberos?)-https://github.com/davehull/Kansa/blob/master/Modules/Process/Get-InjectedThreads.ps1

我尝试了 Wmi 身份验证、模拟和权限的多种变体。不幸的是,仍然没有产生远程数据。 - https://blogs.msmvps.com/richardsiddaway/2011/08/04/authentication-impersonation-and-privileges/

最后,由于 get-WmiObject 在技术上已被弃用,为了支持 Get-CIMInstance,我尝试了 Get-CIMInstance cmdlet 的多种变体。

这是我正在尝试制作的模块中的代码,get-ProcessInfo.ps1

Get-WmiObject win32_process | Select-Object creationdate,ws,ProcessName,ProcessID,ParentProcessID, @{Name = 'ParentProcessName';Expression = {(Get-Process -Id $_.ParentProcessId).Name}},Path,CommandLine,@{Name = 'ParentProcessPath';Expression = {(Get-Process -Id $_.ParentProcessId).Path}}

预期结果应该是进程列表及其相关信息,它在我的本地计算机上运行,​​并且在通过 Kansa.ps1 中的 Invoke-Command 远程运行时不返回任何数据(只是错误)

有人可以为我指明正确的方向吗?这里到底发生了什么,以及我该如何解决这个问题?

*请注意,此脚本是通过远程主机上的 WinRM(Invoke-Command)运行的,因此询问凭据是不可能的,硬编码凭据也是如此。

【问题讨论】:

  • 你试过直接Get-CimInstance -ComputerName吗?无需通过Invoke-Command 参与 PowerShell 远程处理。
  • 我会尝试使用明确指定的凭据在另一个作业中运行您的代码:Invoke-Command { Start-Job { Get-WmiObject ... } -Credential ... } -Credential ...

标签: powershell invoke-command get-wmiobject


【解决方案1】:

在您的计算属性中,您假设 ParentProcessId 始终是有效的 id:

@{Name = 'ParentProcessPath';Expression = {(Get-Process -Id $_.ParentProcessId).Path}

Win32_Process documentation 声明如下:

ParentProcessId 标识的进程有可能已经终止,所以 ParentProcessId 可能不是指正在运行的进程。 ParentProcessId 也可能错误地引用了重用进程标识符的进程。

这意味着ParentProcessId 可能指向一个已经终止的进程。在这种情况下,(Get-Process -Id $_.ParentProcessId).Path 将触发错误(因为没有找到例如 528 的进程,因此 (Get-Process -Id $_.ParentProcessId) 将是 $null 并且您正在尝试调用 $null.Name)。

我在我的机器上测试了你的单线,没有收到你上面描述的任何错误。因此我检查了 Kansa 是如何调用给定模块的,可以在 here 找到:

$Job = Invoke-Command -Session $PSSessions -FilePath $Module -ArgumentList $Arguments -AsJob -ThrottleLimit $ThrottleLimit

如您所见,您的模块文件被作为远程作业调用(请参阅-AsJob 开关)。我问自己,通过后台调用脚本块时,错误处理是否会有所不同。我发现这个有趣的 StackOverflow answer 声明:

使用 throw 会将作业对象的 State 属性更改为“Failed”。 ...

这是有道理的,因为与默认 PowersShell 主机相比,作业不能忽略错误。所以,我将你的命令打包到一个作业中(在我的本地机器上):

$job =start-job -ScriptBlock { Get-WmiObject win32_process | Select-Object creationdate,ws,ProcessName,ProcessID,ParentProcessID, @{Name = 'ParentProcessName';Expression = {(Get-Process -Id $_.ParentProcessId).Name}},Path,CommandLine,@{Name = 'ParentProcessPath';Expression = {(Get-Process -Id $_.ParentProcessId).Path}}  } 
Wait-Job $job
$result =Receive-Job $job

调用Receive-Job时,我在命令行看到以下错误:

找不到进程标识符为 528 的进程。 在行:1 字符:1 + $result =接收-Job $job + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : ObjectNotFound: (528:Int32) [Get-Process], ProcessCommandException + FullyQualifiedErrorId : NoProcessFoundForGivenId,Microsoft.PowerShell.Commands.GetProcessCommand + PSComputerName : 本地主机

我还检查了$result 中的$null

> $null -eq $result
True

从上面可以看出,错误提到了NoProcessFoundForGivenId,这是不言自明的。如您所见,错误处理取决于您的代码如何被调用(作为 Job 或在 PowerShell 主机上)。

你能做什么?

您可以检查ParentProcessId 是否有效。因此,您必须将代码更改为:

Get-WmiObject win32_process | ForEach-Object {

    # Create a custom object with the given properties. If Select-Object doesn't find given property it'll create an empty `NoteProperty` with the given name
    $processInfo = $_ | Select-Object creationdate,ws,ProcessName,ProcessID,ParentProcessID, ParentProcessPath, ParentProcessName
    $p = (Get-Process -Id $_.ParentProcessId -ErrorAction SilentlyContinue) 

    if ($null -ne $p){
        # Parent process exists lets fill up the empty properties
        $processInfo.ParentProcessName = $p.Name
        $processInfo.ParentProcessPath = $p.Path
    }

    $processInfo # Return this value to the pipeline
} 

可能有更复杂的方法来执行$null 检查Select-Object 的计算属性,不幸的是,我不知道。

如果我们将上面的代码包装在一个作业中并运行它:

    $job = start-job -ScriptBlock { Get-WmiObject win32_process | ForEach-Object {
        $processInfo = $_ | Select-Object creationdate,ws,ProcessName,ProcessID,ParentProcessID, ParentProcessPath, ParentProcessName
        $p = (Get-Process -Id $_.ParentProcessId -ErrorAction SilentlyContinue) 
        if ($null -ne $p){
            $processInfo.ParentProcessName = $p.Name
            $processInfo.ParentProcessPath = $p.Path
        }

        $processInfo # Return this value to the pipeline
    } 
}

Wait-Job $job
$result = Receive-Job $job
if ($null -ne $result){
    $result
}
else {
    Write-Error "Job with id $($job.Id) failed"
}

我们将获得所有进程而不会出现任何错误。

希望对您有所帮助。

【讨论】:

  • 非常感谢。这个解决方案效果很好。
  • 这个答案的精彩细节。很有帮助。
猜你喜欢
  • 1970-01-01
  • 2014-10-02
  • 1970-01-01
  • 2015-10-28
  • 2018-03-07
  • 1970-01-01
  • 1970-01-01
  • 2017-11-01
  • 1970-01-01
相关资源
最近更新 更多