Last active
September 1, 2024 17:38
-
-
Save PanosGreg/e772ae0bc171226c8fcd3fed40398591 to your computer and use it in GitHub Desktop.
Get a result like task-manager on the console
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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