Hello,
So I’m tasked with getting the info on a machine whether it’s in use or not. The idea is that we’re tracking if machines are in use and if they’re not we can ask the user if the machine will be used and if not we can delete. This would be deployed thru Splunk so the script would be run on each machine locally. This is the tricky part it seems. If you’re logged in you get a result, if not you get no session found. The function I’m using isn’t mine, it’s one I found on google, but gives me the results I want.
function Get-UserSession {
<#
.SYNOPSIS
Retrieves all user sessions from local or remote computers(s)
.DESCRIPTION
Retrieves all user sessions from local or remote computer(s).
Note: Requires query.exe in order to run
Note: This works against Windows Vista and later systems provided the following registry value is in place
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\AllowRemoteRPC = 1
Note: If query.exe takes longer than 15 seconds to return, an error is thrown and the next computername is processed. Suppress this with -erroraction silentlycontinue
Note: If $sessions is empty, we return a warning saying no users. Suppress this with -warningaction silentlycontinue
.PARAMETER computername
Name of computer(s) to run session query against
.parameter parseIdleTime
Parse idle time into a timespan object
.parameter timeout
Seconds to wait before ending query.exe process. Helpful in situations where query.exe hangs due to the state of the remote system.
.FUNCTIONALITY
Computers
.EXAMPLE
Get-usersession -computername "server1"
Query all current user sessions on 'server1'
.EXAMPLE
Get-UserSession -computername $servers -parseIdleTime | ?{$_.idletime -gt [timespan]"1:00"} | ft -AutoSize
Query all servers in the array $servers, parse idle time, check for idle time greater than 1 hour.
.NOTES
Thanks to Boe Prox for the ideas - http://learn-powershell.net/2010/11/01/quick-hit-find-currently-logged-on-users/
.LINK
http://gallery.technet.microsoft.com/Get-UserSessions-Parse-b4c97837
#>
[cmdletbinding()]
Param(
[Parameter(
Position = 0,
ValueFromPipeline = $True)]
[string[]]$computername = "localhost",
[switch]$parseIdleTime,
[validaterange(0,120)]$timeout = 15
)
ForEach($computer in $computername) {
#start query.exe using .net and cmd /c. We do this to avoid cases where query.exe hangs
#build temp file to store results. Loop until this works
Do{
$tempFile = [System.IO.Path]::GetTempFileName()
start-sleep -Milliseconds 300
}
Until(test-path $tempfile)
#Record date. Start process to run query in cmd. I use starttime independently of process starttime due to a few issues we ran into
$startTime = Get-Date
$p = Start-Process -FilePath C:\windows\system32\cmd.exe -ArgumentList "/c query user /server:$computer > $tempfile" -WindowStyle hidden -passthru
#we can't read in info or else it will freeze. We cant run waitforexit until we read the standard output, or we run into issues...
#handle timeouts on our own by watching hasexited
$stopprocessing = $false
do{
#check if process has exited
$hasExited = $p.HasExited
#check if there is still a record of the process
Try { $proc = get-process -id $p.id -ErrorAction stop }
Catch { $proc = $null }
#sleep a bit
start-sleep -seconds .5
#check if we have timed out, unless the process has exited
if( ( (Get-Date) - $startTime ).totalseconds -gt $timeout -and -not $hasExited -and $proc){
$p.kill()
$stopprocessing = $true
remove-item $tempfile -force
Write-Error "$computer`: Query.exe took longer than $timeout seconds to execute"
}
}
until($hasexited -or $stopProcessing -or -not $proc)
if($stopprocessing){ Continue }
#if we are still processing, read the output!
$sessions = get-content $tempfile
remove-item $tempfile -force
#handle no results
if($sessions){
1..($sessions.count -1) | % {
#Start to build the custom object
$temp = "" | Select ComputerName, Username, SessionName, Id, State, IdleTime, LogonTime
$temp.ComputerName = $computer
#The output of query.exe is dynamic.
#strings should be 82 chars by default, but could reach higher depending on idle time.
#we use arrays to handle the latter.
if($sessions[$_].length -gt 5){
#if the length is normal, parse substrings
if($sessions[$_].length -le 82){
$temp.Username = $sessions[$_].Substring(1,22).trim()
$temp.SessionName = $sessions[$_].Substring(23,19).trim()
$temp.Id = $sessions[$_].Substring(42,4).trim()
$temp.State = $sessions[$_].Substring(46,8).trim()
$temp.IdleTime = $sessions[$_].Substring(54,11).trim()
$logonTimeLength = $sessions[$_].length - 65
try{
$temp.LogonTime = get-date $sessions[$_].Substring(65,$logonTimeLength).trim()
}
catch{
$temp.LogonTime = $sessions[$_].Substring(65,$logonTimeLength).trim() | out-null
}
}
#Otherwise, create array and parse
else{
$array = $sessions[$_] -replace "\s+", " " -split " "
$temp.Username = $array[1]
#in some cases the array will be missing the session name. array indices change
if($array.count -lt 9){
$temp.SessionName = ""
$temp.Id = $array[2]
$temp.State = $array[3]
$temp.IdleTime = $array[4]
$temp.LogonTime = get-date $($array[5] + " " + $array[6] + " " + $array[7])
}
else{
$temp.SessionName = $array[2]
$temp.Id = $array[3]
$temp.State = $array[4]
$temp.IdleTime = $array[5]
$temp.LogonTime = get-date $($array[6] + " " + $array[7] + " " + $array[8])
}
}
#if specified, parse idle time to timespan
if($parseIdleTime){
$string = $temp.idletime
#quick function to handle minutes or hours:minutes
function convert-shortIdle {
param($string)
if($string -match "\:"){
[timespan]$string
}
else{
New-TimeSpan -minutes $string
}
}
#to the left of + is days
if($string -match "\+"){
$days = new-timespan -days ($string -split "\+")[0]
$hourMin = convert-shortIdle ($string -split "\+")[1]
$temp.idletime = $days + $hourMin
}
#. means less than a minute
elseif($string -like "." -or $string -like "none"){
$temp.idletime = [timespan]"0:00"
}
#hours and minutes
else{
$temp.idletime = convert-shortIdle $string
}
}
#Output the result
$temp
}
}
}
else{ Write-warning "$computer`: No sessions found" }
}
}
I added that to a script that has the following:
$computer = hostname
Get-UserSession -Computer $computer
When logged in I get something like this:
ComputerName : mymachine.domain Username : myuser SessionName : rdp-tcp#4 Id : 1 State : Active IdleTime : 10 LogonTime : 11/13/2018 8:07:00 AM
When nobody is logged into the machine I get this:
WARNING: localhost: No sessions found
And I don’t want to see that. I would rather see this:
ComputerName : mymachine.domain Username : SessionName : Id : 1 State : IdleTime : LogonTime :
So is that possible to achieve? I’ve even tried running this on some VM’s using PowerCLI and running the script on the machine when logging in with local admin credentials, but I still get the Warning error above. If this particular function doesn’t work does anybody know of a different one that might do what I ask? Apparently we can’t have it return nothing in splunk, so something is better than nothing.