Instant accurate CPU watch using Windows Powershell

Vijay Ganji 141 Reputation points
2021-07-01T14:21:41.913+00:00

I am using below Powershell code to check the instant CPU utilization in my Windows Server (SQL Server Database boxes).

What I am trying to do get the accurate results of CPU utilization . But the below code does seems to be helping , could anyone aware of this please help. Even using the counter values doesn't give me the accurate values , please help.

Function Get-Cpu {

[CmdletBinding()]

    Param (

        [parameter(Mandatory, ValueFromPipeline)]

        [String[]]$ServerInstance,

        [parameter(Mandatory = $true)] $NumOfSessions

    )

foreach($i in $ServerInstance){

Get-Counter -ComputerName $i -Counter "\Process(*)\% Processor Time" -ErrorAction SilentlyContinue `

  | select -ExpandProperty CounterSamples `

  | where {$_.Status -eq 0  } `

  | sort CookedValue -Descending `

  | select @{N = "SQLInstance";e={$i}} , TimeStamp,

    @{N="Name";E={

        $friendlyName = $_.InstanceName

        try {

            $procId = [System.Diagnostics.Process]::GetProcessesByName($_.InstanceName)[0].Id

            $proc = Get-WmiObject -Query "SELECT ProcessId, ExecutablePath FROM Win32_Process WHERE ProcessId=$procId"

            $procPath = ($proc | where { $_.ExecutablePath } | select -First 1).ExecutablePath

            $friendlyName = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($procPath).FileDescription

        } catch { }

        $friendlyName

    }},

    @{N="CPU";E={(($_.CookedValue/100/$env:NUMBER_OF_PROCESSORS)/100).ToString("P")}}  -First $NumOfSessions |Format-Table -AutoSize



 }

 }

 Get-Cpu
Windows for business Windows Server User experience PowerShell
Windows for business Windows Server User experience Other
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Rich Matheisen 47,901 Reputation points
    2021-07-01T18:42:57.3+00:00

    On line 19, why have you used "ExpandProperty"? That will produce a string, not an object that has property names. That means, for example, line 21 probably doesn't get you the value of the "Status" property as you expect.

    I'd replace lines 25 - 51 with a "ForEach-Object" and build an [ordered] hash with the key names and values you want, and then use that has to produce PSCustomObject(s) that are emitted from the function. It'd be a lot easier to understand than your attempt to use Select-Object.

    0 comments No comments

  2. Rich Matheisen 47,901 Reputation points
    2021-07-01T22:51:23.407+00:00

    I straightened out your code and ran it (I don't have SQL, though). I'm not sure what result you're trying to achieve, though. I get the "accurate results of CPU utilization" part, but not whether you're after only certain processes, or the total CPU for all processes, or something else. Right now the result are only the CPU consumption of the top "X" number processes regardless of what they are. Furthermore, it's only the top "X" results of each individual set of CounterSamples.

    This is how I reformatted your code. I think it's more understandable. I hope you do, too:

    Function Get-Cpu {
        [CmdletBinding()]
        Param (
            [parameter(Mandatory, ValueFromPipeline)]
            [String[]]$ServerInstance,
            [parameter(Mandatory = $true)] $NumOfSessions
        )
    
        $template = [ordered]@{
            SQLInstance = ""
            TimeStamp = ""
            Name = ""
            CPU = ""
        }
    
        foreach ($i in $ServerInstance) {
            Get-Counter -ComputerName $i -Counter "\Process(*)\% Processor Time" -ErrorAction SilentlyContinue |    # gets all processes
                Select-Object -ExpandProperty CounterSamples |
                    Where-Object { $_.Status -eq 0 } |
                        Sort-Object CookedValue -Descending |
                            Select-Object -First $NumOfSessions |   # get only the top X number of CounterSamples
                                ForEach-Object{
                                    $template.SQLInstance = $i
                                    $template.TimeStamp = $_.TimeStamp
                                    $template.Name = Try{
                                                            $procId = [System.Diagnostics.Process]::GetProcessesByName($_.InstanceName)[0].Id
                                                            $proc = Get-WmiObject -Query "SELECT ProcessId, ExecutablePath FROM Win32_Process WHERE ProcessId=$procId" -ErrorAction STOP
                                                            $procPath = (   $proc | 
                                                                                Where-Object { $_.ExecutablePath } | 
                                                                                    Select-Object -First 1).ExecutablePath
                                                            $friendlyName = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($procPath).FileDescription
                                                            $friendlyName
                                                        }
                                                        Catch{
                                                            $i      # in case of failure use the server instance
                                                        }
                                    $template.CPU = "{0:P}" -f (($_.CookedValue / 100 / $env:NUMBER_OF_PROCESSORS) / 100)   # express result as percentage
                                    [PSCustomObject]$template
                                }
        }
    }
    
        Get-Cpu ws06 3
    

    I left the "$friendlyName" variable in the code but it's not necessary. You can remove the "$friendlyName =" and keep the "[System.Diagnostics.FileVersionInfo]::GetVersionInfo($procPath).FileDescription". If you do, remove the "$friendlyName" right below that.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.