Reduce output from three commands to one line - powershell

I have this script:
$counterWS = "\Process(powershell)\Working Set"
$counterWSPe = "\Process(powershell)\Working Set Peak"
$counterWSPr = "\Process(powershell)\Working Set - Private"
$dataWS = Get-Counter -Counter $counterWS
$dataWSPe = Get-Counter -Counter $counterWSPe
$dataWSPr = Get-Counter -Counter $counterWSPr
$dataWS.countersamples | Format-Table Timestamp,#{Name='WorkingSet';Expression={($_.CookedValue/1KB)}},WorkingSetPeak,WorkingSetPrivate -Auto | findstr Timestamp
$dataWS.countersamples | Format-Table Timestamp,#{Name='WorkingSet';Expression={($_.CookedValue/1KB)}},WorkingSetPeak,WorkingSetPrivate -Auto | findstr [-]
while ($true) {
$dataWS.countersamples | Format-Table Timestamp,#{Name='WorkingSet';Expression={($_.CookedValue/1KB)}},WorkingSetPeak,WorkingSetPrivate -Auto | findstr [:]
$dataWSPe.countersamples | Format-Table Timestamp,WorkingSet,#{Name='WorkingSetPeak';Expression={($_.CookedValue/1KB)}},WorkingSetPrivate -Auto | findstr [:]
$dataWSPr.countersamples | Format-Table Timestamp,WorkingSet,WorkingSetPeak,#{Name='WorkingSetPrivate';Expression={($_.CookedValue/1KB)}} -Auto | findstr [:]
Start-Sleep -s $args[0]
}
and the result is like:
Timestamp WorkingSet WorkingSetPeak WorkingSetPrivate
--------- ---------- -------------- -----------------
29/07/2016 18:41:12 10644
29/07/2016 18:41:13 10676
29/07/2016 18:41:14 3056
Is there a way to reduce to make output like this:
Timestamp WorkingSet WorkingSetPeak WorkingSetPrivate
--------- ---------- -------------- -----------------
29/07/2016 18:41:12 10644 10676 3056

Collect all 3 counters at once (the parameter -Counter accepts a list of arguments), then split the result into separate calculated properties:
$ws = '\Process(powershell)\Working Set'
$wsPeak = '\Process(powershell)\Working Set Peak'
$wsPriv = '\Process(powershell)\Working Set - Private'
Get-Counter -Counter $ws, $wsPeak, $wsPriv |
Select-Object Timestamp,
#{n='WorkingSet';e={$_.CounterSamples[0].CookedValue / 1KB}},
#{n='WorkingSetPeak';e={$_.CounterSamples[1].CookedValue / 1KB}},
#{n='WorkingSetPrivate';e={$_.CounterSamples[2].CookedValue / 1KB}}
The CounterSamples property is a list of PerformanceCounterSample objects that can be accessed individually by index.
If you don't want to rely on the results being returned in the order of the parameters passed to Get-Counter you can select them by their path, e.g. like this:
#{n='WorkingSetPeak';e={
($_.CounterSamples | Where-Object { $_.Path -like '*peak' }).CookedValue / 1KB
}}
For continuous sample collection add the parameters -Continuous and -SampleInterval to Get-Counter. A loop is not required.
$interval = 5 # seconds
Get-Counter -Counter $ws, $wsPeak, $wsPriv -Continuous -SampleInterval $interval |
Select-Object ...

Related

How to exclude part of an output in Powershell?

i am writing a script that accepts the device ID as an argument to check the used percentage of a disk. Here is my code.
$device_id = $args[0]
Get-WmiObject -Class Win32_LogicalDisk |
Select-Object -Property DeviceID,
#{label='UsedPercentage'; expression={[Math]::Round((($_.Size - $_.FreeSpace)/$_.Size) * 100, 2)}} |
findstr $device_id
Here is my output. i am passing an argument to see usage of the device by device ID.
PS D:\Development\Powershell> .\disk-usage.ps1 D:
D: 57.69
What i want to do is to just output that number. How do i do this?
There's no need to use findstr to filter the output. Instead, use the parameter argument to filter your WMI query:
$device_id = $args[0]
# use argument to filter WMI query
Get-WmiObject -Class Win32_LogicalDisk -Filter "DeviceID = '$device_id'" |ForEach-Object {
# output the free space calculation, nothing else
[Math]::Round((($_.Size - $_.FreeSpace)/$_.Size) * 100, 2)
}
You can add the 'used percentage' as a property to the WMI object you get back from your query:
$deviceID = args[0]
$diskUsage = Get-WmiObject -Query "SELECT FreeSpace, Size FROM Win32_LogicalDisk WHERE DeviceID = '$deviceID'" |
Add-Member -MemberType ScriptProperty -Name 'UsedPercentage' -Value {[Math]::Round((($this.Size - $this.FreeSpace)/$this.Size) * 100, 2)} -PassThru
Now, $diskUsage is a WMI object with Size, FreeSpace and UsedPercentage properties (as well as some WMI metadata properties you can ignore). You can output the value of any of them by refering to the one you want:
$diskUsage.UsedPercentage
15.3
Or show them in a neat table:
$diskUsage | Format-Table Size, FreeSpace, UsedPercentage -AutoSize
Size FreeSpace UsedPercentage
---- --------- --------------
1013310287872 858247196672 15.3

PowerShell property expression increases execution time by 4-5 times

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.

output not a trimmed value and causing the output error

I have written few powershell command to perform some auditing on HyperV Clusters. The command works fine, But can anyone help me to trim the output, so that I can collect what I need ?
##Audit-CreatingDC
$AuditDC = Invoke-Command -ComputerName $ComputerName {Get-ChildItem -Path HKLM:\cluster\resources -recurse | get-itemproperty -name CreatingDC -erroraction 'silentlycontinue'}| ft CreatingDC,PSComputerName
####Audit-iSCSI
#Show which hosts are not communicating to the storage with the ‘-s’ and where there are duplicated targets:
$AuditISCSI = Invoke-Command -ComputerName $ComputerName { get-iscsisession } | FT PSComputerName, InitiatorPortalAddress, IsConnected -autosize
######Discover checkdsk errors - "Scan Needed". Execute using txt of one node from each cluster.
$AuditCHKDSK = Invoke-Command -ComputerName $ComputerName { get-volume | Where-Object –FilterScript { $_.HealthStatus -eq "Scan Needed" }} | FT PSComputerName, FileSystem, HealthStatus -autosize
And the output for each is below
CreatingDC PSComputerName
---------- --------------
\\dc-sc-02.oim.corp.com slcoc037
PSComputerName InitiatorPortalAddress IsConnected
-------------- ---------------------- -----------
slcoc037 10.214.61.107 True
PSComputerName FileSystem HealthStatus
-------------- ---------- ------------
slcoc037 CSVFS 1
But I need the output for above in this format
\\dc-sc-02.oim.corp.com
10.241.81.107
CSVFS 1
Can anyone help me to trim these 3 commands ?
You probably already know that almost all powershell outputs are objects. Objects have properties. Displaying a particular property would use the syntax $Object.Propertyname. In your case, CreatingDC is a property of the $AuditDC Variable object. Applying that logic, all you need to do is, display it like this:
$AuditDC.CreatingDC
$AuditISCSI.InitiatorPortalAddress
$AuditCHKDSK.FileSystem

Cmdlet to round numbers in output

Is there a cmdlet that rounds numbers (all float values) in the output?
When I run the following command:
get-vm |select ProvisionedSpaceGB,UsedSpaceGB
I get this output as a table:
ProvisionedSpaceGB UsedSpaceGB
------------------ -----------
1224,0003194380551576614379883 349,88938544876873493194580078
1224,0003062393516302108764648 321,74483488313853740692138672
502,80292716529220342636108399 74,052481059916317462921142578
700,00035238638520240783691406 484,56624550372362136840820312
800,0003144945949316024780273 322,26342210918664932250976562
I am aware that I can define that in the select cmdlet like this:
get-vm | select #{ n="ProvisionedSpaceGB"; e={[math]::round( $_.ProvisionedSpaceGB, 2 )}},
#{ n="UsedSpaceGB"; e={[math]::round( $_.UsedSpaceGB, 2 )}}
And then get this output:
ProvisionedSpaceGB UsedSpaceGB
------------------ -----------
1224,00 349,58
1224,00 320,32
502,80 74,05
700,00 484,57
800,00 322,26
But there must be an easier way, like piping it into another cmdlet:
get-vm |select ProvisionedSpaceGB,UsedSpaceGB |RoundNumbers -Precision 2
To get the second output.
I doubt there is a built-in PowerShell cmdlet for that, however - you could write your own filter:
filter Round-FloatValues
{
Param(
[parameter(ValueFromPipeline=$true)]
$objects,
[int]$Precision = 2
)
$objects.PsObject.Properties | ForEach-Object {
if ($_.TypeNameOfValue -eq 'System.Double')
{
[math]::round( $_.Value, $Precision )
}
else
{
$_.Value
}
}
}
Usage:
get-vm |select ProvisionedSpaceGB,UsedSpaceGB |Round-FloatValues -Precision 2

Powershell free disk space for drives without drive letter

I am working on a power shell script based on http://www.powershellneedfulthings.com/?p=36 to check the disk space for volumes that do not have a driver letter assigned.
The script works pretty well, but I'd like to filter that only drives are shown that have less than 10% free disk space. I'm running into troubles using the where-object filter with hash tables.
# calculations for displaying disk size information
$TotalGB = #{Name="Capacity(GB)";expression={[math]::round(($_.Capacity/ 1GB),2)}}
$FreeGB = #{Name="FreeSpace(GB)";expression={[math]::round(($_.FreeSpace / 1GB),2)}}
$FreePerc = #{Name="Free(%)";expression={[math]::round(((($_.FreeSpace / 1GB)/($_.Capacity / 1073741824)) * 100),0)}}
# array declarations
$volumes = #()
# import server names to check
$servers = (Get-Content .\servers.txt)
# check disk space for volumes without drive letter
foreach ($server in $servers){
$volumes += Get-WmiObject -computer $server win32_volume | Where-Object {$_.DriveLetter -eq $null -and $_.Label -ne "System Reserved"}
}
$volumes | Select SystemName, Label, $TotalGB, $FreeGB, $FreePerc | Format-Table -AutoSize
What I tried is:
Where-Object {$FreePerc -le 10}
The current output is:
SystemName Label Capacity(GB) FreeSpace(GB) Free(%)
---------- ----- ------------ ------------- ----
SERVER01 X:\data\ 9.97 0.89 9
SERVER01 X:\log\ 9.97 1.20 12
SERVER01 X:\info\ 9.97 3.49 35
I'd like to only show the volumes that have less than 10% free disk space. So in this case, only the first entry should be shown.
Thanks!
I think the where clause variable $FreePerc is the issue. Arco had the right idea.
$volumes | Select SystemName, Label, $TotalGB, $FreeGB, $FreePerc | Where-Object {$_.'Free(%)' -le 10} | Format-Table -AutoSize
I put the property in single quotes because i think PowerShell would try to evaluate (%) otherwise. Also to make Arco's solution work it might just be easier to call the Name propery of $FreePerc. That way you only have to update one location
$volumes | Select SystemName, Label, $TotalGB, $FreeGB, $FreePerc | Where-Object {$_.($FreePerc.Name) -le 10} | Format-Table -AutoSize