Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Last active September 1, 2024 17:38
Show Gist options
  • Save PanosGreg/e772ae0bc171226c8fcd3fed40398591 to your computer and use it in GitHub Desktop.
Save PanosGreg/e772ae0bc171226c8fcd3fed40398591 to your computer and use it in GitHub Desktop.
Get a result like task-manager on the console
function Get-TaskManager {
<#
.SYNOPSIS
Gets the CPU, memory and disk utilization of the top X processes (by default 10)
The command can also query remote computers.
Sorting works based on the processes with the highest CPU utilization.
.DESCRIPTION
Very briefly, these are the steps taken by this command as part of its process workflow:
- Load an internal function, the Get-TaskUsage
- Remote to one or more computers with Invoke-Command and pass in the internal function
as a parameter.
- Run the internal function on the remote computer(s). This collects the windows performance
counter metrics (for the memory, disk and cpu using the Get-Counter command), against
all the running processes on the system.
- Assemble an object with all the data and pass it on as the output of the Invoke-Command.
Do note that this object also color-codes values with high threshold, for easy review.
- Collect the results locally and format the output to fit everything in the console and
also show all the important pieces of information.
.EXAMPLE
Get-TaskManager
Get an object back with information similar to Task Manager
.EXAMPLE
Get-TaskManager -IncludeUserName
As above but also include the username for each process.
The returning object now also includes the Path of the executable as well
.EXAMPLE
Get-TaskManager -IncludeUserName -ComputerName Server1,Server2 -First 5
Run it against remote computers, and set to show only the first 5 processes with the highest CPU utilization
.EXAMPLE
Get-TaskManager -ComputerName Server2 -Sort Mem
Run it against a remote computer and show top processes based on their Memory utilization
.EXAMPLE
$Pass = 'Super_Password' | ConvertTo-SecureString -AsPlainText -Force
$Cred = [pscredential]::new('UserName',$Pass)
$Srv = 'Server1','Server2','Server3' # <-- these are not domain-joined servers
Get-TaskManager -CN $srv -Credential $Cred -First 4 -IncludeUserName
It runs against a number of remote servers that are not joined to a domain,
but have a user with the same username and password.
.NOTES
Author: Panos Grigoriadis
Date: April 2022
#>
[cmdletbinding(DefaultParameterSetName='Computer')]
[OutputType('PS.TaskManager.Local')]
[OutputType('PS.TaskManager.Remote', ParameterSetName='Computer')]
[OutputType('PS.TaskManager.Remote', ParameterSetName='Session')]
[OutputType('PS.TaskManager.Local.WithUser', ParameterSetName='WithUser')]
[OutputType('PS.TaskManager.Remote.WithUser', ParameterSetName=('Computer','WithUser'))]
[OutputType('PS.TaskManager.Remote.WithUser', ParameterSetName=('Session','WithUser'))]
param (
[Parameter(ParameterSetName='Session')]
[Management.Automation.Runspaces.PSSession[]]$Session,
[Alias('CN')]
[Parameter(ParameterSetName='Computer')]
[string[]]$ComputerName = $env:COMPUTERNAME,
[int]$First = 10,
[Parameter(ParameterSetName='WithUser')]
[Parameter(ParameterSetName='Session')]
[Parameter(ParameterSetName='Computer')]
[switch]$IncludeUserName,
[switch]$NoColour,
[ValidateSet('Cpu','Mem','Dsk','Iop')]
[string]$Sort = 'Cpu',
[Parameter(ParameterSetName='Computer')]
[PSCredential]$Credential,
[ValidateScript({$_ -ge 1})]
[int]$Samples = 1
)
Function Set-UsageOutput {
[cmdletbinding()]
[OutputType([void])]
param (
[switch]$IncludeUserName,
[ValidateSet('Local','Remote')]
[string]$Type = 'Local',
[string]$TypeName
)
if ($Type -eq 'Remote' -and $IncludeUserName.IsPresent) {
$Width = '30'
}
else {$Width = '32'}
$format = @"
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>repo</Name>
<ViewSelectedBy>
<TypeName>$TypeName</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
$(if ($TypeName -like '*Remote*') {
'<TableColumnHeader>
<Width>15</Width>
</TableColumnHeader>'
})
<TableColumnHeader>
<Width>8</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>$Width</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>8</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>10</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>12</Width>
</TableColumnHeader>
$(if ($TypeName -like '*WithUser') {
"<TableColumnHeader>
<Width>$Width</Width>
</TableColumnHeader>"
})
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
$(if ($TypeName -like '*Remote*') {
'<TableColumnItem>
<PropertyName>ComputerName</PropertyName>
</TableColumnItem>'
})
<TableColumnItem>
<PropertyName>PID</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>CPU</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Memory</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Disk</PropertyName>
</TableColumnItem>
$(if ($TypeName -like '*WithUser') {
'<TableColumnItem>
<PropertyName>User</PropertyName>
</TableColumnItem>'
})
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
"@
$FileName = '{0}-40d57c.format.ps1xml' -f $TypeName
$xmlpath = Join-Path $env:TEMP $FileName
$format | Out-File $xmlpath -Force
if (Test-Path $xmlpath) {
try {Update-FormatData -AppendPath $xmlpath -ea Stop}
catch {return "Could not update FormatData`n$_"}
}
else {return "Cannot find format file $xmlpath"}
# NOTE: the $filename will be left behind in the system unfortunately
}
function Get-HelperFunctions {
[cmdletbinding()]
[OutputType([System.Collections.Hashtable])]
param (
$FunctionList = @{ # <-- ShortName, FunctionName
GetTask = 'Get-TaskUsage'
}
)
$hash = @{}
$FunctionList.GetEnumerator() | foreach {
$ShortName = $_.Key
$FunctionName = $_.Value
$def = (Get-Item Function:\* | where Name -eq $FunctionName).Definition
if (-not [bool]$def) {Write-Warning "$FunctionName was NOT found";Continue}
$hash.$ShortName = "function $FunctionName {`n$def`n}"
}
Write-Output $hash
}
Function Get-TaskUsage {
<#
.SYNOPSIS
Gets the CPU, memory and disk utilization of the top X processes (by default 10)
.EXAMPLE
Get-TaskUsage
.EXAMPLE
Get-TaskUsage -IncludeUserName
#>
[cmdletbinding()]
param (
[int]$First = 10,
[Parameter(ParameterSetName='WithUser')]
[switch]$IncludeUserName,
[switch]$NoColour,
[ValidateSet('Cpu','Mem','Dsk','Iop')]
[string]$Sort = 'Cpu',
[ValidateScript({$_ -ge 1})]
[int]$Samples = 1
)
$Counters = ( # <-- to get all the perf counter names: (Get-Counter -ListSet Process).Paths
'\Process(*)\% Processor Time',
'\Process(*)\Working Set - Private',
'\Process(*)\ID Process',
'\Process(*)\IO Data Bytes/sec',
'\Process(*)\IO Data Operations/sec'
)
if ($IncludeUserName.IsPresent) {
$CurrentId = [Security.Principal.WindowsIdentity]::GetCurrent()
$AdminRole = [Security.Principal.WindowsBuiltinRole]::Administrator
$IsAdmin = [Security.Principal.WindowsPrincipal]::new($CurrentId).IsInRole($AdminRole)
if (-not $IsAdmin) {throw 'The "IncludeUserName" parameter requires elevation, please run as admin.'}
$Procs = Get-Process -IncludeUserName -ea Ignore | select Id,UserName,Path # <-- this may slow down runtime by up to ~7 seconds
}
$Perf = Get-Counter -Counter $Counters -ea Ignore -MaxSamples $Samples # <-- this takes ~2 seconds for 1 sample, add 1-1.2 sec for each extra sample
$List = $Perf.CounterSamples
$All = foreach ($cnt in $Counters) {
switch ($cnt) {
{$_ -like '*Processor Time'} {$KeyName = 'Cpu';break}
{$_ -like '*Set - Private'} {$KeyName = 'Mem';break}
{$_ -like '*ID Process'} {$KeyName = 'PID';break}
{$_ -like '*Data Bytes/sec'} {$KeyName = 'Dsk';break}
{$_ -like '*Operations/sec'} {$KeyName = 'Iop';break}
}
$Part = $cnt.Split('\')[-1].Replace('\','\\').Replace('/','\/').Replace('%','\%').ToLower()
$RegEx = '\\process\((.*?)\)\\' + $Part
$List.ForEach({if ($_.Path -Match $RegEx) {
[PSCustomObject] @{
Name = $Matches[1]
$KeyName = $_.CookedValue
}
}}) # if path matches and foreach match
} # foreach counter
$All = $All | Group-Object Name | foreach {
$grp = $_.Group
$id = $grp.Where({$_.PID},'First',1).PID -as [int]
$Cpu = $grp.Where({$null -ne $_.Cpu}).Cpu
$Cpu = [Linq.Enumerable]::Average([double[]]$Cpu)
$Mem = $grp.Where({$null -ne $_.Mem}).Mem
$Mem = [Linq.Enumerable]::Average([double[]]$Mem) # <-- this could also be "NaN"
$Dsk = $grp.Where({$null -ne $_.Dsk}).Dsk
$Dsk = [Linq.Enumerable]::Average([double[]]$Dsk) -as [uint64]
$Iop = $grp.Where({$null -ne $_.Iop}).Iop
$Iop = [Linq.Enumerable]::Average([double[]]$Iop)
[PSCustomObject] @{
Name = $_.Name
PID = $id
Cpu = $Cpu
Mem = $Mem
Dsk = $Dsk
Iop = $Iop
}
}
$Filter = {$_.Name -ne '_total' -and $_.Name -ne 'idle' -and $_.Name -ne 'memory compression'} # <-- you may want to remove "memory compression" from the filter if you want it to be shown
$Some = $All | Sort-Object -Desc $Sort | where $Filter | select -First $First
$CpuCores = [Environment]::ProcessorCount # alt. (Get-WMIObject Win32_ComputerSystem).NumberOfLogicalProcessors
$SizeType = ('b', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB')
if (-not $NoColour.IsPresent) {
$Wht = "$([char]27)[0m" # white
$Gra = "$([char]27)[37m" # gray
$Grn = "$([char]27)[92m" # green
$Yel = "$([char]27)[93m" # yellow
$Mag = "$([char]27)[95m" # magenta
$Red = "$([char]27)[91m" # red
$DefColour = "$([char]27)[0m"
$TotalMem = (Get-CimInstance -ClassName 'CIM_ComputerSystem').TotalPhysicalMemory
$WmiQuery = 'Select Name,Speed from MSFT_NetAdapter WHERE InterfaceOperationalStatus = 1 AND MediaConnectState = 1'
$NetAdapter = Get-CimInstance -Namespace 'root\StandardCimv2' -Query $WmiQuery
$NicSpeed = $NetAdapter[0].Speed/8 # in Bytes/sec not Bits
}
foreach ($Record in $Some) {
$CpuVal = [Decimal]::Round(($Record.Cpu / $CpuCores),2)
$IopVal = [Math]::Round($Record.Iop,0) -as [int]
$MemOrd = [Math]::Floor( [Math]::Log($Record.Mem, 1000) )
$MemRnd = [Math]::Round($Record.Mem/([Math]::Pow(1000, $MemOrd)),1)
$MemVal = [String]($MemRnd) + $SizeType[$MemOrd]
if ($Record.Dsk -eq 0) {$DskVal = $Record.Dsk}
else {
$DskOrd = [Math]::Floor( [Math]::Log($Record.Dsk, 1000) )
$DskRnd = [Math]::Round($Record.Dsk/([Math]::Pow(1000, $DskOrd)),1)
$DskVal = '{0}{1}/s' -f [String]($DskRnd),$SizeType[$DskOrd]
}
if (-not $NoColour.IsPresent) {
if ($Record.Mem -ne 'NaN') {
switch ($Record.Mem/$TotalMem) {
{$_ -lt 0.05} {$MemCol = $Wht ; break}
{$_ -ge 0.05 -and $_ -lt 0.5} {$MemCol = $Grn ; break}
{$_ -ge 0.5 -and $_ -lt 0.75} {$MemCol = $Yel ; break}
{$_ -ge 0.75} {$MemCol = $Red ; break}
}
}
if ($Record.Mem -eq 'NaN') {$MemCol = $Gra}
$MemUsage = '{0}{1}{2}' -f $MemCol,$MemVal,$DefColour
switch ($CpuVal) {
{$_ -lt 5} {$CpuCol = $Wht ; break}
{$_ -ge 5 -and $_ -lt 10} {$CpuCol = $Gra ; break}
{$_ -ge 10 -and $_ -lt 25} {$CpuCol = $Grn ; break}
{$_ -ge 25 -and $_ -lt 50} {$CpuCol = $Yel ; break}
{$_ -ge 50 -and $_ -lt 70} {$CpuCol = $Mag ; break}
{$_ -ge 70} {$CpuCol = $Red ; break}
{$_ -eq 'NaN'} {$CpuCol = $Gra ; break}
}
$CpuUsage = '{0}{1}{2}' -f $CpuCol,$CpuVal,$DefColour
if ($Record.Dsk -ne 'NaN') {
switch ($Record.Dsk/$NicSpeed) {
{$_ -lt 0.1} {$DskCol = $Wht ; break}
{$_ -ge 0.1 -and $_ -lt 0.2} {$DskCol = $Grn ; break}
{$_ -ge 0.2 -and $_ -lt 0.4} {$DskCol = $Yel ; break}
{$_ -ge 0.4} {$DskCol = $Mag ; break}
}
}
if ($Record.Dsk -eq 'NaN') {$DskCol = $Gra}
$DskUsage = '{0}{1}{2}' -f $DskCol,$DskVal,$DefColour
}
else {
$MemUsage = $MemVal
$CpuUsage = $CpuVal
$DskUsage = $DskVal
}
$hash = [ordered] @{
TimeStamp = $Perf.Timestamp[0] # <-- with many samples you get many stamps, hence why I show only the 1st one
ComputerName = $env:COMPUTERNAME
PID = $Record.PID
Name = $Record.Name
CPU = $CpuUsage
Memory = $MemUsage
Disk = $DskUsage
Iops = $IopVal
DiskBytes = $Record.Dsk
MemBytes = $Record.Mem
CpuUtil = $CpuVal
}
if ($IncludeUserName.IsPresent) {
$Proc = $Procs | where Id -eq $Record.PID
$hash['User'] = $Proc.UserName
$hash['Path'] = $Proc.Path
}
[PSCustomObject]$hash
} #foreach process
}
$TaskParam = @{
First = $First
IncludeUserName = $IncludeUserName.IsPresent
NoColour = $NoColour.IsPresent
Sort = $Sort
Samples = $Samples
}
$HasComp = $PSBoundParameters.ContainsKey('ComputerName')
$HasSess = $PSBoundParameters.ContainsKey('Session')
if ($HasComp -or $HasSess) {
$function = Get-HelperFunctions
$GetTask = {
param($f,$TaskParam)
$f.GetEnumerator() | foreach {Invoke-Expression -Command $_.Value}
Get-TaskUsage @TaskParam
}
$IcmParams = @{
Scriptblock = $GetTask
ArgumentList = $function,$TaskParam
}
if ($HasSess) {$IcmParams['Session'] = $Session}
if ($HasComp) {$IcmParams['ComputerName'] = $ComputerName}
if ([bool]$Credential) {$IcmParams['Credential'] = $Credential}
$results = Invoke-Command @IcmParams
$results = $results | select * -ExcludeProperty PSComputerName,RunspaceId,PSSourceJobInstanceId
}
else {$results = Get-TaskUsage @TaskParam}
switch ($true) {
{($HasSess -or $HasComp) -and -not $IncludeUserName.IsPresent} {
$TypeName = 'PS.TaskManager.Remote'
$OutParams = @{Type = 'Remote'}}
{($HasSess -or $HasComp) -and $IncludeUserName.IsPresent} {
$TypeName = 'PS.TaskManager.Remote.WithUser'
$OutParams = @{Type = 'Remote' ; IncludeUserName = $true}}
{(-not $HasSess -and -not $HasComp) -and -not $IncludeUserName.IsPresent} {
$TypeName = 'PS.TaskManager.Local'
$OutParams = @{Type = 'Local'}}
{(-not $HasSess -and -not $HasComp) -and $IncludeUserName.IsPresent} {
$TypeName = 'PS.TaskManager.Local.WithUser'
$OutParams = @{Type = 'Local' ; IncludeUserName = $true}}
}
$FormatIsSet = [bool](Get-FormatData -TypeName $TypeName)
if (-not $FormatIsSet) {Set-UsageOutput @OutParams -TypeName $TypeName}
$out = foreach ($r in $results) {
$r.pstypenames.Clear()
$r.pstypenames.Add($TypeName)
$r
}
Write-Output $out
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment