Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Last Logged on User scanner doesn't always detect the last logged on user. #110

Open
ccheath opened this issue Jun 7, 2024 · 2 comments

Comments

@ccheath
Copy link

ccheath commented Jun 7, 2024

I've been noticing that a lot (about 80%) of our computers do not have a last logged on user even though the scan is completing every time it runs.

So I began to do some testing on these computers that were scanning successfully but returning no data.

At first I thought that it was because of the "-newest 200" flag in the Get-EventLog cmdlet.
So I changed it to "-newest 2000" without any change in results.
Then I removed the "-newest" flag altogether to get all the events possible, but still no results.

I then moved my attention to the if statement below looking at the $LogonType variable.
This turned out to be the issue. There were no types matching "2", "10" or "11".
All we were getting were "3" and "5" types, and none of those were for the logged on user.
They were for the computer account, SYSTEM, and my remote PSSessions.

In a last-ditch effort to try and figure out a way to get some more data out of this scanner I turned to the currently logged in user.
I modified the script to check for the currently logged in user and if that was different that any of the users that were collected by the for loop above in the $UserArray variable. If there is a currently logged in user and it is not in that array then add it to the PSCustomObject to be returned with a logon type of "Current User" and the current timestamp for the last logon field.

Originally I was doing this check with a Get-CIMInstance cmdlet but it seemed to be failing for RDP and VDI users.
I then tried a few other options (WMI, owner of the explorer.exe process, etc) but they also did not work consistently.
Eventually I landed on the quser command and parsing the output of that which seems to be working well for me.

Here is my modified script:

# This script requires that Audit Logon events are enabled in Group Policy and those events are kept for the amount of history preferred

[CmdletBinding()]
param (
    [Switch]$Lowercase
)

$UserArray = New-Object System.Collections.ArrayList
# Query all logon events with id 4624 
Get-EventLog -LogName "Security" -Newest 200 -InstanceId 4624 -ErrorAction "SilentlyContinue" | ForEach-Object {
    $EventMessage = $_
    $AccountName = $EventMessage.ReplacementStrings[5]
    $LogonType = $EventMessage.ReplacementStrings[8]
    if ( $Lowercase ) {
        # Make all usernames lowercase so they group properly in Inventory
        $AccountName = $AccountName.ToLower()
    }
    # Look for events that contain local or remote logon events, while ignoring Windows service accounts
    if ( ( $LogonType -in "2", "10", "11" ) -and ( $AccountName -notmatch "^(DWM|UMFD)-\d" -and ($AccountName -ne "") ) ) {
        # Skip duplicate names
        if ( $UserArray -notcontains $AccountName ) {
            $null = $UserArray.Add($AccountName)            
            # Translate the Logon Type
            if ( $LogonType -eq "2" ) {
                $LogonTypeName = "Local"
            }
            elseif ( $LogonType -eq "10" ) {
                $LogonTypeName = "Remote"
            }
            elseif ( $LogonType -eq "11" ) {                
                $LogonTypeName = "Cached"
            }
			$time = [DateTime]$EventMessage.TimeGenerated.ToString("yyyy-MM-dd HH:mm:ss")
            # Build an object containing the Username, Logon Type, and Last Logon time
            [PSCustomObject]@{
                Username  = $AccountName
                LogonType = $LogonTypeName
                LastLogon = $time
            }
        }
    }    
}
# Get the current logged in user in case nothing is returned by the above
#$userName = (Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty UserName).Split('\')[1]
$queryUser = quser
$userName = $null
if ($queryUser) {
    $userName = $queryUser -match ' (\S+)\s+\d+ '
    if ($matches) {
        $userName = $matches[1]
    }
}
# Return if no username found via quser
if ( $null -eq $userName ) {
	return
}
$userName = $userName.Substring(1)
$userName = $userName.Split(" ")[0]
if ( $Lowercase ) {
    # Make all usernames lowercase so they group properly in Inventory
    $userName = $userName.ToLower()
}
if ($null -ne $userName -and $UserArray -notcontains $userName) {
    [PSCustomObject]@{
        Username  = $userName
        LogonType = "Current User"
        LastLogon = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }
}
@ccheath
Copy link
Author

ccheath commented Jun 25, 2024

modified the script to handle when quser returns errors because no user is logged on or other error conditions

# This script requires that Audit Logon events are enabled in Group Policy and those events are kept for the amount of history preferred

[CmdletBinding()]
param (
    [Switch]$Lowercase
)

$UserArray = New-Object System.Collections.ArrayList
# Query all logon events with id 4624 
Get-EventLog -LogName "Security" -Newest 200 -InstanceId 4624 -ErrorAction "SilentlyContinue" | ForEach-Object {
    $EventMessage = $_
    $AccountName = $EventMessage.ReplacementStrings[5]
    $LogonType = $EventMessage.ReplacementStrings[8]
    if ( $Lowercase ) {
        # Make all usernames lowercase so they group properly in Inventory
        $AccountName = $AccountName.ToLower()
    }
    # Look for events that contain local or remote logon events, while ignoring Windows service accounts
    if ( ( $LogonType -in "2", "10", "11" ) -and ( $AccountName -notmatch "^(DWM|UMFD)-\d" -and ($AccountName -ne "") ) ) {
        # Skip duplicate names
        if ( $UserArray -notcontains $AccountName ) {
            $null = $UserArray.Add($AccountName)            
            # Translate the Logon Type
            if ( $LogonType -eq "2" ) {
                $LogonTypeName = "Local"
            }
            elseif ( $LogonType -eq "10" ) {
                $LogonTypeName = "Remote"
            }
            elseif ( $LogonType -eq "11" ) {                
                $LogonTypeName = "Cached"
            }
			$time = [DateTime]$EventMessage.TimeGenerated.ToString("yyyy-MM-dd HH:mm:ss")
            # Build an object containing the Username, Logon Type, and Last Logon time
            [PSCustomObject]@{
                Username  = $AccountName
                LogonType = $LogonTypeName
                LastLogon = $time
            }
        }
    }    
}
# Get the current logged in user in case nothing is returned by the above
#$userName = (Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty UserName).Split('\')[1]
$queryUser = quser 2>&1
If ($LASTEXITCODE -ne 0) {
    $ErrorDetail = $queryUser.Exception.Message[1]
    Switch -Wildcard ($queryUser) {
        '*[1722]*' { 
            $Status = 'Remote RPC not enabled'
        }
        '*[5]*' {
            $Status = 'Access denied'
        }
        'No User exists for*' {
            $Status = 'No logged on users found'
        }
        default {
            $Status = 'Error'
            $ErrorDetail = $queryUser.Exception.Message
        }
    }
    [pscustomobject]@{ErrorStatus = $Status;ErrorMessage = $ErrorDetail}
    $queryUser = $null
}
$userName = $null
if ($queryUser) {
    $userName = $queryUser -match ' (\S+)\s+\d+ '
    if ($matches) {
        $userName = $matches[1]
    }
}
# Return if no username found via quser
if ( $null -eq $userName ) {
	return
}
$userName = $userName.Substring(1)
$userName = $userName.Split(" ")[0]
if ( $Lowercase ) {
    # Make all usernames lowercase so they group properly in Inventory
    $userName = $userName.ToLower()
}
if ($null -ne $userName -and $UserArray -notcontains $userName) {
    [PSCustomObject]@{
        Username  = $userName
        LogonType = "Current User"
        LastLogon = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }
}

@ccheath
Copy link
Author

ccheath commented Jun 28, 2024

One final change... (I forgot to set the $queryUser variable to $null when no user is found)

# This script requires that Audit Logon events are enabled in Group Policy and those events are kept for the amount of history preferred

[CmdletBinding()]
param (
    [Switch]$Lowercase
)

$UserArray = New-Object System.Collections.ArrayList
# Query all logon events with id 4624 
Get-EventLog -LogName "Security" -Newest 200 -InstanceId 4624 -ErrorAction "SilentlyContinue" | ForEach-Object {
    $EventMessage = $_
    $AccountName = $EventMessage.ReplacementStrings[5]
    $LogonType = $EventMessage.ReplacementStrings[8]
    if ( $Lowercase ) {
        # Make all usernames lowercase so they group properly in Inventory
        $AccountName = $AccountName.ToLower()
    }
    # Look for events that contain local or remote logon events, while ignoring Windows service accounts
    if ( ( $LogonType -in "2", "10", "11" ) -and ( $AccountName -notmatch "^(DWM|UMFD)-\d" -and ($AccountName -ne "") ) ) {
        # Skip duplicate names
        if ( $UserArray -notcontains $AccountName ) {
            $null = $UserArray.Add($AccountName)            
            # Translate the Logon Type
            if ( $LogonType -eq "2" ) {
                $LogonTypeName = "Local"
            }
            elseif ( $LogonType -eq "10" ) {
                $LogonTypeName = "Remote"
            }
            elseif ( $LogonType -eq "11" ) {                
                $LogonTypeName = "Cached"
            }
            $time = [DateTime]$EventMessage.TimeGenerated.ToString("yyyy-MM-dd HH:mm:ss")
            # Build an object containing the Username, Logon Type, and Last Logon time
            [PSCustomObject]@{
                Username  = $AccountName
                LogonType = $LogonTypeName
                LastLogon = $time
            }
        }
    }    
}
# Get the current logged in user in case nothing is returned by the above
#$userName = (Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -ExpandProperty UserName).Split('\')[1]
$queryUser = quser 2>&1
If ($LASTEXITCODE -ne 0) {
    $ErrorDetail = $queryUser.Exception.Message[1]
    Switch -Wildcard ($queryUser) {
        '*[1722]*' { 
            $Status = 'Remote RPC not enabled'
        }
        '*[5]*' {
            $Status = 'Access denied'
        }
        'No User exists for*' {
            $Status = 'No logged on users found'
        }
        default {
            $Status = 'Error'
            $ErrorDetail = $queryUser.Exception.Message
        }
    }
    [PSCustomObject]@{
        Username  = $Status
        LogonType = $ErrorDetail
        LastLogon = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }
    $queryUser = $null
}
$userName = $null
if ($queryUser) {
    $userName = $queryUser -match ' (\S+)\s+\d+ '
    if ($matches) {
        $userName = $matches[1]
    }
}
# Return if no username found via quser
if ( $null -eq $userName ) {
    return
}
$userName = $userName.Substring(1)
$userName = $userName.Split(" ")[0]
if ( $Lowercase ) {
    # Make all usernames lowercase so they group properly in Inventory
    $userName = $userName.ToLower()
}
if ($null -ne $userName -and $UserArray -notcontains $userName) {
    [PSCustomObject]@{
        Username  = $userName
        LogonType = "Current User"
        LastLogon = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant