PowerShell property expression increases execution time by 4-5 times - powershell

Scroll down for TL;DR
I need to get the following properties for every process as quickly as possible, ideally 5 seconds, maximum 10 seconds: ID, Name, Description, Path, Company, Username, Session ID, StartTime, Memory, CPU (percentage, not time)
To get this data, I put together the following snippet which (I think) is functionally perfect:
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
The issue is that its taking 18-22 seconds to execute, caused by this line (which adds about 16 seconds):
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={
[int]($ProcessCPU | ?{'IDProcess' -eq $_.Id}).PercentProcessorTime
}}
}
TotalSeconds : 19.061206
When I remove the slow property expression noted above and keep the WMI query, execution takes about 4.5 seconds:
Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object IDProcess, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
}
TotalSeconds : 4.5202906
I thought that by getting all of the required data in a single query and referring back to the $ProcessCPU array would be fast - but I appreciate I'm iterating through each of the 250 arrays stored in $Processes.
TL;DR:
Is there a more performant method of joining two objects on a common property rather than using iteration as I have above? I.E. $ProcessCPU.IDProcess on $Processes.Id?
I tried the following block to test $Output = $ProcessCPU + $Processes | Group-Object -Property Id, it executed in just 3 seconds, but the output wasn't acceptable:
PS C:\Windows\system32> Measure-Command -Expression {
$ProcessCPU = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | Select-Object #{Name='Id';Expression={[int]$_.IDProcess}}, PercentProcessorTime
$Processes = Get-Process -IncludeUserName |
Select-Object `
#{Name='Id';Expression={[int]$_.Id}},
#{Name='Name';Expression={[string]$_.Name}},
#{Name='Description';Expression={[string]$_.Description}},
#{Name='Path';Expression={[string]$_.Path}},
#{Name='Company';Expression={[string]$_.Company}},
#{Name='Username';Expression={[string]$_.UserName}},
#{Name='SessionId';Expression={[string]$_.SessionId}},
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}}
$Output = $ProcessCPU + $Processes | Group-Object -Property Id
}
TotalSeconds : 2.9656969

Use CIM to build up a hashtable that maps process IDs (PIDs) to their CPU percentages first.
Then make the calculated property passed to Select-Object consult that hashtable for efficient lookups:
Get-CimInstance Win32_PerfFormattedData_PerfProc_Process |
ForEach-Object -Begin { $htCpuPctg=#{} } `
-Process { $htCpuPctg[$_.IdProcess] = $_.PercentProcessorTime } #`
Get-Process -IncludeUserName |
Select-Object Id,
Name,
Description,
Path,
Company,
UserName,
SessionId,
#{Name='StartTime';Expression={[string](($_.StartTime).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"))}},
#{Name='MemoryMB';Expression={[int]([math]::Round($_.WorkingSet/1MB,2))}},
#{Name='CPUPercent';Expression={ $htCpuPctg[[uint32] $_.Id] }}
Note:
Get-CimInstance rather than Get-WimObject is used, because the CIM cmdlets superseded the WMI cmdlets in PowerShell v3 (released in September 2012). Therefore, the WMI cmdlets should be avoided, not least because PowerShell Core, where all future effort will go, doesn't even have them anymore. For more information, see this answer.
There is usually no need to use calculated properties such as #{Name='Id';Expression={[int]$_.Id}} to simply extract a property as-is - just use the property's name - Id - as a Select-Object -Property argument (but you've since clarified that you're using calculated properties because you want explicit control over the property's data type for sending data to an IoT Gateway via JSON).
Note that CIM reports PIDs as [uint32]-typed values, whereas Get-Process uses [int] values - hence the need to cast to [uint32] in the hashtable lookup.

Related

Task Manager thread counts and username query

I'm new to working with powershell and I'm trying to create a script where I can check a remote machines specific process thread counts along with the username and in the output I hope I can get the highest thread user.
I'm started from this script as a reference but I can't seen to find the right commands/syntax
Get-Process | Select-Object Name, #{Name='ThreadCount';Expression ={$_.Threads.Count}} | Sort-Object -Property Threadcount -Descending
Get-Process have an optional -IncludeUserName switch argument you can include to get the username out of the command. It will requires admin. priviledge to collect that information.
Once you have the information, you will need to group by user and then sort by the sum of threads per user to get your desired "Highest thread users".
Here is what that would look like.
#requires -runasadministrator
$ProcessesSummary = Get-Process -IncludeUserName |
Select-Object Name, #{Name = 'ThreadCount'; Expression = { $_.Threads.Count } },Username
$ProcessesSummary |
Group-Object -Property Username |
Select Name,#{N='TotalThreads';E={($_.Group.ThreadCount |
Note that if you do not do anaything with $ProcessesSummary other than using it for the total thread per user, you can forego the first statement completely and instead just do
#requires -runasadministrator
Get-Process -IncludeUserName |
Group-Object -Property Username |
Select Name,#{N='TotalThreads';E={($_.Group.Threads.Count | Measure-Object -Sum).Sum}} |
Sort TotalThreads -Descending |
Where Name -NotLike 'NT*' # If you want to remove some of the system accounts from the output

How can I use powershell to group process names and show the sum of memory used

I am trying to wrap my head around combining powershell options in order to produce a simple table of the top 10 memory users on my system (server, pc, etc). My PC is Windows 7 with no timeline in site for upgrade to Windows 10 due to Covid 19. I cannot add applications to my work PC that has not gone through a vetting process (read, it takes forever) so most of the time I create my own.
I would like to produce a result that looks something like this:
Count Name Memory Sum in MB
10 Firefox 5000
3 javaw 1000
The order I would like to be able to select by changing a property in the powershell options. So for example, sort by count, name or memory. My sample table is not set in stone.
I have come across the following 2 pieces of powershell and have been trying to adapt them but get errors.
(Get-Process | Measure-Object WorkingSet -sum).sum /1gb
Get-Process | Group-Object -Property Name -NoElement | Where-Object {$_.Count -gt 1}
For sake of learning, I don't mind seeing an "ugly" version and an optimized version.
You can use this:
$proc=ps|select -eXp name;$proc2=#()
$proc|%{
if(!("$($_)" -in $proc2)){$proc2+="$($_)"
$mem=0;ps $_|select -eXp workingSet|%{$mem+=$_/1MB}
[pscustomobject][ordered]#{
'Count'=(ps $_ -ea silentlyContinue).Count
'Name'=$_
'Memory in MB'=$mem
}}}
The PSCustomObject accelerator was introduced in PowerShell v3 so I don't know if the the output looks like a table in Windows 7 however the following pipeline returns desired properties even in PowerShell v2:
Get-Process |
Group-Object -Property Name -NoElement |
Where-Object { $_.Count -gt 1 } |
ForEach-Object {
[PSCustomObject]#{
Count= $_.Count
Name = $_.Name
'Memory Sum in MB' = [math]::Round(( Get-Process -Name $_.Name |
Measure-Object WorkingSet -sum).sum /1Mb, 3)
}
} # | Sort-Object -Property 'Memory Sum in MB'

How to get max CPU percentage from from 5 trials

I am new to Powershell and struggling with syntax.
I want to write a script which gives me max CPU usage by a process out of 5 attempts.
$properties=#(
#{Name="Process Name"; Expression = {$_.name}},
#{Name="CPU (%)"; Expression = {$_.PercentProcessorTime}},
#{Name="Memory (MB)"; Expression = {[Math]::Round(($_.workingSetPrivate / 1mb),2)}}
)
Get-WmiObject -class Win32_PerfFormattedData_PerfProc_Process | Select-Object $properties
I have to run the above process 5 times and pick the top process which has max CPU usage.
This should get you what you want (remember to also include your definition of $properties):
1 .. 5 |
ForEach-Object {
Get-WmiObject -class Win32_PerfFormattedData_PerfProc_Process
} | Where-Object Name -notin '_Total','Idle' |
Sort-Object -Property 'PercentProcessorTime' -Descending |
Select-Object -First 1 -Property $properties
1 .. 5 is the range operator, which generates the set of numbers 1,2,3,4,5. This is just a quick hack to run ForEach-Object 5 times.
Where-Object Name -notin '_Total','Idle' excludes some 'processes' that always have high values but are unlikely to be what you're looking for. Generally it is more efficient to update the call to Get-WmiObject to exclude these at that stage, but for clarity I went with this technique.
Sort-Object -Property 'PercentProcessorTime' -Descending takes all of the readings and sorts them in order from largest CPU value to lowest.
Select-Object -First 1 -Property $properties Selects just the first object in the sorted list (i.e. the one with the highest value). Note that it is better to do this last and not after each call to Get-WmiObject as it creates a new custom object for each WMI one returned, almost all of which we discard further along the line - it is more efficient to do this 'duplication' for only the final object we select.

Powershell - Bytes sent/received

I need to create a script that samples the network traffic every 30 seconds and stores the bytes sent/received. This data is then later used to draw graphs. I wrote one that works perfectly on Windows 2012 but i realised some of the cmdlets were not available in previous versions like 2008 so i am seeking alternatives.
For windows 2012 i used get-netadapterstatistics to get the received/sent bytes but this won't work on pre 2012 so i thought i could use netstat -e but the problem is both are giving me completely different results and i was hoping someone can tell me why? The script below was written to see the different between data.
function getNic{
$nic = Get-NetRoute | ? DestinationPrefix -eq '0.0.0.0/0' | Get-NetIPInterface | Where ConnectionState -eq "Connected" | Select -ExpandProperty InterfaceAlias
return $nic
}
function getBR{
$b = ((netstat -e | Select-String "Bytes") -split '\s+')[2]
$a = (Get-NetAdapterStatistics |Where InterfaceAlias -eq $nic_name |Select -ExpandProperty SentBytes)
$a - $script:startbr
$b - $script:startbr2
$script:startbr = $a
$script:Startbr2 = $b
}
$nic_name = getNic
$startbr = (Get-NetAdapterStatistics |Where InterfaceAlias -eq $nic_name |Select -ExpandProperty SentBytes)
$startbr2 = ((netstat -e | Select-String "Bytes") -split '\s+')[2]
for(1..1000){
getBR
Start-Sleep 5
}
The results are as below
0
0
4577
18308
6695
26780
9055
36220
Ideally i am only interested in capturing traffic on the external interface.
While i can´t offer you an explanation for the difference between your methods i could offer you an alternative that should work on pre 2012 as well as on 2012 upwards:
$ifIndex = Get-WmiObject -Class win32_ip4routetable | where {$_.destination -eq "0.0.0.0"} | select -ExpandProperty InterfaceIndex
$ifIndex = "InterfaceIndex=" + $ifIndex
$nic_name = Get-WmiObject -Class win32_networkadapterconfiguration -Filter $ifIndex | select -ExpandProperty Description
$nic = [System.Net.NetworkInformation.Networkinterface]::GetAllNetworkInterfaces() | where {($_.description -eq $nic_name) -and ($_.operationalstatus -eq "up")}
$stats = $nic.GetIPv4Statistics()
$bytesSent = $stats.BytesSent
$bytesReceived = $stats.BytesReceived
This gives results consistent with the Get-NetAdapterStatistics Cmdlet on my system
After thinking about it maybe netstat shows statistics for multiple network adapters (maybe including loopback) combined since there is no differentiation by nic? Just guessing but this might explain the increased bytecount. Sadly there´s no details to be found in the docs

Convert GUID string to octetBytes using PowerShell

I have a powershell script which outputs all Exchange 2003 mailboxes by size.
$computers = "vexch01","vexch02"
foreach ($computer in $computers) {
Get-Wmiobject -namespace root\MicrosoftExchangeV2 -class Exchange_Mailbox -computer $computer | sort-object -desc Size | select-object MailboxDisplayName,StoreName,#{Name="Size/Mb";Expression={[math]::round(($_.Size / 1024),2)}}, MailboxGUID | Export-Csv -notype -Path $computer.csv
}
Currently this outputs the MailboxGUID as a string type GUID (e.g. {21EC2020-3AEA-1069-A2DD-08002B30309D}). I want to look up users in AD by this, but AD stores them in octetBytes format.
I have found some powershell functions which will do the conversion but only when the curly braces are removed. The Guid.ToString method should supply this, but I can't get it to work in the above.
However, if I could figure out how to do that, the Guid.ToByteArray method might get me even closer.
Has anyone cracked this?
Update: the answers so far helped me write a function that converts the mailboxguid into the correct format for searching via LDAP. However, I now cannot get this working in the script. This is my updated script:
function ConvertGuidToLdapSearchString(
[parameter(mandatory=$true, position=0)]$Guid
)
{
$guid_object = [System.Guid]$Guid
($guid_object.ToByteArray() | foreach { '\' + $_.ToString('x2') }) -join ''
}
# Gets data through WMI from specified Exchange mailbox servers
$servers = "vexch01","vexch02"
foreach ($server in $servers) {
Get-Wmiobject -namespace root\MicrosoftExchangeV2 -class Exchange_Mailbox -computer $computer | sort-object -desc Size | select-object MailboxDisplayName,StoreName,#{Name="Size/Mb";Expression={[math]::round(($_.Size / 1024),2)}}, #{Name="LDAP Guid";Expression={ConvertGuidToLdapSearchString(MailboxGUID)}} | Export-Csv -notype -Path $server.csv
}
I'm not sure why using the function in the select-object with #{Name="LDAP Guid";Expression={ConvertGuidToLdapSearchString(MailboxGUID)}} doesn't work.
Is there another way of using this function in select-object that will give the string?
In conjunction with Andy Schneider's answer, you may find this function useful:
function Convert-GuidToLdapSearchString(
[parameter(mandatory=$true, position=0)][guid]$Guid
)
{
($Guid.ToByteArray() | foreach { '\' + $_.ToString('x2') }) -join ''
}
(I thought I had a more clever way to do this by adding a ScriptProperty to System.Guid, but I seem to have learned that you can't effectively add members to structs.)
I'm not sure I understand what you are trying to accomplish based on your comment, but I think you may have just left out a $_. Here is a somewhat contrived example that creates an object with a property that is a GUID, then uses select and Convert-GuidToLdapSearchString to convert the format. I hope it helps.
$o = New-Object PSObject -Property #{ GUID = $([Guid]::NewGuid()) }
$o
$o | select #{ Name='SearchString'; Expression={ Convert-GuidToLdapSearchString $_.GUID } }
This is not at all how I had imagined the function being used. I expected you would use it to create an LDAP search clause such as:
$searchString = Convert-GuidToLdapSearchString '{9e76c48b-e764-4f0c-8857-77659108a41e}'
$searcher = [adsisearcher]"(msExchMailboxGuid=$searchString)"
$searcher.FindAll()
Are you casting the string to a GUID ?
$guid = [System.Guid]"{21EC2020-3AEA-1069-A2DD-08002B30309D}"
$guid.ToString()
$guid.ToByteArray()