Open ccheath opened 5 months ago
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"
}
}
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"
}
}
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: