Managing Child Processes

Today in my training, a student asked how to manage child processes. That's not a bad question at all, and as it turns out, it's a great way to show off the capabilities of PowerShell.

So why would you care about child processes in the first place? Well, for example to monitor and keep track of what installation packages are doing.

When you launch an application from powershell, like setup.exe for example, you can get back its process id and monitor it or kill it if it takes too long - that's cool. But what if this process launches additional processes in the background? How can you get a hold of all of these processes?

Launching A Process And Keeping Control

Let's start simple and try and launch a process from PowerShell. The objective is to get back its process id, so you can continue to check and monitor it. Start-Process can start processes for you, and with a little-known parameter called -PassThru, it will even pass the process object back to you. This way, you can store it in a variable and use it later to retrieve the process id, read its CPU consumption or kill it:

PS> $process = Start-Process -FilePath notepad -PassThru
PS> $process.CPU
0,0156001
PS> $process.ID
3992
PS> $process.hasExited
False
PS> $process.Kill()
PS> $process.hasExited
True

As you see, Start-Process delivered back an object that was able to read real-time statistics from the live process and was even able to kill it at your convenience.

Finding Child Processes

To find all the processes a given process has started, Get-Process is of no use because it does not return the parent process id. Fortunately, WMI can help. Its class Win32_Process does include the information. So here is a small function called Find-ChildProcess. It accepts a process id and returns all the child processes that were spawned from the parent process:

function Find-ChildProcess {
param($ID=$PID)


Get-WmiObject -Class Win32_Process -Filter "ParentProcessID=$ID" |
Select-Object -Property ProcessName, ProcessID, CommandLine
}

By default, Find-ChildProcess uses the process ID of your current PowerShell host. So if you run Find-ChildProcess without specifying a process ID, you get back all the processes that you launched from within your powershell session. Try it and launch a couple of windowed applications such as notepad! Then, call Find-ChildProcess. You get back all the child processes. Cool!

Making It Recursive

Of course, a child process can launch another process as well, so there can be a literal process tree. To get a hold of these child-child-processes, what we need as a final touch is recursion. Since we already have a function that gets child processes, all you need to do is call it again for all child processes to find the processes of a child process.

The final solution also renames and changes the column (property) ProcessID. It renames it to 'ID' and changes the type to an integer array. Why? Because Stop-Process binds to that property via pipeline. So by fitting the column to the needs of Stop-Process, you can then pipe the results of Find-ChildProcess directly to Stop-Process in case you need to stop and kill the whole enchilada (you can download this function here: http://powershell.com/cs/media/p/16455.aspx):

function Find-ChildProcess {
param($ID=$PID)

$CustomColumnID = @{
Name = 'Id'
Expression = { [Int[]]$_.ProcessID }
}

$result = Get-WmiObject -Class Win32_Process -Filter "ParentProcessID=$ID" |
Select-Object -Property ProcessName, $CustomColumnID, CommandLine

$result
$result
| Where-Object { $_.ID -ne $null } | ForEach-Object {
Find-ChildProcess -id $_.Id
}
}

NOTE: I added the Where-Object in black and bold above after it occured to me that some process do not have parent process information and this could lead to unwanted WMI "query invalid" exceptions. Now all is fine.

To test drive this, in your powershell enter Start-Process powershell. This opens a new powershell in a new window - a child process. Inside the new powershell window, enter Start-Process cmd. This opens yet another nested child process, a cmd shell. Inside of it, enter notepad to launch an instance of notepad.

Then return to your initial powershell and display the child items:

PS> Find-ChildProcess

ProcessName Id CommandLine
----------- -- -----------
notepad.exe 3180 "C:\Windows\system32\notepad.exe"
powershell.exe 6112 "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
cmd.exe 5728 "C:\Windows\system32\cmd.exe"
notepad.exe 5636 notepad

As you see, all child processes - even the nested ones - are reported.

And since there is a fitting column ID with the proper type, you can then pipe the results on to Stop-Process to kill the entire process tree:

PS> Find-ChildProcess | Stop-Process

So Find-ChildProcess effectively became a new member of the PowerShell command family, playing by the rules. You can combine it with other related cmdlets. Great question! And a cool solution that illustrates the power of PowerShell and its extensibility.

Stay tuned...!

Tobias

Microsoft MVP PowerShell Germany

P.S.
If you live in Germany or other parts of Europe and your company would like to set up a truly great PowerShell training, just contact me! I regularly train mid- to large-size companies. Trainings are always a blast with tons of real-world-examples and solutions. Here's how to get in touch with me: tobias.weltner@scriptinternals.de


Posted May 09 2012, 05:09 AM by Tobias
Copyright 2012 PowerShell.com. All rights reserved.