Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Archives
Log in
Subscribe
September 1, 2026

Improving Get-Win32Process

In this issue:

  • Filtering
    • DateTime Values
      • NewerThan or OlderThan
  • Using CIMSessions
    • Local vs Remote
  • Adding a StopWatch
  • Using the Information Stream
  • Get-Win32Process Revised
  • Summary

A few issues ago I started sharing my solution to the July 2026 PowerShell scripting challenge. I asked you to write a PowerShell function or script that used Get-CimInstance to query the Win32_Process class as a replacement for Get-Service. In PowerShell 7, Get-Service eliminated the -Computername parameter. As an exercise, I wanted to see what you could come up with using Get-CimInstance.

When we left this topic last, I showed you my Get-Win32Process function. As I was preparing to write about type and format extensions, I realized there were a few changes I could make to improve the function which makes for a great learning opportunity. That's why I offer these scripting challenges. So let's revisit my function and see what we can learn.

Filtering

My original function included CIM filtering. I showed you how the function filtered on a process name or process ID. Then, I realized a user, or me, might want to filter processes further such as by WorkingSetSize. I could just as easily use Where-Object.

PS C:\> Get-Win32Process | where WorkingSetSize -ge 500MB

ProcessId Name               HandleCount WorkingSetSize VirtualSize   PSComputerName
--------- ----               ----------- -------------- -----------   --------------
4360      Memory Compression 0           575221760      759955456     PROSPERO
15320     explorer.exe       6719        526581760      2205510574080 PROSPERO
26632     brave.exe          539         595791872      3886698864640 PROSPERO
29436     Dropbox.exe        7325        600989696      3695531700224 PROSPERO
20616     pwsh.exe           2098        642777088      2342485958656 PROSPERO

This is an example of late filtering. There's nothing wrong with this approach and sometimes it is the only solution. But if there is an option for early filtering, you should use it. The rule of thumb is to filter as early in your expression as possible. You may have also heard this described as filter left.

My function is technically already filtering with Get-CimInstance. What I need to add is a more general filter parameter and append it to my existing name or ID filter. Adding the parameter is simple enough.

[Parameter(
    ParameterSetName = 'Name',
    HelpMessage = 'Specify an additional filter using legacy operators like WorkingSetSize >= $(250MB)'
)]
[ValidateNotNullOrEmpty()]
[string]$Filter

> Remember that CIM/WMI filters use the legacy operators.

My original function has two parameter sets. One to find processes by name and one to find it by process ID. The latter will only return a single object so there's no need for additional filtering. I'll only want that when querying by name. This means I need to make sure the new parameter only belongs to the Name parameter set.

After loading the update function into my session I can verify this.

PS C:\> Get-Command Get-Win32Process -Syntax

Get-Win32Process [[-Name] <string>] [-IncludeUserName] [-Filter <string>] [-Computername <string>] [<commonparameters>]

Get-Win32Process -ID <int> [-IncludeUserName] [-Computername <string>] [<commonparameters>]

I still need to use the parameter. My code already defines a default filter, "Name LIKE '$name'". If there is an additional filter, I'll append it using the legacy AND operator.

switch ($PSCmdlet.ParameterSetName) {
    'Name' {
        $Name = $Name.Replace('*', '%')
        #capitalizing the WQL operators so that they stands out
        [string]$defaultFilter = "Name LIKE '$name'"
        if ($filter) {
            $defaultFilter += " AND $filter"  #&lt;-- note the beginning space
        }
        $cimParams['Filter'] = $defaultFilter
    }
    'ID' {
        $cimParams['Filter'] = "ProcessID = $id"
    }
}
Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Using filter: WHERE $($cimParams.Filter)"

The subtle and critical change, which is easy to miss, is that I need a space when joining the filters together. Showing the filter in the verbose messaging also helps me identify potential syntax issues.

Now I can run the command with a more complex filter.

