Get remote machine info, even if there's no session

Welcome Forums General PowerShell Q&A Get remote machine info, even if there's no session

This topic contains 4 replies, has 3 voices, and was last updated by

 
Participant
1 week, 5 days ago.

  • Author
    Posts
  • #126960

    Participant
    Points: 27
    Rank: Member

    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.

  • #126978

    Participant
    Points: 305
    Helping Hand
    Rank: Contributor

    Replace:

    else{ Write-warning "$computer`: No sessions found" }
    

    with:

    else{
        [pscustomobject]@{
            ComputerName = $computer
            Username     = $null
            SessionName  = $unll
            Id           = $null
            State        = $null
            IdleTime     = $null
            LogonTime    = $null
        }
    }
    
  • #127203

    Participant
    Points: 190
    Helping Hand
    Rank: Participant

    To be honest the function seems overly complicated to me.
    If all you want is to check if a user is interactively logged on that is.
    But maybe I missunderstand the goal here.

    The problem with VM's and RDP is that you're not "logged in" to the machine itself.
    You're logged in via an RDP session.
    So the usual methods don't work.

    So to find out if someone is interactively logged in via RDP you can e.g. use the owner of the explorer.exe process.
    Not sure if it's 100% fool proof but I've yet to see an explorer.exe process running without a real user.

    If multiple users are logged in at the same time you will need to do a foreach loop through the results.
    If you want to get each user that is.

    $explorerProcess = Get-WmiObject -Class win32_process -Filter "Name = 'explorer.exe'"
    $user = $explorerProcess.GetOwner().User
    

    Then just assemble the object similar to what Rob writes.
    One for the exist case and the other for not found.

  • #127205

    Participant
    Points: 27
    Rank: Member

    So I have another script that I've used in the past that will get the info if someone was logged in, disconnected, idle, etc, but that went thru PowerCLI and was connecting to each VM in a specific folder with credentials I specified as well that were on each machine.  But what I'm trying to do here is going to expand beyond that to other machines that would have different passwords, but something that Splunk can run on the machines locally.  If someone is logged in then we'll get the info back into Splunk which is great.  However if nobody was logged in it returned that error which wasn't good as there's no data to go back to Splunk.  Typically there should only be one user logged into the machines, as they're typically someone's laptop, desktop or VM.

    The script before just did a simple query user /server:$vm on the VM's, but it appears I need something a little more than that to get the data that's needed, hence why I used the function that I found in the original post.  The idea is we keep track of who's logged in to a machine and who isn't.  If we see a machine that hasn't been logged into for a long time we can talk to the owner if it's needed or not, if we can delete, decommission, etc.  Hope this helps clarify things.

  • #127344

    Participant
    Points: 190
    Helping Hand
    Rank: Participant

    If the function works but it's the "No session found" case that is the issue.
    Then just do what Rob posted, that way you'll get an output to Splunk even if there are no sessions.

You must be logged in to reply to this topic.