PowerShell 属性 表达式将执行时间增加 4-5 倍

PowerShell property expression increases execution time by 4-5 times

向下滚动查看 TL;DR

我需要尽快获取每个进程的以下属性,最好是 5 秒,最多 10 秒:ID、名称、说明、路径、公司、用户名、会话 ID、开始时间、内存,CPU(百分比,不是时间)

为了获取这些数据,我整理了以下片段(我认为)在功能上是完美的:

$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes  = Get-Process -IncludeUserName | 
                Select-Object `
                    @{Name='Id';Expression={[int]$_.Id}}, 
                    @{Name='Name';Expression={[string]$_.Name}}, 
                    @{Name='Description';Expression={[string]$_.Description}}, 
                    @{Name='Path';Expression={[string]$_.Path}}, 
                    @{Name='Company';Expression={[string]$_.Company}}, 
                    @{Name='Username';Expression={[string]$_.UserName}},
                    @{Name='SessionId';Expression={[string]$_.SessionId}}, 
                    @{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},  
                    @{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
                    @{Name='CPUPercent';Expression={
                        [int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
                    }}

问题是执行需要 18-22 秒,由以下行(增加了大约 16 秒)引起:

@{Name='CPUPercent';Expression={
     [int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
PS C:\Windows\system32> Measure-Command -Expression {
    $ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
    $Processes  = Get-Process -IncludeUserName | 
                    Select-Object `
                        @{Name='Id';Expression={[int]$_.Id}}, 
                        @{Name='Name';Expression={[string]$_.Name}}, 
                        @{Name='Description';Expression={[string]$_.Description}}, 
                        @{Name='Path';Expression={[string]$_.Path}}, 
                        @{Name='Company';Expression={[string]$_.Company}}, 
                        @{Name='Username';Expression={[string]$_.UserName}},
                        @{Name='SessionId';Expression={[string]$_.SessionId}}, 
                        @{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},  
                        @{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
                        @{Name='CPUPercent';Expression={
                            [int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
                        }}
}

TotalSeconds      : 19.061206

当我删除上面提到的缓慢 属性 表达式并保留 WMI 查询时,执行大约需要 4.5 秒:

Measure-Command -Expression {
    $ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
    $Processes  = Get-Process -IncludeUserName | 
                    Select-Object `
                        @{Name='Id';Expression={[int]$_.Id}}, 
                        @{Name='Name';Expression={[string]$_.Name}}, 
                        @{Name='Description';Expression={[string]$_.Description}}, 
                        @{Name='Path';Expression={[string]$_.Path}}, 
                        @{Name='Company';Expression={[string]$_.Company}}, 
                        @{Name='Username';Expression={[string]$_.UserName}},
                        @{Name='SessionId';Expression={[string]$_.SessionId}}, 
                        @{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},  
                        @{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
}

TotalSeconds      : 4.5202906

我认为通过在单个查询中获取所有必需的数据并返回引用 $ProcessCPU 数组会很快 - 但我很感激我正在遍历存储在 $Processes.

长话短说:

有没有比使用上面的迭代更高效的方法将两个对象连接到一个公共 属性 上? IE。 $ProcessCPU.IDProcess on $Processes.Id

我尝试了以下块来测试 $Output = $ProcessCPU + $Processes | Group-Object -Property Id,它只执行了 3 秒,但输出不可接受:

PS C:\Windows\system32> Measure-Command -Expression {
    $ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object @{Name='Id';Expression={[int]$_.IDProcess}}, PercentProcessorTime
    $Processes  = Get-Process -IncludeUserName | 
                    Select-Object `
                        @{Name='Id';Expression={[int]$_.Id}}, 
                        @{Name='Name';Expression={[string]$_.Name}}, 
                        @{Name='Description';Expression={[string]$_.Description}}, 
                        @{Name='Path';Expression={[string]$_.Path}}, 
                        @{Name='Company';Expression={[string]$_.Company}}, 
                        @{Name='Username';Expression={[string]$_.UserName}},
                        @{Name='SessionId';Expression={[string]$_.SessionId}}, 
                        @{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},  
                        @{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
    $Output = $ProcessCPU + $Processes | Group-Object -Property Id
}

TotalSeconds      : 2.9656969

  • 首先使用 CIM 建立一个哈希表,将进程 ID (PID) 映射到它们的 CPU 百分比。

  • 然后将计算的 属性 传递给 Select-Object 查阅该哈希表以进行高效查找:

Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
  ForEach-Object -Begin   { $htCpuPctg=@{} } `
                 -Process { $htCpuPctg[$_.IdProcess] = $_.PercentProcessorTime }       #`

Get-Process -IncludeUserName | 
  Select-Object Id,
                Name,
                Description,
                Path,
                Company,
                UserName,
                SessionId,
                @{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},  
                @{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
                @{Name='CPUPercent';Expression={ $htCpuPctg[[uint32] $_.Id] }}

注:

    使用
  • Get-CimInstance 而不是 Get-WimObject,因为 CIM cmdlet 取代了 PowerShell v3(2012 年 9 月发布)中的 WMI cmdlet。因此,应该避免使用 WMI cmdlet,尤其是因为 PowerShell Core,所有未来的努力都将去向,甚至 没有 它们了。有关详细信息,请参阅 this answer

  • 通常不需要使用 @{Name='Id';Expression={[int]$_.Id}} 等计算属性来简单地按原样提取 属性 - 只需使用 属性 的 name - Id - 作为 Select-Object -Property 参数(但你已经澄清你正在使用计算属性,因为你想明确控制 属性 的数据类型,用于通过 JSON).

  • 向物联网网关发送数据
  • 请注意,CIM 将 PID 报告为 [uint32] 类型的值,而 Get-Process 使用 [int] 值 - 因此需要转换为 [uint32]在哈希表查找中。