Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
August 26, 2025

Processing the Process Scripting Challenge

We're close to the end of another month which means it is time to look at a solution for last month's scripting challenge. These are great opportunities to build your PowerShell scripting skills. Last month's challenge centered on getting process information using the CIM cmdlets. You should be able to apply the techniques and concepts to other scripting projects. As always, my solutions are not the final word or the only way to accomplish the task. Let's dig in.

Part 1

For the first part of the challenge, I asked you to write a PowerShell function that accepts a computername and credential as parameters, and query all processes, excluding the System and System Idle processes, on the specified computer using CIM. The output should include the following information:

  • Computername
  • ProcessName
  • ProcessID
  • WorkingSetSize
  • the command line used to launch the process
  • the process start time

My thought was that you would use the function to get process information from a server. Therefore my function is named Get-ServerProcess.

function Get-ServerProcess {
    [cmdletbinding()]
    [OutputType('ServerProcess')]
    [alias('gsp')]
    param(
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            HelpMessage = 'Specify the name of a computer to query.'
        )]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName = $env:COMPUTERNAME,
        [ValidateNotNullOrEmpty()]
        [PSCredential]$Credential
    )

    begin {
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Starting $($MyInvocation.MyCommand)"
        Write-Verbose "[$((Get-Date).TimeOfDay) BEGIN  ] Running under PowerShell version $($PSVersionTable.PSVersion)"
        #initialize an array for temporary CIMSessions
        $cs = @()
    } #begin

    process {
        if (-not $PSBoundParameters.ContainsKey('Computername')) {
            #Add the default
            $PSBoundParameters['Computername'] = $ComputerName
        }
        try {
            Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Creating temporary CIMSession"
            $PSBoundParameters | Out-String | Write-Verbose
            $cs += New-CimSession @PSBoundParameters -ErrorAction Stop
        }
        catch {
            Write-Error $_
            return
        }

        Write-Verbose "[$((Get-Date).TimeOfDay) PROCESS] Getting Process Information"
        $data = $cs | Get-CimInstance -ClassName Win32_Process -Filter 'ProcessID>=5' |
        Sort-Object -Property CSName, ProcessName
        foreach ($item in $data) {
            [PSCustomObject]@{
                PSTypeName   = 'ServerProcess'
                Name         = $item.Name
                ID           = $item.ProcessId
                WS           = $item.WorkingSetSize
                CommandLine  = $item.CommandLine
                StartTime    = $item.CreationDate
                ComputerName = $item.CSName
            }
        }
    } #process

    end {
        if ($cs.Count -gt 0) {
            Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Removing temporary CIMSessions"
            $cs | Remove-CimSession
        }
        Write-Verbose "[$((Get-Date).TimeOfDay) END    ] Ending $($MyInvocation.MyCommand)"
    } #end

} #close Get-ServerProcess

My function creates a temporary CIM session to the specified computer. It then queries the Win32_Process class, filtering out the System and System Idle processes. Instead of filtering by name, I am filtering by ProcessID. From what I can tell, the processes I want to filter out will have a low process ID. I don't know exactly what the cutoff is, but a value of 5 worked during development so that's what I used.

The function write a custom object to the pipeline for each process. The object has a PSTypeName of ServerProcess. This will allow us to create a custom format XML file later. Here is an example of the unformatted output.

Get-ServerProcess
figure 1

Part 2

For the second part of challenge, I asked you to revise the function so that it include the process owner name in the domain\user name format and how long the process has been running. Using Get-CimClass or the Get-CimClassMethod function from the PSScriptTools module, you can see that the Win32_Process class has a method named GetOwner().

PS C:\> Get-CimClassMethod -ClassName win32_process

   Class: Root/Cimv2:Win32_Process

Name                    ResultType Parameters
----                    ---------- ----------
AttachDebugger          UInt32     {}
Create                  UInt32     {CommandLine, CurrentDirectory, ProcessStartupInformation, Pro...
GetAvailableVirtualSize UInt32     {AvailableVirtualSize}
GetOwner                UInt32     {User, Domain}
GetOwnerSid             UInt32     {Sid}
SetPriority             UInt32     {Priority}
Terminate               UInt32     {Reason}

Because we work with CIM instances, we need to use the Invoke-CimMethod cmdlet to call the method.

PS C:\> $p = Get-CimInstance -ClassName win32_process -filter "ProcessID=$PID"
PS C:\> $p | Invoke-CimMethod -Name GetOwner

Domain   ReturnValue User PSComputerName
------   ----------- ---- --------------
PROSPERO           0 Jeff

The result is an object that includes the domain and user name.

To get the running time, I can subtract the process creation time from the current date and time.

PS C:\> New-Timespan -Start $p.CreationDate -end (Get-Date)

Days              : 0
Hours             : 2
Minutes           : 35
Seconds           : 44
Milliseconds      : 294
Ticks             : 93442949806
TotalDays         : 0.108151562275463
TotalHours        : 2.59563749461111
TotalMinutes      : 155.738249676667
TotalSeconds      : 9344.2949806
TotalMilliseconds : 9344294.9806

However, there are a few major changes I'm going to make. Instead of using CIM to get the information locally, I'm going to use a script block and get the information using Invoke-Command. This in effect is using a PSSession to the remote computer. This will allow me to use Get-CimInstance and Invoke-CimMethod on the remote computer without having to create a CIM session.

I am also tweaking the Get-CimInstance command to eke out a little more performance. The process object has many properties. I only need a few of them, so I am going to specify the properties I want using the -Property parameter.

$properties = 'Name','ProcessID','WorkingSetSize','CommandLine','CreationDate','CSName'
$data = Get-CimInstance -ClassName Win32_Process -Filter 'ProcessID>=5' -Property $properties

With this in mind, I can revise the function.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.