Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Building a PowerShell Process Memory Tool

Posted on December 28, 2018January 2, 2019

This week I've been testing out a new browser, Brave, as a possible replacement for Firefox. One of the reasons I switched to Firefox from Chrome was performance and better resource utilization. Brave may now be a better choice, but that's not what this article is about. In order to assess resource utilization I turned to the Get-Process PowerShell cmdlet. Because I found myself needing to get some very specific results, I decided to write a PowerShell function to simplify the process. This is why you should be learning and using PowerShell, to build your own tools to solve your business needs. Here is some of the story on my development process.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Start with the Basics

I first started with a core PowerShell expression to display process information, using the Workingset property.

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

Substitute in any process you want like chrome to see similar results. This simple expression gets all the brave processes and pipes them to Measure-Object which calculates the sum and average of the workingset property. It would be helpful to see the process name in the output especially because I intend to do comparisons with Firefox. If I do this:

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

I get the sum for both browsers. No. I would need to run Get-Process and Measure-Object for each process. One way would be to use 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
}

This is better. I'm using the common PipelineVariable parameter to retrieve the process name. Measure-Object's output doesn't include the process name from Get-Process so in order for Select-Object to work I need a way to access something from earlier in the pipeline. That's what PipelineVariable achieves.

Getting process data with PowerShell

Formatting Values

I don't know about you but I can't readily convert all those bytes into something more meaningful. But PowerShell can.

"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)}}
}

I now have code that generates the friendly result I need.

PowerShell formatted results

But I need to make this flexible and re-usable. This is where functions come into play. I can quickly turn this into a function.

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)}}
}

}

And it works.

Results from a basic PowerShell function

Scaling Out

At this point I could consider this complete. However, when I write PowerShell functions, especially something is getting values or data, I typically want to be able to have it work with remote computers. My initial thought was that because Get-Process has a -Computername parameter, all I had to do was add it to my function and pass it along in the code. The potential downside to this approach is that when you connect to a remote computer in this way, you are connecting over legacy remoting connections. I'm trying to avoid using legacy connections where possible so that means I need to take advantage of PowerShell remoting.  One solution would be to wrap my Get-Process command inside an Invoke-Command expression.

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
}

This is getting closer to a mature, PowerShell function.

Getting process data via PowerShell remoting

Functions Don't Format

As you can see, this function doesn't include any error handling. And since I never need to see the RunspaceID property I would always have to wrap my function in another PowerShell expression to remove it. It would be nicer if the function wrote an object to the pipeline with just values I wanted to see. The other potential drawback is that my sum and average values are formatted in MB. I have made an assumption that limits the flexibility of this function. I was initially lazy. The better approach is to write an object to the pipeline with raw, unformatted data.

As I revised the function, this became the core of the scriptblock:

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}} 
}

The output values were now back to bytes. But you know, I really would like to see them formatted as MB by default. PowerShell can do that and if fact does that all the time. When you run Get-Process the default display is formatted for you. What you see is not the raw value of the underlying process objects.  PowerShell is using a format directive. You can too.

Format Data

First, you need to give your output object a type name. I could have built a function using a PowerShell class. But it is just as easy to insert a type name.

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
    $_
}

Now the tricky part. Format directives are special XML files saved with a .ps1xml file. One of the things I can do is define a custom heading and value.

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

The default output uses the formatting directives but I can still use the underlying actual property names.

Using a proper PowerShell function

By the way, don't read too much into these results as Firefox has a lot of extensions and Brave has several open tabs.

Instead of creating a separate .ps1xml file, I included the contents as a here string in my script file with my function and create a temporary file which is then imported using Update-FormatData. The complete file is on Github.

Yes, this took a little bit of time to develop, but I now have a re-usable and flexible PowerShell function. and a model that I can use for future functions. I think it is helpful to see the development process as much as the final product. If you have feedback or questions, please feel free to leave a comment.  Enjoy!

Update 2 January 2019

I've updated the code in the Github gist to better handle the use of wildcards. I also tweaked the formatting directives.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

3 thoughts on “Building a PowerShell Process Memory Tool”

  1. Collin Chaffin says:
    December 31, 2018 at 5:36 pm

    Great article! I added some info in the Gist for you to review though regarding the use of wildcards that folks might want to avoid using until updated.

    1. Jeffery Hicks says:
      December 31, 2018 at 6:41 pm

      Thanks for the comments. Yes, wildcards should be avoided which I tried to stress in the comment based help.

  2. Pingback: Building More PowerShell Functions • The Lonely Administrator

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d