I want to thank Jason Field for the bulk of this script!
Our team was presented with an issue where we needed to measure the CPU Percentage Processor Times for Each Core within the Physical Processor and be able to output that data quickly through PowerShell. We all have knowledge that Performance Monitor can do this through a GUI, but it is very difficult to be able to output that data to a file in a manner that can be easily read. We had an original PowerShell cmdlet that would accomplish this for the total percent processor time for all cores over a one minute period:
1 | "Average Processor Time for 60 seconds: $([math]::round((get-counter -Counter "\Processor(_Total)\% Processor Time" -SampleInterval 1 -MaxSamples 60 | select -ExpandProperty countersamples | select -ExpandProperty cookedvalue | Measure-Object -Average).average,2))%" |
Our challenge was to be able to do this per core, over the same time period, and get the average for each core, so that we could measure the output accordingly. I had been working on a script that was able to run the command, but not in a parallel fashion. The script was running in sequence and was taking way longer than one minute to complete. Frustrating to say the least.
In comes Jason Field, showing me the meaning and value of the back tick as well has how to run and monitor job functions in a PowerShell Script so that the task could be completed, as needed, across a server array.
The main purpose of the (back tick) is that it allows for variables to be used in the script block on the remote server instead of being filled in before creating the script block.
Here is the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | <# CPUProcTimeForArray.ps1 - This Script is prepared by Jason Field. Modified by Lance Lingerfelt - LDLNET LLC. #> <# The script has been prepared and tested in Powershell 4.0. So when you run this script, please make sure that the Powershell version is 4.0 or above. #> <# This script will measure the Percentage Processor Time of each CPU core in each target server in your server array for one minute and average it for a total of four cores per processor. The results will be exported to a CSV file in C:\Reports #> <# Please Adjust the Script as needed for your Server Array and number of total Processor Cores that need to be measured. #> <# While this script has been tested multiple times, it is recommended that you test it in a test environment before using it in a production environment. #> <# While every attempt has been made to make this script error free, we do not take any responsibility for any consequence that would happen while running the script. Modifications to the script are not our responsibility either.#> Write-Host "This script will measure the Percentage Processor Time of each CPU core in each target server in your server array for one minute and average it for a total of four cores per processor. The results will be exported to a CSV file in C:\Reports" -ForegroundColor Green Write-Host "Please Adjust the Script as needed for your Server Array and number of total Processor Cores that need to be measured." -ForegroundColor Yellow $results=@() $jobs=@() $date = Get-Date -Format G #Set your Server array of hosts to be processed. $servers = "Server01","Server02","Server03","Server04","Server05","Server06" Foreach($server in $servers){ If($server -eq $env:computername){ $script = " `$counter = Get-Counter -ComputerName $server '\Processor(*)\% Processor Time' -SampleInterval 1 -MaxSamples 60 `$Counter = `$counter.Countersamples `$Core1 = [math]::Round(((`$counter | where{`$_.instancename -eq '0'} | Measure-Object -Property CookedValue -Average).average),2) `$Core2 = [math]::Round(((`$counter | where{`$_.instancename -eq '1'} | Measure-Object -Property CookedValue -Average).average),2) `$Core3 = [math]::Round(((`$counter | where{`$_.instancename -eq '2'} | Measure-Object -Property CookedValue -Average).average),2) `$Core4 = [math]::Round(((`$counter | where{`$_.instancename -eq '3'} | Measure-Object -Property CookedValue -Average).average),2) `$ALLCores = [math]::Round(((`$counter[0..3] | Measure-Object -Property Cookedvalue -Average).average),2) `$ServerReport = New-Object -TypeName PSObject -Property @{ Server = '$server' LongJoins = `$longJoin Core1 = `$Core1 Core2 = `$Core2 Core3 = `$Core3 Core4 = `$Core4 ALLCores =`$ALLCores } `$ServerReport " $scriptblock = [scriptblock]::Create($Script) $Session = New-PSSession $jobs+=Invoke-Command -Session $Session -ErrorAction 'SilentlyContinue' -ScriptBlock $scriptblock -asjob } Else{ $Session = New-PSSession -ComputerName $server $script = " `$counter = Get-Counter -ComputerName $server '\Processor(*)\% Processor Time' -SampleInterval 1 -MaxSamples 60 `$Counter = `$counter.Countersamples `$Core1 = [math]::Round(((`$counter | where{`$_.instancename -eq '0'} | Measure-Object -Property CookedValue -Average).average),2) `$Core2 = [math]::Round(((`$counter | where{`$_.instancename -eq '1'} | Measure-Object -Property CookedValue -Average).average),2) `$Core3 = [math]::Round(((`$counter | where{`$_.instancename -eq '2'} | Measure-Object -Property CookedValue -Average).average),2) `$Core4 = [math]::Round(((`$counter | where{`$_.instancename -eq '3'} | Measure-Object -Property CookedValue -Average).average),2) `$ALLCores = [math]::Round(((`$counter[0..3] | Measure-Object -Property Cookedvalue -Average).average),2) `$ServerReport = New-Object -TypeName PSObject -Property @{ Server = '$server' LongJoins = `$longJoin Core1 = `$Core1 Core2 = `$Core2 Core3 = `$Core3 Core4 = `$Core4 ALLCores =`$ALLCores } `$ServerReport " $scriptblock = [scriptblock]::Create($Script) $jobs+=Invoke-Command -Session $Session -ErrorAction 'SilentlyContinue' -ScriptBlock $scriptblock -asjob } } $ActiveJobs=Get-Job while ($ActiveJobs){ $CompletedJobs=Get-Job -State 'Completed' if ($CompletedJobs) { Write-output ("Checking {0} job(s) out of remaining {1}" -f $CompletedJobs.count, $ActiveJobs.count) foreach ($job in $CompletedJobs) { #Adds completed jobs to Results $results+=Receive-Job -Id $job.ID Remove-Job -Id $job.id } } #Checks for failed jobs and reports on failed jobs $FailedJobs=Get-Job -State 'FAILED' if ($FailedJobs) { Write-output ("Checking failed jobs") foreach ($job in $FailedJobs) {$cpu Write-Output ("Failed Job Removed") $results+=Receive-Job -Id $job.ID Remove-Job -Id $job.id } } $ActiveJobs=Get-Job if($ActiveJobs) {sleep 30} } #Remove Sessions Get-PSSession | Remove-PSSession #Gathers Results $date $results | Select Server,Core1,Core2,Core3,Core4,ALLCores #Exports Results to CSV $results | Select Server,Core1,Core2,Core3,Core4,ALLCores | Export-csv c:\Reports\CPUProcTimeForArray.csv -NoTypeInformation |
What I learned from this script is that the back tick ” ` “ allows for multiple commands to be run in a sub-routine within the script job and be gathered before the main script command is run and the output given. This gets past the multi-threading issue I was having with my original script. The script can then be run across multiple servers using the Invoke-Command cmdlet or over a remote PowerShell session as a Job. The jobs can then be monitored as the scripts finish across the multiple servers in the time period given for the samples. I had modified the script to do the multiple samples and then take the average of the CookedValue per the original cmdlet. I could not, however, get the ExpandProperty parameter to work with the script.
Please, as I am still learning, if you see an error with this script, please alert me with a comment or contact me directly so that I can update the script properly.
Thanks again Jason! It really was like a light bulb going “DING” when I figured it out with concern to the back tick. I had also been having issues with the active job monitoring process. It was a real help!