
close
close
Chance to win $250 in Petri 2023 Audience Survey
If you’re like me, you run a lot of applications on your desktop. If you’re also like me, you let applications run for days at a time. There are consequences to letting applications run indefinitely, as some apps like to chip away at memory. Browsers are a perfect example of this, where I have to remind myself to restart my browser every few days because before I realize it, the application could be using almost a 1GB of memory. But this article isn’t about picking the best browser, it’s about how I decided to use PowerShell to keep track of applications using a lot of memory. To do this, I’m going to create a WMI subscriber using the CIM cmdlets. I used the same ideas to create my battery watcher tool.
A WMI subscriber is a special type of query that’s semi-persistent. You can configure the query to check for changes to the system every X number of seconds. This is referred to as the polling interval.
In this example, I’m going to check my computer every two minutes.
$Poll = 120
The query is a bit different than what you would normally see with WMI.
$query = "Select * from CIM_InstModification within $Poll where TargetInstance ISA 'Win32_Process' AND TargetInstance.WorkingSetSize>=$(500MB)"
Let’s break it down. I want to select all properties from the WMI class called CIM_InstModification. This is a special system class that’s triggered when an object is modified. I don’t want to find every object, as it is changed every second. So I tell WMI to check within a time frame of seconds. When this event fires, WMI will have a new object called the TargetInstance. This will be the changed object, and I only care about those that are Win32_Process objects.The ISA operator accomplishes that.
The last part of the query is to limit results to those process objects, which is what the TargetInstance is, with a WorkingSetSize property of greater or equal to 500 MB. I’m using a subexpression,$(500MB), so that PowerShell will expand the expression and plug in the value. Otherwise, I would have to know that 500 MB is 524288000 bytes. I’m using this value for the sake of my demonstration. In practice, I’ll bump this to 1 GB.
When you create the event subscriber, you can choose to simply record the events in your PowerShell session as matching events are detected. Or you can take action. In my case, I want to do a few things everytime a matching process is found. I want to create a log file, and I want to display a popup message.
$action={ #create a log file $logPath= "C:\Work\HighMemLog.txt" "[$(Get-Date)] Computername = $($Event.SourceEventArgs.NewEvent.SourceInstance.CSName)" | Out-File -FilePath $logPath -Append -Encoding ascii "[$(Get-Date)] Process = $($Event.SourceEventArgs.NewEvent.SourceInstance.Name)" | Out-File -FilePath $logPath -Append -Encoding ascii "[$(Get-Date)] Command = $($Event.SourceEventArgs.NewEvent.SourceInstance.Commandline)" | Out-File -FilePath $logPath -Append -Encoding ascii "[$(Get-Date)] PID = $($Event.SourceEventArgs.NewEvent.SourceInstance.ProcessID)" | Out-File -FilePath $logPath -Append -Encoding ascii "[$(Get-Date)] WS(MB) = $([math]::Round($Event.SourceEventArgs.NewEvent.SourceInstance.WorkingSetSize/1MB,2))" | Out-File -FilePath $logPath -Append -Encoding ascii "[$(Get-Date)] $('*' * 60)" | Out-File -FilePath $logPath -Append -Encoding ascii #create a popup $wsh = New-Object -ComObject Wscript.shell $Title = "$(Get-Date) High Memory Alert" $msg = @" Process = $($Event.SourceEventArgs.NewEvent.SourceInstance.Name) PID = $($Event.SourceEventArgs.NewEvent.SourceInstance.ProcessID) WS(MB) = $([math]::Round($Event.SourceEventArgs.NewEvent.SourceInstance.WorkingSetSize/1MB,2)) "@ #timeout in seconds. Use -1 to require a user to click OK. $Timeout = 10 $wsh.Popup($msg,$TimeOut,$Title,16+32) }
When the event occurs, I want this scriptblock to execute. The first part creates a log file. When the event fires, you will automatically get a few objects, which will represent the matching object.
The $Event.SourceEventArgs.NewEvent.SourceInstance will be the WMI Win32_Process object. As you can see, I’m selecting a few properties and writing the result to my log file. I’m including a time stamp and a string of 60 characters at the end to separate the entries.
Viewing the triggered event log file (Image Credit: Jeff Hicks)
A pop-up alert (Image Credit: Jeff Hicks)
Register-CimIndicationEvent -Query $query -SourceIdentifier "HighProcessMemory" -Action $action
You can see the registration with the Get-EventSubscriber cmdlet.
Using PowerShell’s Get-EventSubscriber cmdlet (Image Credit: Jeff Hicks)
Get-EventSubscriber -SourceIdentifier "HighProcessMemory" | Unregister-Event
One final note, I’m running this locally, but you can just as easily create event subscriptions for things happening on remote machines. You might want to watch process utilization on a critical server. If there’s something you think you could use these techniques would be helpful, let me know, and it might become a future article.
More in PowerShell
Most popular on petri