当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 构建 PowerShell 进程内存工具

作者:精品下载站 日期:2024-12-14 07:53:55 浏览:11 分类:玩电脑

构建 PowerShell 进程内存工具


本周我一直在测试一款新浏览器 Brave,它可能是 Firefox 的替代品。我从 Chrome 转向 Firefox 的原因之一是性能和更好的资源利用率。 Brave 现在可能是更好的选择,但这不是本文的主题。为了评估资源利用率,我使用了 Get-Process PowerShell cmdlet。因为我发现自己需要获得一些非常具体的结果,所以我决定编写一个 PowerShell 函数来简化该过程。这就是为什么应该学习和使用PowerShell,以构建您自己的工具来解决的业务需求。这是我的开发过程的一些故事。

我首先使用核心 PowerShell 表达式来使用工作集属性来显示进程信息。

Get-Process brave | Measure-Object Workingset -sum -average | 
Select-object Count,Sum,Average

替换为您想要的任何进程(例如 Chrome)以看到类似的结果。这个简单的表达式获取所有勇敢的进程并将它们传输到 Measure-Object,该对象计算工作集属性的总和和平均值。在输出中查看进程名称会很有帮助,特别是因为我打算与 Firefox 进行比较。如果我这样做:

Get-Process brave,firefox | Measure-Object Workingset -sum -average | 
Select-object Count,Sum,Average

我得到了两个浏览器的总和。不,我需要为每个进程运行 Get-Process 和 Measure-Object。一种方法是使用 ForEach-Object。

"brave","firefox" | foreach-object {
Get-Process -name $_ -PipelineVariable pv | 
Measure-Object Workingset -sum -average | 
Select-object @{Name="Name";Expression = {$pv.name}},
Count,Sum,Average
}

这个更好。我使用通用 PipelineVariable 参数来检索进程名称。 Measure-Object 的输出不包含 Get-Process 中的进程名称,因此为了使 Select-Object 正常工作,我需要一种方法来访问管道早期的内容。这就是 PipelineVariable 所实现的目标。

[玩转系统] 构建 PowerShell 进程内存工具

我不了解你,但我无法轻易地将所有这些字节转换成更有意义的东西。但 PowerShell 可以。

"brave","firefox" | foreach-object {
Get-Process -name $_ -PipelineVariable pv | 
Measure-Object Workingset -sum -average | 
Select-object @{Name="Name";Expression = {$pv.name}},
Count,
@{Name="SumMB";Expression = {[math]::round($_.Sum/1MB,2)}},
@{Name="AvgMB";Expression = {[math]::round($_.Average/1MB,2)}}
}

我现在有了可以生成我需要的友好结果的代码。

[玩转系统] 构建 PowerShell 进程内存工具

但我需要使其灵活且可重用。这就是函数发挥作用的地方。我可以快速将其变成一个函数。

Function Get-MyProcess {
[cmdletbinding()]
Param([string[]]$Name)

$Name | foreach-object {
Get-Process -name $_ -PipelineVariable pv | 
Measure-Object Workingset -sum -average | 
Select-object @{Name="Name";Expression = {$pv.name}},
Count,
@{Name="SumMB";Expression = {[math]::round($_.Sum/1MB,2)}},
@{Name="AvgMB";Expression = {[math]::round($_.Average/1MB,2)}}
}

}

它有效。

[玩转系统] 构建 PowerShell 进程内存工具

至此我可以认为这已经完成了。但是,当我编写 PowerShell 函数时,尤其是获取值或数据的函数时,我通常希望能够使其与远程计算机一起工作。我最初的想法是,因为 Get-Process 有一个 -Computername 参数,所以我所要做的就是将其添加到我的函数中并在代码中传递它。这种方法的潜在缺点是,当您以这种方式连接到远程计算机时,您是通过旧的远程连接进行连接。我试图尽可能避免使用旧连接,这意味着我需要利用 PowerShell 远程处理。一种解决方案是将 Get-Process 命令包装在 Invoke-Command 表达式中。

Function Get-MyProcess {
[cmdletbinding()]
Param([string[]]$Name,[string]$Computername = $env:computername)

Invoke-Command -ScriptBlock {
$using:Name | foreach-object {
Get-Process -name $_ -PipelineVariable pv | 
Measure-Object Workingset -sum -average | 
Select-object @{Name="Name";Expression = {$pv.name}},
Count,
@{Name="SumMB";Expression = {[math]::round($_.Sum/1MB,2)}},
@{Name="AvgMB";Expression = {[math]::round($_.Average/1MB,2)}}
}
} -ComputerName $computername
}

