I'm trying to write a script that fetches the CPU temperature using OpenHardwareMonitor and will alert if it averages above a certain threshold over a given duration.
Getting the current temperature is working perfectly, but I'm struggling to average them out because on some systems there may be one or more CPU so I can't just log the temperature every minute and divide by how many minutes have passed, below is the best I've got (that doesn't work), please could some kind soul help me see where I'm going wrong?
$tempThreshold = 80 # Will alert if average temperature is above this
$minutes = 3 # Duration in minutes to test for
start-process "$env:SystemRoot\TEMP\OpenHardwareMonitor\openhardwaremonitor.exe"
# Loop each minute
for ($i=1; $i -le $minutes; $i++) {
$arrSensors=#{}
if ((Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT * FROM Sensor WHERE Sensortype='Temperature'" | ? {$_.Name -match 'CPU Package'}).value) {
Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT * FROM Sensor WHERE Sensortype='Temperature'" | ? {$_.Name -Match "CPU Package"} | % {$arrSensors[$_.Name]=$_.Value}
} else {
Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT * FROM Sensor WHERE Sensortype='Temperature'" | ? {$_.Identifier -match 'cpu/'} | % {$arrSensors[$_.Name]=$_.Value}
}
$temps = foreach ($sensor in $arrSensors.getEnumerator()) {
Start-Sleep -Seconds 60
$temp = $temp+$sensor.value
}
}
# Alert if average above threshold
if ($temps/$minutes -gt $tempThreshold) {
Write-Host "Alert function here"
}
i was also looking for monitoring cpu temperature on windows. my backend was grafana with an influxdb. using ohm and wmi was very cpu intense and i found https://github.com/nickbabcock/OhmGraphite#influxdb-configuration. this app is based on ohm but sends the data directly to the influxdb (no expensive wmi-calls needed). maybe this is also of interest for you. using grafana you could average the value and set a threshold there.
but - maybe that is not an option for you so here the script to get the average using the ohm application.
the downside of this script is, that the script runs for 2 minutes - so you won't get the alert every minute (unless you run it twice with a minute appart (which could be easily setup using scheduled tasks).
$tempThreshold = 80 # Will alert if average temperature is above this
$minutes = 3 # Duration in minutes to test for
start-process "$env:SystemRoot\TEMP\OpenHardwareMonitor\openhardwaremonitor.exe"
$counter = 0
$sum = 0
while($counter -lt $minutes) {
$cpuArray = Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT * FROM Sensor WHERE Sensortype='Temperature'"
if($cpuArray | where name -like 'cpu package*') {
$cpuTempArray = $cpuArray | where name -like 'cpu package*'
} else {
$cpuTempArray = $cpuArray | where name -like 'cpu core*'
}
foreach($cpuTemp in $cpuTempArray){
$sum += $cpuTemp.value
}
$counter++
if($counter -ne $minutes) {
Start-Sleep -Seconds 60
}
}
$average = $sum/($counter*$cpuTempArray.Count)
# Alert if average above threshold
if($average -gt $tempThreshold) {
Write-Host "Alert function here"
}
as mentioned my other answer the other script runs once and IMHO cannot be seen as "monitoring" - as in 'alert whenever the threshold is hit' - like ever minute.
the following script runs in an endless loop. it collects the set amount of data ($minutes) as average of every minutes. you might wanna change this to store the max value of all cpus in the $valueArray and then take the average of this.
$tempThreshold = 80 # Will alert if average temperature is above this
$minutes = 3 # Duration in minutes to test for
Start-Process -FilePath "$env:SystemRoot\TEMP\OpenHardwareMonitor\openhardwaremonitor.exe"
$valueArray = [System.Collections.ArrayList]#()
while($true) {
# keep only #no of values in array specified in $minutes
if($valueArray.Count -gt $minutes) {
# remove oldest entry from array
$valueArray.RemoveAt(0)
}
if($valueArray.Count -eq $minutes) {
$average = ($valueArray | Measure-Object -Sum).Sum/$minutes
# Alert if average above threshold
if($average -gt $tempThreshold) {
Write-Host "Alert function here"
}
}
$sensorArray = Get-WmiObject -Namespace "Root\OpenHardwareMonitor" -Query "SELECT * FROM Sensor WHERE Sensortype='Temperature'"
if($sensorArray | where name -like 'asdf cpu package*') {
$cpuTempArray = $sensorArray | where name -like 'cpu package*'
} else {
$cpuTempArray = $sensorArray | where name -like 'cpu core*'
}
$counter = 0
$sum = 0
foreach($cpuTemp in $cpuTempArray) {
$counter++
$sum += $cpuTemp.value
}
$currentAverageTemp = $sum/$counter
$valueArray.Add($currentAverageTemp)
Start-Sleep -Seconds 60
}
Related
ive been dabbling with powershell for a while now and ive been trying to modify some data in an array.
Problem is that my source array is very large and this script takes hours to run. Maybe someone can help my optimize my script.
With a small source array the script runs just fine btw.
$array_metric_hour = #()
$array_metric_hour =
foreach ($resource in $resources) {
Write-Progress -Id 0 "Step $resource"
foreach ($hour in $Time_Array) {
Write-Progress -Id 1 -ParentId 0 "Step $resource - Substep" ($hour.timestamp+":00")
[pscustomobject] #{
resourceID = $resource
resourceName = $array_bill.resources.($resource).name
time = $hour.timestamp+":00"
Poweredon = ((($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp}).poweredon | Measure-Object -Maximum).Maximum)
#Cpu_On = if (($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -eq "0,0"}).poweredon) {0} else {(($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -ne "0,0"}).provisionedCpu | Measure-Object -Maximum).Maximum}
Mem_GB_On = if (($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -eq "0,0"}).poweredon) {0} else {(($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp -and $_.poweredOn -ne "0,0"}).provisionedMem_GB | Measure-Object -Maximum).Maximum}
hardware_Diskspace_GB = ((($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp}).hardware_Diskspace_GB | Measure-Object -Maximum).Maximum)
#used_Diskspace_GB = ((($Array_combined | Where-Object {$_.resourceID -eq $resource -and $_.hour -eq $hour.timestamp}).used_Diskspace_GB | Measure-Object -Maximum).Maximum)
}
}
}
Some extra information that is required:
$Time_array has every full hour in a month, so 745 values in this case.
$array_combined exist of 98131 lines (5 minute interval with metrics during a month.
this array has the folowing items per interval.
resourceID
resourceName
timestamps
human_timestamp
hour
date
poweredOn
provisionedMem_GB
hardware_Diskspace_GB
used_Diskspace_GB
thanks for all the comments, next time ill try to supply all and correct information.
The suggestions of creating an extra filter was the winner for me. the scripts is 50 times faster in the current state and for now quick enough.
Added $filter1 and $filter2.
$array_metric_hour = #()
$array_metric_hour =
foreach ($resource in $resources) {
$filter1 = $Array_combined | Where-Object {$_.resourceID -eq $resource}
Write-Progress -Id 0 "Step $resource"
foreach ($hour in $Time_Array) {
$filter2 = $filter1 | Where-Object {$_.hour -eq $hour.timestamp}
Write-Progress -Id 1 -ParentId 0 "Step $resource - Substep" ($hour.timestamp+":00")
[pscustomobject] #{
resourceID = $resource
resourceName = $array_bill.resources.($resource).name
time = $hour.timestamp+":00"
Poweredon = if ($filter2 | where poweredOn -eq "0,0") {"0"} else {($filter2.poweredOn | Measure-Object -Maximum).Maximum}
Mem_GB_On = if ($filter2 | where poweredOn -eq "0,0") {0} else {(($filter2).provisionedMem_GB | Measure-Object -Average).Average}
hardware_Diskspace_GB = ((($filter2).hardware_Diskspace_GB | Measure-Object -Average).Average)
}
}
}
Since you're still not have provided much more of your code (e.g. where does "Array_combined" come from?), here are some important notes:
Don't use "Write-Progress" on every iteration! It has a very very huge impact on performace when using PS <=5.1, 6 and 7.
With the current "7.1" build I am using it works like a charm ("7.1.0-preview.7"). Have to look when they fixed it.
Avoid pipe'ing as much as you can when you want to have the best performance. Streaming data from one command to the other is compared to statements like "foreach {}" (NOT "Foreach-Object"!) really bad.
Here is an example for your template, even when there are some important steps missing:
# Progress bar definition
$progressActivity1 = 'Processing items'
$progressCounter1 = -1
$progressMax1 = #($resources).Count
$progressInterval1 = [math]::Ceiling($progressMax1 * 0.1) # each 10%
$progressId1 = 1
$progressParentId1 = 0
# *** use a list if your script adds objects several times.
# *** Note: "Arrays" are immutable and will be re-created each time you add something
$array_metric_hour = [System.Collections.Generic.List[psobject]]::new()
# *** good approach to add the result of a forEach-statement directly to variable. Performance is similar compared to adding objects to a list.
$array_metric_hour = foreach ($resource in $resources) {
# Progress bar counter & drawing (each 10%)
$progressCounter1++
If ($progressCounter1 % $progressInterval1 -eq 0) {
Write-Progress -Activity $progressActivity1 -PercentComplete($progressCounter1 / $progressMax1 * 100) -Id $progressId1 -ParentId $progressParentId1
}
# *** "Array_combined" is unknwon.... but according to the usage:
# !!! try to create a dictionary/hashtable of "Array_combined" with "resourceID" as key.
# !!! hash tables/dictionaries are much faster to access a particular item than arrays
# !!! access would be: $filter1 = $Array_combined[$resource]
$filter1 = $Array_combined | Where-Object { $_.resourceID -eq $resource }
# Progress bar definition
$progressActivity2 = 'Processing items'
$progressCounter2 = -1
$progressMax2 = #($Time_Array).Count
$progressInterval2 = [math]::Ceiling($progressMax2 * 0.1) # each 10%
$progressId2 = 2
$progressParentId2 = $progressId1
foreach ($hour in $Time_Array) {
# ??? don't know what $filter1 is about ...
# !!! replace that; use a hastable/dictionary
# !!! alternatively: use "foreach"-statement OR method ".where{}" which was introduced in PS 4.0
$filter2 = $filter1 | Where-Object { $_.hour -eq $hour.timestamp }
# Progress bar counter & drawing (each 10%)
$progressCounter2++
If ($progressCounter2 % $progressInterval2 -eq 0) {
Write-Progress -Activity $progressActivity2 -PercentComplete($progressCounter2 / $progressMax2 * 100) -Id $progressId2 -ParentId $progressParentId2
}
[pscustomobject] #{
resourceID = $resource
resourceName = $array_bill.resources.($resource).name
time = $hour.timestamp + ':00'
# ??? "Where-Object" could be replaced ... but don't know the background or data ....
# !!! replace "Measure-Object" with "[Linq.Enumerable]" methods if possible
Poweredon = if ($filter2 | Where-Object poweredOn -EQ '0,0') { '0' } else { ($filter2.poweredOn | Measure-Object -Maximum).Maximum }
# !!! same as above
Mem_GB_On = if ($filter2 | Where-Object poweredOn -EQ '0,0') { 0 } else { (($filter2).provisionedMem_GB | Measure-Object -Average).Average }
# !!! same as above
hardware_Diskspace_GB = ((($filter2).hardware_Diskspace_GB | Measure-Object -Average).Average)
}
}
}
# Progress completed
Write-Progress -Activity $progressActivity -Completed -Id $progressId1
Write-Progress -Activity $progressActivity -Completed -Id $progressId2
On Windows 10, in task manager/performance it gives the values for 'Receive' and 'Send' for network adapters. I got curious as to how MS does this so I thought of performance counters in powershell. So I cobbled together a quick bit of code to experiment with, put it in a background job, this:
$Computer = hostname;
$sndrecsum = {((get-counter -Counter '\network adapter(*)\Bytes Received/sec' -MaxSamples 30).CounterSamples | measure cookedvalue –Sum).Sum};
$sptstb = New-Object System.Net.WebClient;
$sptstb.DownloadFile('http://client.akamai.com/install/test-objects/10MB.bin', 'Out-Null');
Start-Job -Name sxrxs -scriptblock $sndrecsum -ArgumentList $Computer;
Get-Job -Name sxrxs | Wait-Job; $sndrecsumout = Get-Job -Name sxrxs | Receive-Job; ($sndrecsumout | measure -Maximum).Maximum
here's the issue. The output '($sndrecsumout | measure -Maximum).Maximum' should be getting the maximum value in the array $sndrecsumout. It does do that but the max value in the array is always wayyyy lower than it should be and never matches that shown in task manager/performance for 'Receive' and 'Send' (after conversion to the prevailing units shown in task manager/performance at the same time). So i'm not sure if task manager/performance is using performance counters or if i'm screwing up somewhere.
Anyway, two questions: First, how does task manager/performance get the values for 'Receive' and 'Send'? - and - Two, am I screwing up with the way i'm trying to do this?
Thanks for replies and advice in advance.
ok, think i've got it figured out here, at least a little, maybe. This tracks with the Windows 10 performance monitor for the interface;
$sptst = New-Object System.Net.WebClient; $Computer = hostname; $strlf += $("" | Out-String);
$totx = {
get-counter -Counter '\Network Interface(Intel[R] Ethernet Connection [2] I218-V)\Bytes Total/sec' -MaxSamples 40 | ForEach {[math]::Round((($_.countersamples.cookedvalue | measure -sum).sum / 1Mb), 4)}
};
$recx = {
get-counter -Counter '\network Interface(Intel[R] Ethernet Connection [2] I218-V)\Bytes Received/sec' -MaxSamples 40 | ForEach {[math]::Round((($_.countersamples.cookedvalue | measure -sum).sum / 1Mb), 4)}
};
$sntx = {
get-counter -Counter '\network Interface(Intel[R] Ethernet Connection [2] I218-V)\Bytes Sent/sec' -MaxSamples 40 | ForEach {[math]::Round((($_.countersamples.cookedvalue | measure -sum).sum / 1Mb), 4)}
};
Start-Job -Name xtot -scriptblock $totx -ArgumentList $Computer;
Start-Job -Name xrec -scriptblock $recx -ArgumentList $Computer;
Start-Job -Name xsnt -scriptblock $sntx -ArgumentList $Computer;
$ts = Measure-Command -Expression {$sptst.DownloadFile('http://testmy.net/dl-10MB', 'Out-Null')}; $dxtmex = [math]::Round($ts.TotalSeconds, 4);
Get-Job -Name xtot -HasMoreData $True | Wait-Job ; $outxtot = Get-Job -Name xtot | Receive-Job;
$xthrx = ($outxtot | measure -Maximum).Maximum; $testnuma = $xthrx -match '^[\d\.]+$'; if ($testnuma -eq 'True') {$MBxthrx = $xthrx; $Mbpsxthrx = $xthrx / 0.125};
Get-Job -Name xrec -HasMoreData $True | Wait-Job; $outxrec = Get-Job -Name xrec | Receive-Job;
$xrecx = ($outxrec | measure -Maximum).Maximum ; $testnumb = $xrecx -match '^[\d\.]+$'; if ($testnumb -eq 'True') {$MBxrecx = $xrecx; $Mbpsxrecx = $xrecx / 0.125};
Get-Job -Name xsnt -HasMoreData $True | Wait-Job; $outxsnt = Get-Job -Name xsnt | Receive-Job;
$xsntx = ($outxsnt | measure -Maximum).Maximum ; $testnumb = $xsntx -match '^[\d\.]+$'; if ($testnumb -eq 'True') {$MBxsntx = $xsntx; $Mbpsxsntx = $xsntx / 0.125};
$txtl = '|Throughput = ' + $Mbpsxthrx + ' Mbps (' + $MBxthrx + ' MB/s) [ Test Time = ' + $dxtmex + ' seconds ]';
$rxsx = 'Received (D/L) : ' + $Mbpsxrecx + ' Mbps (' + $MBxrecx + ' MB/s)' + ' / Sent (U/L) : ' + $Mbpsxsntx + ' Mbps (' + $MBxsntx + ' MB/s)';
Return $txtl + $strlf + $rxsx
Edit: removed previous answer - this one is much better and improved. Ignore the labels 'Throughput' - 'Received (D/L)' - 'Sent (U/L)' as just called them something to differentiate while playing around with it so call them anything you wish if desired. 'Throughput' in the windows performance monitor is expressed as a percentage and not like this anyway but the results expressed in 'Throughput' above code when converted for percentage in math (not shown in above code) seem to track with the perf monitor which is why I chose the label 'Throughput' here. Also during this figured out how to get the correct instance name for the interface for use in the counters, I don't show its use (or the code for it) this post, and just put the interface name in above code, to keep strictly on topic but if someone wants to see it and its OK to do i'll post it here also. I'll just probably come back later and post it anyway if its Ok in this post and if not ya'll can remove it if you wish if it would be off topic some. Any improvements/suggestions welcome.
Thank You.
I want to get the CPU usage % (not processor time) of a particular process using a powershell command.
Example: (Windows 8 Task Manager)
I want to get that 2.9% with a command.
Here is the correct answer which is support case then you have multiple processs with same name https://stackoverflow.com/a/34844682/483997
# To get the PID of the process (this will give you the first occurrance if multiple matches)
$proc_pid = (get-process "slack").Id[0]
# To match the CPU usage to for example Process Explorer you need to divide by the number of cores
$cpu_cores = (Get-WMIObject Win32_ComputerSystem).NumberOfLogicalProcessors
# This is to find the exact counter path, as you might have multiple processes with the same name
$proc_path = ((Get-Counter "\Process(*)\ID Process").CounterSamples | ? {$_.RawValue -eq $proc_pid}).Path
# We now get the CPU percentage
$prod_percentage_cpu = [Math]::Round(((Get-Counter ($proc_path -replace "\\id process$","\% Processor Time")).CounterSamples.CookedValue) / $cpu_cores)
Get-Process -Name system | select CPU
Get the cpu time at 2 instance as (cpu2-cpu1)/(t2-t1)*100. You will get CPU value in %.
Get-Process -Name system | select CPU # Get the cpu time at 2 instance as (cpu2-cpu1)/(t2-t1)*100. You will get CPU value in %.
$processName = 'OUTLOOK'
$sleep_time = 1 # value in seconds
while (1) {
$CPU_t1 = Get-Process -Name $processName | Select CPU
$CPU_t1_sec = $($CPU_t1.CPU)
#Write-Host "CPU_t1: $($CPU_t1.CPU)"
$date1 = (Get-Date)
sleep -Seconds $sleep_time
$CPU_t2=Get-Process -Name $processName | Select CPU
#Write-Host "CPU_t2: $($CPU_t2.CPU)"
$CPU_t2_sec = $($CPU_t2.CPU)
$date2 = (Get-Date)
$date_diff = $date2 - $date1
$diff_time = $date_diff.seconds
#Write-Host "TimeDiff: $diff_time"
#compute them to get the percentage
$CPU_Utilization = ($CPU_t2_sec - $CPU_t1_sec)/$diff_time
$CPU_Utilization_per = $CPU_Utilization * 100
#Sleep $sleep_time
Clear-Host Write-Host "CPU_Utilization_Per: $CPU_Utilization_per"
#Write-Host "====================="
}
Get-Process -Name PowerShell | Select CPU
Is this what you're looking for?
Or something more monitoring based?
param
(
[String]
[Parameter(Mandatory)]
$Title
)
do
{
$process = Get-Process -Name $Title
$process
Start-Sleep -Seconds 1
} while ($process)
I have a script that can shutdown vms based on names entered into an array.
That part works fine, but the next part after the shutdown initiates is supposed to wait for a period and notify how many vms it is waiting to shut down before it moves on to another phase of shutting down other vms.
I am not getting any vms counted out of the array. Here is the code for a particular phase:
$waittime = 5 #Seconds
#Create Phase 1 Array
[Array] $PHASE1 = "TestSvr2008"
# For each of the VMs on the Hyperv hosts that are powered on
Foreach ($VM in ($PHASE1 | %{ Get-VM | Where { $_.State -eq "Running" }})){
# Shutdown the guest cleanly
$VM | Stop-VM -Confirm:$false }
# Set the amount of time to wait before assuming the remaining powered on guests are stuck
$waittime = 120 #Seconds
$Time = (Get-Date).TimeofDay
do {
# Wait for the VMs to be Shutdown cleanly
sleep 1.0
$timeleft = $waittime - ($Newtime.seconds)
$numvms = ($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count
Write "Waiting for shutdown of $numvms VMs or until $timeleft seconds"
$Newtime = (Get-Date).TimeofDay - $Time
} until ((#($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count) -eq 0 -or ($Newtime).Seconds -ge $waittime)
Thanks
This line in do and until blocks has issues:
$numvms = ($PHASE1 | %{ Get-VM | Where { $_.$VM -eq $PHASE1 }}).Count
$_.$VM is typo, it should be $_.VM
$_.VM -eq $PHASE1 tries to check if VM name is equal to array. Comparisons do not work that way.
ForeEach-Object is unnecessary
Get-Vm accepts array of VM names by pipeline or directly as Name parameter. So you can do this:
$numvms = ($PHASE1 | Get-VM).Count
or
$numvms = (Get-VM -Name $PHASE1).Count
I am working on query user command in PowerShell to filter the content to get the users who wer disconnected for more than 2 days on the server.
This is my result:
USERNAME SESSIONNAME ID STATE IDLE TIME LOGON TIME
a_admin 2 Disc 20+16:56 19.08.2015
b_admin 3 Disc . 10.12.2015
c_admin 4 Disc 5+22:33 24.08.2015
d_admin 5 Disc 17:47 17.12.2015
e_admin 6 Disc 101+18:58 02.09.2015
f_admin 7 Disc 1+01:27 14.12.2015
The problem is the query user don't retrieve the data as an object format, so I can't select any column from these data, can any one help me to find a way to filter this content? Also, I am having a problem in the content of the idle time. It seems weird!?
I tried to put the output in a text file then get the content back and do some filtration, but the result is the same (USERNAME with empty records).
query user produces string output. You can't convert that to objects by piping it into Format-Table. And Select-Object won't do with the output of Format-Table what you seem to expect anyway.
Use a regular expression match to transform the string output into a list of objects:
$server = 'servername'
$re = '(\w+)\s+?(\S*)\s+?(\d+)\s+Disc\s+(\S+)\s+(\d+\.\d+\.\d+)'
query user /server:$server | Where-Object { $_ -match $re } | ForEach-Object {
New-Object -Type PSCustomObject -Property #{
'Username' = $matches[1]
'SessionID' = $matches[3]
'IdleTime' = $matches[4]
'LogonTime' = $matches[5]
}
} | Select-Object Username, IdleTime
This will give you everything as string values, though. Since you want to filter on the idle time you may want to convert the values to appropriate types. Using a more elaborate regular expression (with named groups) will help with that.
$server = 'servername'
$re = '(?<username>\w+)\s+?' +
'(\S*)\s+?' +
'(?<session>\d+)\s+' +
'Disc\s+' +
'(?:(?:(?<days>\d+)\+)?(?<hours>\d+):)?(?<minutes>\d+)\s+' +
'(?<logon>\d+\.\d+\.\d+)'
query user /server:$server | Where-Object { $_ -match $re } | ForEach-Object {
New-Object -Type PSCustomObject -Property #{
'Username' = $matches['username']
'SessionID' = [int]$matches['session']
'IdleTime' = if ($matches['days']) {
New-TimeSpan -Days $matches['days'] -Hours $matches['hours'] -Minutes $matches['minutes']
} elseif ($matches['hours']) {
New-TimeSpan -Hours $matches['hours'] -Minutes $matches['minutes']
} else {
New-TimeSpan -Minutes $matches['minutes']
}
'LogonTime' = [DateTime]::ParseExact($matches['logon'], 'dd\.MM\.yyyy', [Globalization.CultureInfo]::InvariantCulture)
}
} | Where-Object {
$_.IdleTime.TotalDays -gt 2
} | Select-Object Username, IdleTime