PS C:\&gt; $r = Get-win32Process -filter "WorkingSetSize&gt;=$(500MB)" -Verbose
VERBOSE: [16:37:49.2944633 BEGIN  ] Starting Get-Win32Process v1.1.0
VERBOSE: [16:37:49.2950480 BEGIN  ] Running under PowerShell v7.6.5
VERBOSE: [16:37:49.2953104 PROCESS] Using parameter set Name
VERBOSE: [16:37:49.2966046 PROCESS] Using filter: WHERE Name LIKE '%' AND WorkingSetSize&gt;=524288000
VERBOSE: Perform operation 'Query CimInstances' with following parameters, ''queryExpression' = SELECT * FROM Win32_Process WHERE Name LIKE '%' AND WorkingSetSize&gt;=524288000,'queryDialect' = WQL,'namespaceName' = root\cimv2'.
VERBOSE: Operation 'Query CimInstances' complete.
VERBOSE: [16:37:49.4520701 PROCESS] Found 5 matching processes
VERBOSE: [16:37:49.4528798 END    ] Ending Get-Win32Process

My initial command using late filtering took 255ms. Using early filtering cut that down to 169ms. As I query multiple machines and remote machines, this difference will pay off. Especially if I take the extra step of getting the user name for each process.

One other thing I want to point out in my example is that I placed the 500MB value in a sub-expression. If I ran Get-win32Process -filter "WorkingSetSize&gt;=500MB", PowerShell would treat 500MB as a string which obviously is useless. Putting it in a sub-expression means that PowerShell will "resolve" 500MB to an integer and plug that into the filter.

DateTime Values

Most of the properties you would want to filter are numeric so those should be rather straightforward. However, the CreationDate property is returned as a DateTimeValue. This is a bit more challenging to filter on.

Commands like this will fail.

PS C:\&gt; $t = (Get-Date).AddMinutes(-30)
PS C:\&gt; $r = Get-Win32Process -filter "CreationDate&gt;=$t"
PS C:\&gt; $r
Get-CimInstance:
Line |
  85 |              $get = Get-CimInstance @cimParams
     |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~
     | The WS-Management service cannot process the request. The WQL query is invalid.

My goal is to find all processes started in the last 30 minutes. The problem is that the WQL query is expecting a string. Turning $t to string isn't enough. Even though we are using CIM cmdlets, they are still querying the legacy WMI repository on the backend. I like that the CIM cmdlets automatically convert datetime properties to DateTime objects. But that won't work in a filter.

For that, we need to return to WMI and convert the datetime object to a DMTF datetime string. In Windows PowerShell we could use the built-in ConvertFromDateTime and ConvertToDateTime functions on WMI objects. CIM instances lack these methods, but we can still access them even in PowerShell 7. We can invoke the same .NET method that the legacy convert methods were using:

$tt = [System.Management.ManagementDateTimeConverter]::ToDmtfDateTime($t)

Now $t is a DMTF string, 20260826161841.220466-240, which I can use in a filter.

PS C:\&gt; $r = Get-win32Process -filter "CreationDate&gt;='$tt'" -Verbose
VERBOSE: [16:59:05.6971808 BEGIN  ] Starting Get-Win32Process v1.1.0
VERBOSE: [16:59:05.6974793 BEGIN  ] Running under PowerShell v7.6.5
VERBOSE: [16:59:05.6977121 PROCESS] Using parameter set Name
VERBOSE: [16:59:05.6981944 PROCESS] Using filter: WHERE Name LIKE '%' AND CreationDate&gt;='20260826161841.220466-240'
VERBOSE: Perform operation 'Query CimInstances' with following parameters, ''queryExpression' = SELECT * FROM Win32_Process WHERE Name LIKE '%' AND CreationDate&gt;='20260826161841.220466-240','queryDialect' = WQL,'namespaceName' = root\cimv2'.
VERBOSE: Operation 'Query CimInstances' complete.
VERBOSE: [16:59:05.8625887 PROCESS] Found 3 matching processes
VERBOSE: [16:59:05.8632738 END    ] Ending Get-Win32Process

> Don't forget to wrap the string in single quotes.

You should be able to use this technique to filter on any WMI class that has a datetime property using Get-CimInstance.

Want to read the full issue?
Already a paid subscriber? Click here to log in.
GitHub
Bluesky
LinkedIn
Mastodon
jdhitsolutions.github.io
Powered by Buttondown, the easiest way to start and grow your newsletter.