这越来越接近成熟的 PowerShell 功能。

[玩转系统] 构建 PowerShell 进程内存工具

如您所见,该函数不包含任何错误处理。由于我永远不需要查看 RunspaceID 属性,因此我始终必须将函数包装在另一个 PowerShell 表达式中才能将其删除。如果该函数将一个仅包含我想要查看的值的对象写入管道,那就更好了。另一个潜在的缺点是我的总和和平均值的格式为 MB。我做了一个限制这个函数灵活性的假设。起初我很懒。更好的方法是使用原始的、未格式化的数据将对象写入管道。

当我修改该函数时,这成为脚本块的核心:

foreach ($item in $ProcessName) {
    Get-Process -Name $item -PipelineVariable pv -OutVariable ov |
        Measure-Object -Property WorkingSet -Sum -Average |
        Select-Object -Property @{Name = "Name"; Expression = {$pv.name}},
    Count, 
    @{Name = "Threads"; Expression = {$ov.threads.count}},
    Average, Sum,
    @{Name = "Computername"; Expression = {$env:computername}} 
}

输出值现在恢复为字节。但你知道,我真的很希望看到它们默认格式化为 MB。 PowerShell 可以做到这一点,而且事实上一直都是这样做的。当您运行 Get-Process 时,系统会为您设置默认显示格式。您看到的并不是底层流程对象的原始值。 PowerShell 使用格式指令。你也可以。

首先,您需要为输出对象指定类型名称。我可以使用 PowerShell 类构建一个函数。但插入类型名称也同样容易。

Invoke-Command @PSBoundParameters | Select-Object -Property * -ExcludeProperty RunspaceID,PS* |
ForEach-Object {
    #insert a custom type name for the format directive
    $_.psobject.typenames.insert(0, "myProcessMemory") | Out-Null
    $_
}

现在是棘手的部分。格式指令是与 .ps1xml 文件一起保存的特殊 XML 文件。我可以做的一件事是定义自定义标题和值。

<TableColumnItem>
    <ScriptBlock>[math]::Round($_.average/1MB,4)</ScriptBlock>
</TableColumnItem>

默认输出使用格式指令,但我仍然可以使用底层的实际属性名称。

[玩转系统] 构建 PowerShell 进程内存工具

顺便说一下,不要过多地解读这些结果,因为 Firefox 有很多扩展,而 Brave 有几个打开的选项卡。

我没有创建单独的 .ps1xml 文件,而是使用函数将内容作为此处字符串包含在脚本文件中,并创建一个临时文件,然后使用 Update-FormatData 导入该临时文件。完整的文件位于 Github 上。

Get-ProcessMemory.ps1:

Function Get-ProcessMemory {
 get-processmemory code,powershell*

Name                   Count Threads      AvgMB      SumMB      Computername
----                   ----- -------      -----      -----      ------------
Code                       9     154   112.8199  1015.3789         BOVINE320
powershell                 3      77   179.1367   537.4102         BOVINE320
powershell_ise             1      21   242.0586   242.0586         BOVINE320


The default output displays the process name, the total process count, the total number of threads, the average workingset value per process in MB and the total workingset size of all processes also formatted in MB.
.EXAMPLE
PS C:\> get-processmemory -computername srv1,srv2,dom1 -Name lsass -cred company\artd

Name                   Count Threads      AvgMB      SumMB     Computername
----                   ----- -------      -----      -----     ------------
lsass                      1      30    60.1719    60.1719             DOM1
lsass                      1       7    10.8594    10.8594             SRV1
lsass                      1       7     9.6953     9.6953             SRV2
.EXAMPLE
PS C:\> get-processmemory *ActiveDirectory* -Computername dom1 | select-object *

Name         : Microsoft.ActiveDirectory.WebServices
Count        : 1
Threads      : 10
Average      : 46940160
Sum          : 46940160
Computername : DOM1

This example uses a wildcard for the process name because the domain controller only has one related process. The output shows the raw values.
.EXAMPLE
PS C:\> get-processmemory *edge*,firefox,chrome | sort sum -Descending

Name                   Count Threads      AvgMB      SumMB     Computername
----                   ----- -------      -----      -----     ------------
firefox                    6     189   180.4134  1082.4805        BOVINE320
chrome                     8     124    67.1377   537.1016        BOVINE320
MicrosoftEdgeCP            4     171    66.1846   264.7383        BOVINE320
MicrosoftEdge              1      37    70.1367    70.1367        BOVINE320
MicrosoftEdgeSH            1      10     8.2734     8.2734        BOVINE320

Get browser processes and sort on the underlying SUM property in descending order.
.NOTES
    Learn more about PowerShell: http://jdhitsolutions.com/blog/essential-powershell-resources/
.LINK
Get-Process
Invoke-Command
.INPUTS
[string[]]
#>

    [CmdletBinding()]
    [OutputType("myProcessMemory")]

    Param
    (
        [Parameter(Mandatory, ValueFromPipeline, Position = 0)]
        [string[]]$Name,
        [ValidateNotNullOrEmpty()]
        [string[]]$Computername = $env:COMPUTERNAME,
        [PSCredential]$Credential,
        [switch]$UseSSL,
        [Int32]$ThrottleLimit,
        [ValidateSet('Default', 'Basic', 'Credssp', 'Digest', 'Kerberos', 'Negotiate', 'NegotiateWithImplicitCredential')]
        [ValidateNotNullorEmpty()]
        [string]$Authentication = "default"
    )

    Begin {

        Write-Verbose "Starting $($MyInvocation.Mycommand)"

        $sb = {
            Param([string[]]$ProcessName)

            # a process might have multiple instances so get each one by name
            #group the processes to accomodate the use of wildcards
            $data = Get-Process -Name $ProcessName | Group-Object -Property Name
            foreach ($item in $data) {
                $item.group | Measure-Object -Property WorkingSet -Sum -Average |
                    Select-Object -Property @{Name = "Name"; Expression = {$item.name}},
                Count,
                @{Name = "Threads"; Expression = {$item.group.threads.count}},
                Average, Sum,
                @{Name = "Computername"; Expression = {$env:computername}}
            }
        } #close ScriptBlock

        #update PSBoundparamters so it can be splatted to Invoke-Command
        $PSBoundParameters.Add("ScriptBlock", $sb) | Out-Null
        $PSBoundParameters.add("HideComputername", $True) | Out-Null

    } #begin

    Process {

        $PSBoundParameters.Remove("Name") | Out-Null
        Write-Verbose "Querying processes $($name -join ',') on $($Computername -join ',')"

        #need to make sure argument is treated as an array
        $PSBoundParameters.ArgumentList = , @($Name)
        if (-Not $PSBoundParameters.ContainsKey("Computername")) {
            #add the default value if nothing was specified
            $PSBoundParameters.Add("Computername", $Computername) | Out-Null
        }
        $PSBoundParameters | Out-String | Write-Verbose

        Invoke-Command @PSBoundParameters | Select-Object -Property * -ExcludeProperty RunspaceID, PS* |
            ForEach-Object {
            #insert a custom type name for the format directive
            $_.psobject.typenames.insert(0, "myProcessMemory") | Out-Null
            $_
        }

    } #process

    End {

        Write-Verbose "Ending $($MyInvocation.Mycommand)"

    } #end

} #close function

#region formatting directives for the custom object
[xml]$format = @'


    
        
            default
            
                myProcessMemory
            
            
            
                
                    
                        Name
                        20
                        left
                    
                    
                        Count
                        7
                        right
                    
                     
                        Threads
                        7
                        right
                    
                    
                        AvgMB
                        10
                        right
                    
                    
                        SumMB
                        10
                        right
                    
                    
                        Computername
                        16
                        right
                    
                 
                
                    
                        
                            
                                Name
                             TableColumnIt
                            
                                Count
                             TableColumnIt
                            
                                Threads
                             TableColumnIt
                            
                                


是的,这花了一点时间来开发,但我现在有了一个可重用且灵活的 PowerShell 功能。以及我可以用于未来功能的模型。我认为了解开发过程和最终产品是有帮助的。如果您有反馈或疑问,请随时发表评论。享受!

2019 年 1 月 2 日更新

我更新了 Github gist 中的代码,以更好地处理通配符的使用。我还调整了格式指令。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