Reputation: 43
I want to stop processes that are running higher than 14% CPU usage.
$process = get-process
foreach ($proc in (Get-WmiObject Win32_Processor)){
if($proc.numberofcores -eq $null){
$cores++
}else{
$cores = $cores + $proc.numberofcores
}
}
foreach($name in $process){
$processName = $name.processName
foreach($hog in $processName){
$cpuusage = [Math]::round(((((Get-Counter "\Process($processName)\%
Processor Time" -MaxSamples 2).Countersamples)[0].CookedValue)/$cores),2)
if($cpuusage -gt 14){
Stop-Process -Name $processName
}
}
}
I am getting the following as an error, an nothing else. I expect the Idle(0)
not to work, but nothing else is being killed.
Stop-Process : Cannot stop process "Idle (0)" because of the following error: Access is denied At line:14 char:17 + Stop-Process -Name $processName + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : CloseError: (System.Diagnostics.Process (Idle):Process) [Stop-Process], ProcessCommandException + FullyQualifiedErrorId : CouldNotStopProcess,Microsoft.PowerShell.Commands.StopProcessCommand
I have tried to replace the $processName
variables within the second foreach loop to $hog
and I still get the same error.
After reading @JosefZ answer I got something that satisfies what I require for my class. Posting it here for reference;
$process = get-process
foreach ($pro in $process){
$name = $pro.ProcessName
$CpuCores = (Get-WmiObject -Class Win32_Processor).NumberOfCores
$CpuValue = ((Get-Counter "\Process($name)\% Processor Time").CounterSamples.CookedValue)/$CpuCores
$percent = [Decimal]::Round($CpuValue, 3)
if($percent -ge 15){
Stop-Process -Name $name
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Process $name was using more than $percent % CPU. We have eliminated it.",0,"Ok",0x1)
}
}
Upvotes: 4
Views: 9954
Reputation: 912
Couple of additional pointers for you. Firstly you should use Win32_ComputerSystem
and NumberOfLogicalProcessors
instead of Win32_Processor
with NumberOfCores
. The reason being, that the performance counters account for HyperThreading on systems that have it, so your calculation based off the physical cores will give you a processor time value twice the size of what it should be. For example, on my machine with 6 Physical Cores, I have 12 logical processors due to HyperThreading. Using your original calculation, a process using 8% CPU would be reported as 16% and thus be incorrectly stopped. The Win32_ComputerSystem
version will return all logical processors across all physical CPU's, so multi socketed servers would also be calculated correctly.
C:\WINDOWS\system32> (Get-WmiObject win32_processor).NumberofCores
6
C:\WINDOWS\system32> (Get-WmiObject win32_computersystem).Numberoflogicalprocessors
12
Second, you should be stopping the process by it's ID not by Name, as this will have unintended consequences. A simple example is Chrome, which has a process per tab, all of which have the Name Chrome. So a single tab could be having issues causing high CPU, but your script calling Stop-Process -Name Chrome
would close all instances of Chrome, including the ones that aren't doing anything.
The following example script resolves both of these issues:
#Get all cores, which includes virtual cores from hyperthreading
$cores = (Get-WmiObject Win32_ComputerSystem).NumberOfLogicalProcessors
#Get all process with there ID's, excluding processes you can't stop.
$processes = ((Get-Counter "\Process(*)\ID Process").CounterSamples).where({$_.InstanceName -notin "idle","_total","system"})
#Get cpu time for all processes
$cputime = $processes.Path.Replace("id process", "% Processor Time") | get-counter | select -ExpandProperty CounterSamples
#Get the processes with above 14% utilisation.
$highUsage = $cputime.where({[Math]::round($_.CookedValue / $cores,2) -gt 14})
# For each high usage process, grab it's process ID from the processes list, by matching on the relevant part of the path
$highUsage |%{
$path = $_.Path
$id = $processes.where({$_.Path -like "*$($path.Split('(')[1].Split(')')[0])*"}) | select -ExpandProperty CookedValue
Stop-Process -Id $id -Force -ErrorAction SilentlyContinue
}
Note that the usage of the .where(
syntax requires PowerShell 5 or above. This script also has the added bonus of being much faster than calling Get-Counter
in a foreach loop.
Upvotes: 2
Reputation: 30113
Note that Performance counters are often protected by access control lists (ACLs). To get all available performance counters, open Windows PowerShell with the "Run as administrator" option and Your ability to stop processes depends on your permissions.
The following script was used for debugging with CPU-time consuming wmic path cim_datafile
and antivirus full scan:
Set-StrictMode -Version latest
$process = get-process
$cores = 0
foreach ($proc in (Get-WmiObject Win32_Processor)){
if($proc.numberofcores -eq $null){
$cores++
}else{
$cores = $cores + $proc.numberofcores
}
}
### $cores
foreach($name in $process){
$processName = $name.processName
if ( $processName -notmatch "^Idle" ) {
foreach($hog in $processName){
$cpuusage = -1
$cpuusage = [Math]::round(((((Get-Counter "\Process($processName)\% Processor Time" -MaxSamples 2).Countersamples)[0].CookedValue)/$cores),2)
if ($cpuusage -gt 14) {
Stop-Process -Name $processName -PassThru -ErrorAction Continue
### "{0} {1} {2} {3}" -f '+', $cpuusage, $name.Id, $processName
} else {
if($cpuusage -ne 0){
### "{0} {1} {2} {3}" -f '-', $cpuusage, $name.Id, $processName
}
}
}
}
}
Dirty solution: Stop-Process -Name $processName -ErrorAction SilentlyContinue -PassThru
"Idle" means "inactive" (not operating or being used). When the "System Idle Process" is at 100 %
, that means nothing is using your CPU resources.
Read Stopping Processes (Stop-Process
) at MSDN:
Windows PowerShell gives you flexibility for listing processes, but what about stopping a process?
The
Stop-Process
cmdlet takes aName
orId
to specify a process you want to stop. Your ability to stop processes depends on your permissions. Some processes cannot be stopped. For example, if you try to stop the idle process, you get an error:PS> Stop-Process -Name Idle Stop-Process : Process 'Idle (0)' cannot be stopped due to the following error: Access is denied At line:1 char:13 + Stop-Process <<<< -Name Idle
Read Get-Help 'Stop-Process' -Online
Outputs
None,
System.Diagnostics.Process
This cmdlet returns a
System.Diagnostics.Process
object that represents the stopped process, if you specify thePassThru
parameter. Otherwise, this cmdlet does not generate any output.
Upvotes: 3