I need to watch when certain processes are started or stopped on a Windows machine. I'm currently tapped into the WMI system and querying it every 5 seconds, but this causes a CPU spike every 5 seconds because WMI is WMI. Is there a better way of doing this? I could just make a list of running processes and attach an Exited event to them through the System.Diagnostics Namespace, but there is no Event Handler for creation.
This is not exactly how you'd do it in the real world but should help. This seems not to drive my CPU much at all.
static void Main(string[] args)
{
// Getting all instances of notepad
// (this is only done once here so start up some notepad instances first)
// you may want use GetProcessByPid or GetProcesses and filter them as required
Process[] processesToWatch = Process.GetProcessesByName("notepad");
foreach (var process in processesToWatch)
{
process.EnableRaisingEvents = true;
process.Exited +=
(s, e) => Console.WriteLine("An instance of notepad exited");
}
Thread watchThread = new Thread(() =>
{
while (true)
{
Process[] processes = Process.GetProcesses();
foreach (var process in processes)
{
Console.WriteLine("{0}:{1}", process.Id, process.ProcessName);
}
// Don't dedicate a thread to this like I'm doing here
// setup a timer or something similiar
Thread.Sleep(2000);
}
});
watchThread.IsBackground = true;
watchThread.Start();
Console.WriteLine("Polling processes and waiting for notepad process exit events");
Console.ReadLine();
}
I've had CPU spikes when listening to WMI events in cases where I have failed to detach properly from my events on exit/cleanup. You might want to check you are not "leaking" WMI event subscriptions. Just in case detach from the event as early as possible and make sure you always do it.
To illustrate further, here's an example from my PowerShell book that listens to WMI events using the PSEventing library:
Add-PSSnapin PSEventing -ErrorAction SilentlyContinue
$queryString = @' SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32_Service' AND TargetInstance.Name = 'w3svc' AND TargetInstance.State = 'Stopped' '@
$query = New-Object System.Management.WQLEventQuery ` -argumentList $queryString
$watcher = New-Object System.Management.ManagementEventWatcher($query)
Connect-EventListener watcher EventArrived
$watcher.Start()
echo "Waiting for the W3CSVC service to stop..." Get-Event -wait |
foreach {
Write-Host -foreground Red "The W3SVC service has stopped!" }$watcher.Stop()
Disconnect-EventListener watcher EventArrived
echo "done"
If I do not do the Disconnect-EventListener bit upon script exit, I get CPU spikes the third or fourth time I attach to the event. My guess is that the system still tries to deliver events.
If you are only looking for PID/Name of your processes, you may instead wish to pick up on Win32_ProcessTrace events, using a WQL query such as "SELECT * FROM Win32_ProcessTrace WHERE TargetInstance.ProcessName = 'name'" if applicable*.
The pitfall of using "SELECT * FROM __InstanceModificationEvent WITHIN 10 WHERE TargetInstance ISA 'Win32Process' AND TargetInstance.Name = 'name'" is in how it works on the back end. If you inspect wbemess.log within your %windir%\system32\wbem\logs directory, you will notice the following logs (using __InstanceDeletionEvent):
(Wed Jul 22 13:58:31 2009.73889577) : Registering notification sink with query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' in namespace //./root/CIMV2. (Wed Jul 22 13:58:31 2009.73889577) : Activating filter 047209E0 with query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' in namespace //./root/CIMV2. (Wed Jul 22 13:58:31 2009.73889577) : Activating filter 0225E560 with query select * from __ClassOperationEvent where TargetClass isa "Win32_Process" in namespace //./root/CIMV2. (Wed Jul 22 13:58:31 2009.73889577) : Activating filter 'select * from __ClassOperationEvent where TargetClass isa "Win32_Process"' with provider $Core (Wed Jul 22 13:58:31 2009.73889587) : Activating filter 'select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process'' with provider $Core (Wed Jul 22 13:58:31 2009.73889587) : Instituting polling query select * from Win32_Process to satisfy event query select * from __InstanceDeletionEvent within 10 where TargetInstance ISA 'Win32_Process' (Wed Jul 22 13:58:31 2009.73889587) : Executing polling query 'select * from Win32_Process' in namespace '//./root/CIMV2' (Wed Jul 22 13:58:31 2009.73889697) : Polling query 'select * from Win32_Process' done (Wed Jul 22 13:58:41 2009.73899702) : Executing polling query 'select * from Win32_Process' in namespace '//./root/CIMV2' (Wed Jul 22 13:58:41 2009.73899792) : Polling query 'select * from Win32_Process' done
As you can see, the actual event implementation on the remote machine is to perform a query against Win32_Process on an interval that is specified by your value in the WITHIN clause. As a result, any processes that start and stop within that poll will never fire an event.
You can set the WITHIN clause to a small value to try and minimize this effect, but the better solution is to use a true event like Win32_ProcessTrace, which should always fire.
*Note that MSDN indicates Win32_ProcessTrace requires a minimum of Windows XP on a client machine and Windows 2003 on a server machine to work. If you are working with an older OS, you may be stuck using the __InstanceModificationEvent query.