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.

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.