96

I am looking for a PowerShell cmdlet that can provide similar functionality to the Linux Top app. Something that refreshes at some given interval and displays the process list with CPU % util.

I have seen scripts that list CPU % utilization in a loop but something like top would be way more handy as we have setup SSH/Powershell access for management (I still prefer a putty shell!)

4
  • This falls squarely in the superuser.com category of questions.
    – Gabe
    Aug 15, 2010 at 1:26
  • Cool -didnt realize that site even existed! (I am primarily a C# developer)
    – TimAtVenturality
    Aug 15, 2010 at 2:37
  • 3
    The CPU property on the Process Object is not CPU percentage it is CPU time total since process start.
    – user213525
    Apr 2, 2013 at 23:07
  • Alternatively just use the Task Manager on Windows 10, which can accessed via ctrl + alt + del as a shortcut.
    – Kris Stern
    Sep 16, 2021 at 6:38

18 Answers 18

53

This is a simple one-liner that will also keep the labels at the top:

While(1) {ps | sort -des cpu | select -f 15 | ft -a; sleep 1; cls}

This works because formatting the table without any parameters just drawls the default table. autosize is used to automatically adjust the column width so all the data can fit on screen.


Breakdown of the shortened commands:

  • select -f: shortcut for -first
  • ft: shortcut for Format-Table
  • -a: shortcut for -autosize
  • sleep: defaults to using seconds
1
  • 9
    CPU in ps is number of seconds of total usage, not % CPU. So this is not so useful.
    – Artyom
    Apr 12, 2018 at 11:14
40

A similar solution as others, but using Get-Counter instead of Get-Process:

While(1) {  $p = get-counter '\Process(*)\% Processor Time'; cls; $p.CounterSamples | sort -des CookedValue | select -f 15 | ft -a}

Sample output:

Path                                                      InstanceName              CookedValue
----                                                      ------------              -----------
\\server_name\process(_total)\% processor time                 _total               4806.03969127454
\\server_name\process(idle)\% processor time                   idle                 1103.7573538257
\\server_name\process(program2)\% processor time               program              749.692930701698
\\server_name\process(program5)\% processor time               program              563.424255927765
\\server_name\process(program1)\% processor time               program              535.714866291973
\\server_name\process(program6)\% processor time               program              455.665518455242
\\server_name\process(program3)\% processor time               program              426.416718284128
\\server_name\process(program)\% processor time                program              395.628507577693
\\server_name\process(program4)\% processor time               program              335.591496700144
\\server_name\process(microsoftedgecp2)\% processor time       microsoftedgecp      129.310484967028
\\server_name\process(system)\% processor time                 system               80.0493478367316
\\server_name\process(chrome8)\% processor time                chrome               1.53941053532176

I found most of the other solutions here using Get-Process report the total CPU time since the start of the process, which wasn't useful on my server that stays up 24/7 where the top result was always svchost and system at millions of seconds.

  • A true top or Task Manager equivalent would give a snapshot of the CPU usage recorded recently over some fixed time and Get-Counter provides that. I figured this alternative is worth contributing since this question is still the top Google result for powershell top.

Based on Example 13 from the Get-Counter docs, a breakdown of the command:

  • While(1) {: Creates a loop
  • get-counter '\Process(*)\% Processor Time': Selects CPU % data, which takes a significant amount of time to return, so no need to sleep

  • cls: Clear for the new table

  • sort -des CookedValue: Sort largest on top for CookedValue [field we're interested in]
  • select -f 15: Display first 15
  • ft -a: Display in formatted table
3
  • 6
    This is the best answer: Get-Counter gives you the "instantaneous" CPU, rather than the cumulative CPU time from ps. Better formatting: Get-Counter '\Process(*)\% Processor Time' | Select-Object -ExpandProperty countersamples| Select-Object -Property instancename, cookedvalue| ? {$_.instanceName -notmatch "^(idle|_total|system)$"} | Sort-Object -Property cookedvalue -Descending| Select-Object -First 25| ft InstanceName,@{L='CPU';E={($_.Cookedvalue/100/$env:NUMBER_OF_PROCESSORS).toString('P')}} -AutoSize
    – pjhsea
    Sep 22, 2017 at 18:53
  • 1
    I ran into issues with errors causing the command to stop before reporting all running processes. This can be avoided with: $p = get-counter '\Process(*)\% Processor Time' -ErrorAction SilentlyContinue As you can imagine, this will report as many processes as possible.
    – Ian Link
    Jan 9, 2020 at 1:25
  • Best answer! The international version which works on German/French etc installations is while(1) { $p = Get-Counter -Counter '\230(*)\6' ; clear ; $p.CounterSamples | Sort-Object -Descending CookedValue | Select-Object -First 25} - See HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\<number>\Counter for which number represents what. Apr 19, 2022 at 21:32
30

There's nothing that I know of that in single cmdlet form, but like you say, scripts are easy to write to emulate top.

while (1) { ps | sort -desc cpu | select -first 30; sleep -seconds 2; cls }
4
  • close enough - I can tweak it from here... well, done! (I am a C# developer, but manage our servers too - so coming up the PowerShell curve...)
    – TimAtVenturality
    Aug 15, 2010 at 2:35
  • if you want to learn more - by example - check out www.poshcode.org
    – x0n
    Aug 15, 2010 at 16:35
  • @TimAtVenturality - You can wrap the script as a function with parameters to more closely replicate top. Aug 17, 2010 at 1:56
  • This is a very pleasant solution in terms of the sleep cycles
    – Kris Stern
    Aug 26, 2021 at 15:06
19

You can try NTop (binaries), an htop alternative for windows, with Vi-emulation for Windows:

Screenshot

  • Command line options:
    • C: Use monochrome color scheme
    • h: Display help
    • p <PID>: Show only the given PIDs
    • s <COLUMN>: Sort by this column
    • u <USERNAME>: Only display processes belonging to this user
    • v: Print version

  • Interactive commands:
    • , , PageUp, PageDn, j, k: Scroll the process list
    • CTRL+ or : Change the process sort column
    • g: Go to the top of the process list
    • G: Go to the bottom of the process list
    • Space: Tag a selected process
    • U: Untag all tagged processes
    • K: Kill all tagged processes
    • I: Invert the sort order
    • F: Follow process
      If the sort order causes the currently selected process to move in the list, make the selection bar follow it (moving the cursor manually automatically disables this feature).
    • n: Next in search
    • N: Previous in search

  • Vi commands:
    • :exec <command>: Executes the given Windows command
    • :kill <PIDs>: Kill all given processes
    • :q or :quit: Quit NTop
    • /<PATTERN> or :search <PATTERN>: Do a search
    • :sort <COLUMN>: Sort the process list after the given column
    • :tree: View process tree
1
  • 2
    Can you elaborate on how to accomplish the solution with this? from review Good guidance on recommending software here
    – fixer1234
    Mar 29, 2019 at 21:43
12

Provides nice headings at the top with every update without needing to clear the entire console:

$saveY = [console]::CursorTop
$saveX = [console]::CursorLeft

while ($true) {
  Get-Process | Sort -Descending CPU | Select -First 30;
  Sleep -Seconds 2;
  [console]::setcursorposition($saveX,$saveY+3)
}
6

I'm not aware of a PowerShell cmdlet that provides the functionality, but here is a freeware external command that does about what you want, Mark Russinovich's pslist from the Sysinternals suite:

  • pslist provides a list of executing processes in a configurable view
  • pslist -s provides the sort of continuous update you want, with a default refresh rate of once per second

I prefer to use Mark's GUI Process Explorer, but pslist is handy for console sessions.

4

This is just a slightly nicer way, as you get to see the headings up top every time:

While (1) {
  Ps | Sort -desc cpu | Select -first 30; Sleep -seconds 2; Cls;
  Write-Host "Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName";
  Write-Host "-------  ------    -----      ----- -----   ------     -- -----------"
}

This is just a slightly nicer way, as you get to see the headings up top every time

4

I wrote wttop, a top for the new Windows Terminal that can be launched via Powershell.

4

Save the following to mytop.ps1, within a folder in your PATH environment variable, then use one of the following from any PowerShell console:

  • mytop: [Defautl] Sort by Memory and show the first 30 lines
  • mytop CPU 50: Sort by 'CPU' and show the first 50 lines.
  • While(1) {$p = myTop Memory 50; cls; $p}: Refreshes every second
##################################################
#  Linux top equivalent in PowerShell
##################################################
if ($args[0] -eq $null) {
  $SortCol = "Memory"
} else {
  $SortCol = $args[0]
}

if ($args[1] -eq $null) {
  $Top = 30
} else {
  $Top = $args[1]
}


$LogicalProcessors = (Get-WmiObject -class Win32_processor -Property NumberOfLogicalProcessors).NumberOfLogicalProcessors;

function myTopFunc ([string]$SortCol = "Memory", [int]$Top = 30) {
  ## Check user level of PowerShell
  if (
    ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
    ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
  )
  {
    $procTbl = get-process -IncludeUserName | select ID, Name, UserName, Description, MainWindowTitle
  } else {
    $procTbl = get-process | select ID, Name, Description, MainWindowTitle
  }

  Get-Counter `
    '\Process(*)\ID Process',`
    '\Process(*)\% Processor Time',`
    '\Process(*)\Working Set - Private'`
    -ea SilentlyContinue |
  foreach CounterSamples |
  where InstanceName -notin "_total","memory compression" |
  group { $_.Path.Split("\\")[3] } |
  foreach {
    $procIndex = [array]::indexof($procTbl.ID, [Int32]$_.Group[0].CookedValue)
    [pscustomobject]@{
      Name = $_.Group[0].InstanceName;
      ID = $_.Group[0].CookedValue;
      User = $procTbl.UserName[$procIndex]
      CPU = if($_.Group[0].InstanceName -eq "idle") {
        $_.Group[1].CookedValue / $LogicalProcessors
        } else {
        $_.Group[1].CookedValue
      };
      Memory = $_.Group[2].CookedValue / 1KB;
      Description = $procTbl.Description[$procIndex];
      Title = $procTbl.MainWindowTitle[$procIndex];
    }
  } |
  sort -des $SortCol |
  select -f $Top @(
    "Name", "ID", "User",
    @{ n = "CPU"; e = { ("{0:N1}%" -f $_.CPU) } },
    @{ n = "Memory"; e = { ("{0:N0} K" -f $_.Memory) } },
    "Description", "Title"
  ) | ft -a
}

myTopFunc -SortCol $SortCol -top $Top

Example output:

Name                               ID User                         CPU   Memory       Description
----                               -- ----                         ---   ------       -----------
sqlservr                         7776 NT SERVICE\MSSQLSERVER       0.0%  19,001,488 K SQL Server Windows NT - 64 Bit
python                          12872 NA\user1                     0.0%  2,159,796 K  Python
svchost                          3328 NT AUTHORITY\SYSTEM          1.6%  1,022,080 K  Host Process for Windows Services
onedrive                        11872 NA\user1                     0.0%  423,396 K    Microsoft OneDrive
python                          13764 NA\user1                     0.0%  304,608 K    Python
chrome                          21188 NA\user1                     0.0%  250,624 K    Google Chrome
python                          28144 NA\user2                     0.0%  225,824 K    Python
code                            21384 NA\user1                     0.0%  211,160 K    Visual Studio Code
code                            27412 NA\user2                     0.0%  185,892 K    Visual Studio Code
ssms                            18288 NA\user1                     29.5% 155,452 K    SSMS
chrome                           7536 NA\user1                     0.0%  154,124 K    Google Chrome
code                            21652 NA\user1                     0.0%  149,900 K    Visual Studio Code
explorer                         3204 NA\user1                     0.0%  134,340 K    Windows Explorer
python                          11712 NA\user1                     0.0%  130,624 K    Python
chrome                          21588 NA\user1                     0.0%  107,448 K    Google Chrome
code                            10152 NA\user1                     0.0%  100,880 K    Visual Studio Code
code                            20232 NA\user2                     0.0%  99,124 K     Visual Studio Code
python                          22184 NA\user1                     0.0%  94,800 K     Python
code                            14828 NA\user1                     0.0%  84,872 K     Visual Studio Code
searchui                        13344 NA\user1                     0.0%  78,260 K     Search and Cortana application
com.docker.service              10644 NT AUTHORITY\SYSTEM          0.0%  77,332 K     Docker.Service

Additional credit to answers from rokumaru, LotPings, and DBADon

1
  • It seems to be broken in my machine, there is no output. I am using Windows 10. Aug 15, 2023 at 4:27
2

This may also do the trick:

function htopish {
  Param (
  [Parameter(Position=1)] [Alias("l")]
  [int]$TotalList=24,
  [Parameter(Position=2)] [Alias("r")]
  [int]$Invertal=1
  )
  Begin {}
  Process {
    While ($true) {
      $CounterSamples = Get-Counter '\Process(*)\ID Process','\Process(*)\% Processor Time','\Process(*)\Working Set' | Select-Object -Expand CounterSamples
      Clear-Host
      $CounterSamples | Group-Object { Split-Path $_.Path } | Where-Object {$_.Group[1].InstanceName -notmatch "^Idle|_Total|System$"} | Sort-Object -Property {$_.Group[1].CookedValue} -Descending | Select-Object -First $TotalList | Format-Table @{Name="ProcessId";Expression={$_.Group[0].CookedValue}},@{Name="ProcessorUsage";Expression={[System.Math]::Round($_.Group[1].CookedValue/100/$env:NUMBER_OF_PROCESSORS,4)}},@{Name="ProcessName";Expression={$_.Group[1].InstanceName}},@{Name="WorkingSet";Expression={[System.Math]::Round($_.Group[2].CookedValue/1MB,4)}}
    Sleep -Seconds $Invertal
  }
  }
  End {}
}
  • Function relies on the Get-Counter samples and will output the ProcessId,ProcessName, ProcessorUsage, and WorkingSet.
  • This counter sample could be further enhanced to include User and CommandLine in the output, but I haven't yet worked out a performant way to do so.
1

This comment from Mark should get more recommendation because it does almost exactly what the question was and it works:

  • Provides the nice headings at the top with every update without needing to clear the entire console.

    $saveY = [console]::CursorTop
    $saveX = [console]::CursorLeft      
    
    while ($true) {
      Get-Process | Sort -Descending CPU | Select -First 30;
      Sleep -Seconds 2;
      [console]::setcursorposition($saveX,$saveY+3)
    }
    
1
  • Except Get-Process doesn't give you a snapshot of CPU, it's cumulative.
    – Jaykul
    Aug 29, 2020 at 3:01
1

I want to point out that if you would like a Linux-like environment for Windows you can use Cygwin, as it brings a Linux-like environment to Windows, where you can use almost every command.

1

Glances

  • Win, Linux supported, high CPU consumption
  • Win over SSH supported
  • Win - not supported window resize
  • REST API supported
  • Standalone / Client / Web server modes supported
  • Cygwin not supported

Glances example in Windows over SSH

Installation with Chocolatey

choco install pip
pip install windows-curses
pip install glances

or (if cannot find pip)

choco install python
c:\Python310\Scripts\pip.exe install windows-curses
c:\Python310\Scripts\pip.exe install glances

Run

C:\Python310\Scripts\glances.exe

Usage

Discover documentation please.

0

If you wish to filter by process, use findstr:

while (1) { Ps |Findstr explorer | Sort -desc cpu | Select -first 30; Sleep -seconds 2; Cls }
0

You might want to launch resource monitor from Powershell via resmon, which can be closed via Alt+F4, switching focus back to the Powershell terminal.

1
  • 1
    OP would like to use remote powershell sessions, so a GUI answer doesn't fit here.
    – P-L
    Mar 25, 2019 at 15:21
0

Something else to look at is SysInternals

  • These are command-line tools to dump all processes, along with Process Monitor and Process Explorer.
0

The below command will give the top 10 CPU utilization, with output refreshed every 5 seconds:

while(1) {
  Ps | Sort-Object -Property CPU -Descending | Select -First 10
  Write-Host "output will be refreshed in 5 sec's `n `n Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName"
  Sleep -Seconds 5
}
0
0

To run top directly from cmd, you'll need to create %WinDir%\top.bat with this code:

@echo off

Cls && @echo TOP Program initialisation. Please Wait...

Powershell -ExecutionPolicy unrestricted -command "& {
  Cls
  While(1) { Ps | Sort -des CPU | Select -f 35 | Ft -a; sleep 2; Cls }
}"

You must log in to answer this question.