Hello folks,
I have this script that Im pretty happy with, but would like to make 2 changes, neither of which work when i try them.
I would like to be able to run the script passing in multiple computers on the command line, like so:
Get-MKProcesses -computer computer1, computer2, etc
As it is I can run it with various other params like these:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer -number 2
Get-MKProcesses -number 6 -computer c:\temp\inputfiles\servers.txt
and they all work great. When I try and tweak for the comma separated list, I end up breaking one of those other input methods.
The other thing was , my starttime column shows multiple entries for some, anyway to only have 1 start time per process?
Any help would be great, thanks!
Here is the script:
<#
Get-MKProcesses
function to get X number of top processes by cpu%
usage:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer -number 2
Get-MKProcesses -number 6 -computer c:\temp\inputfiles\servers.txt
#>
function Get-MKProcesses {
param (
$computer = $env:COMPUTERNAME,
$number = 5
)
# Check if $computer is a file path
if (Test-Path $computer -ErrorAction SilentlyContinue) {
$computerNames = Get-Content $computer
}
else {
$computerNames = @($computer)
}
foreach ($computerName in $computerNames) {
$scriptblock = {
param($localNumber)
$i = 0
# Get the top processes by CPU usage
$topProcesses = Get-Process -IncludeUserName | Sort-Object CPU -Descending | Select-Object -First $localNumber
# Get the total CPU usage
$totalCPU = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue
# Get the hostname
$hostname = $env:COMPUTERNAME
# Output information for each process as PSCustomObject
$processInfo = foreach ($process in $topProcesses) {
$i++
$processuser = $process.username
$processName = $process.ProcessName
$processid = $process.Id
$processcputime = $process.cpu
$cpuCounter = (Get-Counter "\Process($($processName))\% Processor Time").CounterSamples.CookedValue
$cpuUsage = "{0:N2}%" -f $cpuCounter
$path = if ($process.Path) { $process.Path } else { "N/A" }
$memoryUsage = "{0:N2} MB" -f ($process.WorkingSet / 1MB)
$processInfoInstance = Get-CimInstance -ClassName Win32_Process -Filter "Name='$processName.exe'"
$startTime = $processInfoInstance.CreationDate
[PSCustomObject]@{
Number = $i
ProcessName = $processName
Path = $path
PID = $processid
CPUUsage = $cpuUsage
MemoryUsage = $memoryUsage
ProcessCPUTime = $processcputime
ProcessUser = $processuser
Hostname = $hostname
StartTime = $startTime
}
}
# Output the process information
$processInfo | Format-Table -AutoSize
Write-Host "Total CPU usage $totalCPU`r`n" -NoNewLine -ForegroundColor Yellow
Write-Host "ProcessCPUTime shows CPU time consumed by the process in seconds" -ForegroundColor Red
}
if ($computerName -eq $env:COMPUTERNAME) {
# Run the script block locally
. $scriptblock -localNumber $number
}
else {
# Run the script block on a remote computer
Invoke-Command -ComputerName $computerName -ScriptBlock $scriptblock -ArgumentList $number
}
}
}
The paragraph about static parameters already shows how to make a parameter accepting arrays of string values.
In my opinion that’s a very bad approach since it would be unexpected for a parameter named -Computer to accept a file path. And I would use the parameter name ComputerName instead as this is very common. You could use -Computer as an alias if you like or need for backwards compatibility.
If you have multiple processes you also have multiple start times … that’s logical, isn’t it?
You will have to come up with an idea how you like to handle multiple processes with the same name. Especially for browsers that’s a very common thing to launch multiple processes even when you just started them and didn’t even open more than one tab.
Thanks Olaf. I didnt really think about the -computer taking a path not making sense lol, and turns out changing that bit solves my problem with the string input.
<#
Get-MKProcesses
function to get X number of top processes by cpu%
usage:
Get-MKProcesses (for local computer) -number 10
Get-MKProcesses -computer remotecomputer1,remotecomputer2 -number 2
Get-MKProcesses -number 6 -computer c:\temp\inputfiles\servers.txt
#>
function Get-MKProcesses {
param (
[string[]]$computer = $env:COMPUTERNAME,
$number = 5,
$path
)
# Check if $computer is a file path
if (Test-Path $path -ErrorAction SilentlyContinue) {
$computernames = Get-Content $path
}
else {
$computernames = @($computer)
}
foreach ($computername in $computernames) {
$scriptblock = {
param($localNumber)
$i = 0
# Get the top processes by CPU usage
$topProcesses = Get-Process -IncludeUserName | Sort-Object CPU -Descending | Select-Object -First $localNumber
# Get the total CPU usage
$totalCPU = (Get-Counter '\Processor(_Total)\% Processor Time').CounterSamples.CookedValue
# Get the hostname
$hostname = $env:COMPUTERNAME
# Output information for each process as PSCustomObject
$processInfo = foreach ($process in $topProcesses) {
$processInfoInstance = Get-CimInstance -ClassName Win32_Process -Filter "Name='$processName.exe'"
$startTime = $processInfoInstance.CreationDate
$i++
$processuser = $process.username
$processName = $process.ProcessName
$numProcesses = (Get-Process -Name $processName).Count
$processid = $process.Id
$processcputime = $process.cpu
$cpuCounter = (Get-Counter "\Process($($processName))\% Processor Time").CounterSamples.CookedValue
$cpuUsage = "{0:N2}%" -f $cpuCounter
$path = if ($process.Path) { $process.Path } else { "N/A" }
$memoryUsage = "{0:N2} MB" -f ($process.WorkingSet / 1MB)
[PSCustomObject]@{
Number = $i
ProcessName = $processName
Path = $path
PID = $processid
CPUUsage = $cpuUsage
MemoryUsage = $memoryUsage
ProcessCPUTime = $processcputime
ProcessUser = $processuser
Processes = $numProcesses
StartTime = $startTime
}
}
# Output the process information
$processInfo | Format-Table -AutoSize
Write-Host "Total CPU usage $totalCPU`r`n" -NoNewLine -ForegroundColor Yellow
Write-Host "ProcessCPUTime shows CPU time consumed by the process in seconds" -ForegroundColor Red
}
if ($computername -eq $env:COMPUTERNAME) {
# Run the script block locally
. $scriptblock -localNumber $number
}
else {
# Run the script block on a remote computer
Invoke-Command -ComputerName $computername -ScriptBlock $scriptblock -ArgumentList $number
}
}
}
# Check if both $computername and $filepath are provided
if ($computername -and $filepath) {
Write-Error "Please choose either a remote computer or a file as input, not both."
return
}
To be honest - since you already have a way to prvide a list of computers to the function I would drop the filepath parameter completely. Instead I would make the parameter -ComputerName “pipeline aware” using ValueFromPipeline or even ValueFromPipelineByPropertyName. This way you could easily do something like this: