Last active
          October 16, 2025 21:21 
        
      - 
      
 - 
        
Save jborean93/3d7448b5e4b6f8912168ffb624bc1b11 to your computer and use it in GitHub Desktop.  
    Gets the LSA logon session data
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | # Copyright: (c) 2025, Jordan Borean (@jborean93) <[email protected]> | |
| # MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
| # Added GetLastErrorRecord | |
| #Requires -Module @{ ModuleName = 'Ctypes'; ModuleVersion = '0.3.0' } | |
| using namespace System.ComponentModel | |
| using namespace System.Management.Automation | |
| using namespace System.Runtime.InteropServices | |
| using namespace System.Security.Principal | |
| Function Get-LogonSessionData { | |
| <# | |
| .SYNOPSIS | |
| Gets the LSA logon session data. | |
| .DESCRIPTION | |
| Gets the LSA logon session data for the process' specified. This session | |
| data contains information about the logon session stored in LSA. | |
| .PARAMETER ProcessId | |
| The process id's to get the logon session data for. | |
| .EXAMPLE | |
| Get-LogonSessionData | |
| Gets the logon session data for the current process. | |
| .EXAMPLE | |
| Get-LogonSessionData -ProcessId 1234 | |
| Gets the logon session data for the process 1234. | |
| #> | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [Alias('Id')] | |
| [int[]] | |
| $ProcessId = $pid | |
| ) | |
| begin { | |
| $PROCESS_QUERY_INFORMATION = 0x00000400 | |
| $TokenStatistics = 10 | |
| [Flags()] enum LogonUserFlags { | |
| None = 0x00000000 | |
| Guest = 0x0000000 | |
| NoEncryption = 0x00000002 | |
| CachedAccount = 0x00000004 | |
| UsedLmPassword = 0x00000008 | |
| ExtraSids = 0x00000020 | |
| SubauthSessionKey = 0x00000040 | |
| ServerTrustAccount = 0x00000080 | |
| Ntlmv2Enabled = 0x00000100 | |
| ResourceGroups = 0x00000200 | |
| ProfilePathReturned = 0x00000400 | |
| Ntv2 = 0x00000800 | |
| Lmv2 = 0x00001000 | |
| Ntlmv2 = 0x00002000 | |
| Optimized = 0x00004000 | |
| Winlogon = 0x00008000 | |
| Pkinit = 0x00010000 | |
| NoOptimized = 0x00020000 | |
| NoElevation = 0x00040000 | |
| ManagedService = 0x00080000 | |
| } | |
| enum SecurityLogonType { | |
| UndefinedLogonType = 0 | |
| Interactive = 2 | |
| Network | |
| Batch | |
| Service | |
| Proxy | |
| Unlock | |
| NetworkCleartext | |
| NewCredentials | |
| RemoteInteractive | |
| CachedInteractive | |
| CachedRemoteInteractive | |
| CachedUnlock | |
| } | |
| ctypes_struct LUID { | |
| [int]$LowPart | |
| [int]$HighPart | |
| } | |
| ctypes_struct LSA_LAST_INTER_LOGON_INFO { | |
| [int64]$LastSuccessfulLogon | |
| [int64]$LastFailedLogon | |
| [int]$FailedAttemptCountSinceLastSuccessfulLogon | |
| } | |
| ctypes_struct LSA_UNICODE_STRING { | |
| [int16]$Length | |
| [int16]$MaximumLength | |
| [IntPtr]$Buffer | |
| } | |
| ctypes_struct SECURITY_LOGON_SESSION_DATA { | |
| [int]$Size | |
| [Luid]$logonId | |
| [LSA_UNICODE_STRING]$UserName | |
| [LSA_UNICODE_STRING]$LogonDomain | |
| [LSA_UNICODE_STRING]$AuthenticationPackage | |
| [int]$LogonType | |
| [int]$Session | |
| [IntPtr]$Sid | |
| [int64]$LogonTime | |
| [LSA_UNICODE_STRING]$LogonServer | |
| [LSA_UNICODE_STRING]$DnsDomainName | |
| [LSA_UNICODE_STRING]$Upn | |
| [int]$UserFlags | |
| [LSA_LAST_INTER_LOGON_INFO]$LastLogonInfo | |
| [LSA_UNICODE_STRING]$LogonScript | |
| [LSA_UNICODE_STRING]$ProfilePath | |
| [LSA_UNICODE_STRING]$HomeDirectory | |
| [LSA_UNICODE_STRING]$HomeDirectoryDrive | |
| [int64]$LogoffTime | |
| [int64]$KickOffTime | |
| [int64]$PasswordLastSet | |
| [int64]$PasswordCanChange | |
| [int64]$PasswordMustChange | |
| } | |
| ctypes_struct TOKEN_STATISTICS { | |
| [Luid]$TokenId | |
| [Luid]$AuthenticationId | |
| [Int64]$ExpirationTime | |
| [int]$TokenType | |
| [int]$ImpersonationLevel | |
| [int]$DynamicCharged | |
| [int]$DynamicAvailable | |
| [int]$GroupCount | |
| [int]$PrivilegeCount | |
| [int]$ModifiedId | |
| } | |
| $advapi32 = New-CtypesLib Advapi32.dll | |
| $advapi32.Returns([bool]).SetLastError($true).OpenProcessToken = [Ordered]@{ | |
| ProcessHandle = [IntPtr] | |
| DesiredAccess = [TokenAccessLevels] | |
| TokenHandle = [ref][IntPtr] | |
| } | |
| $advapi32.Returns([bool]).SetLastError($true).Entrypoint('GetTokenInformation').GetTokenInformationTokenStatistics = [Ordered]@{ | |
| TokenHandle = [IntPtr] | |
| TokenInformationClass = [int] | |
| TokenInformation = [ref][TOKEN_STATISTICS] | |
| TokenInformationLength = [int] | |
| ReturnLength = [ref][int] | |
| } | |
| $advapi32.Returns([int]).LsaNtStatusToWinError = [Ordered]@{ | |
| Status = [int] | |
| } | |
| $kernel32 = New-CtypesLib Kernel32.dll | |
| $kernel32.Returns([IntPtr]).GetCurrentProcess = [Ordered]@{} | |
| $kernel32.Returns([IntPtr]).SetLastError($true).OpenProcess = [Ordered]@{ | |
| dwDesiredAccess = [int] | |
| bInheritHandle = [bool] | |
| dwProcessId = [int] | |
| } | |
| $kernel32.Returns([bool]).SetLastError($true).CloseHandle = [Ordered]@{ | |
| hObject = [IntPtr] | |
| } | |
| $secur32 = New-CtypesLib Secur32.dll | |
| $secur32.Returns([int]).LsaFreeReturnBuffer = [Ordered]@{ | |
| Buffer = [IntPtr] | |
| } | |
| $secur32.Returns([int]).LsaGetLogonSessionData = [Ordered]@{ | |
| LogonId = [ref][Luid] | |
| ppLogonSessionData = [ref][IntPtr] | |
| } | |
| Function ConvertFrom-FileTime { | |
| [OutputType([DateTime])] | |
| param ( | |
| [Parameter(Mandatory, ValueFromPipeline)] | |
| [int64] | |
| $InputObject | |
| ) | |
| process { | |
| if ($InputObject -eq [Int64]::MaxValue) { | |
| return | |
| } | |
| [DateTime]::FromFileTimeUtc($InputObject) | |
| } | |
| } | |
| Function ConvertFrom-LsaString { | |
| [OutputType([string])] | |
| [CmdletBinding()] | |
| param ( | |
| [Parameter(Mandatory, ValueFromPipelineByPropertyName)] | |
| [int] | |
| $Length, | |
| [Parameter(Mandatory, ValueFromPipelineByPropertyName)] | |
| [IntPtr] | |
| $Buffer | |
| ) | |
| process { | |
| if ($Length -eq 0 -or $Buffer -eq [IntPtr]::Zero) { | |
| return | |
| } | |
| [Marshal]::PtrToStringUni($Buffer, $Length / 2) | |
| } | |
| } | |
| } | |
| process { | |
| foreach ($procId in $ProcessId) { | |
| $sessionDataPtr = $procHandle = $token = [IntPtr]::Zero | |
| try { | |
| if ($procId -eq $pid) { | |
| Write-Verbose "Opening current process handle" | |
| $procHandle = $kernel32.GetCurrentProcess() | |
| } | |
| else { | |
| Write-Verbose "Opening process handle for $procId" | |
| $procHandle = $kernel32.OpenProcess( | |
| $PROCESS_QUERY_INFORMATION, | |
| $false, | |
| $procId) | |
| if ($procHandle -eq [IntPtr]::Zero) { | |
| $err = $kernel32.GetLastErrorRecord('OpenProcessFailed') | |
| $err.ErrorDetails = "Failed to open process ${procId}: $err" | |
| $PSCmdlet.WriteError($err) | |
| continue | |
| } | |
| } | |
| Write-Verbose "Opening token handle for process $procId" | |
| $res = $advapi32.OpenProcessToken( | |
| $procHandle, | |
| [TokenAccessLevels]::Query, | |
| [ref]$token) | |
| if (-not $res) { | |
| $err = $advapi32.GetLastErrorRecord('OpenProcessTokenFailed') | |
| $err.ErrorDetails = "Failed to open process token for ${procId}: $err" | |
| $PSCmdlet.WriteError($err) | |
| continue | |
| } | |
| $tokenStats = [TOKEN_STATISTICS]::new() | |
| $tokenStatsLength = [Marshal]::SizeOf([type]$tokenStats.GetType()) | |
| $res = $advapi32.GetTokenInformationTokenStatistics( | |
| $token, | |
| $TokenStatistics, | |
| [ref]$tokenStats, | |
| $tokenStatsLength, | |
| [ref]$tokenStatsLength) | |
| if (-not $res) { | |
| $err = $advapi32.GetLastErrorRecord('GetTokenInformationTokenStatisticsFailed') | |
| $err.ErrorDetails = "Failed to get TokenStatistics for ${procId}: $err" | |
| $PSCmdlet.WriteError($err) | |
| continue | |
| } | |
| $logonId = $tokenStats.AuthenticationId | |
| $ntStatus = $secur32.LsaGetLogonSessionData( | |
| [ref]$logonId, | |
| [ref]$sessionDataPtr) | |
| if ($ntStatus) { | |
| $winError = $advapi32.LsaNtStatusToWinError($ntStatus) | |
| $exp = [Win32Exception]::new($winError) | |
| $err = [ErrorRecord]::new( | |
| $exp, | |
| "LsaGetLogonSessionDataFailed", | |
| [ErrorCategory]::InvalidResult, | |
| $winError) | |
| $err.ErrorDetails = 'Failed to get LSA logon session data for {0}: {1} (0x{2:X8})' -f @( | |
| $procId, $exp.Message, $winError) | |
| $PSCmdlet.WriteError($err) | |
| continue | |
| } | |
| $sessionData = [Marshal]::PtrToStructure($sessionDataPtr, [type][SECURITY_LOGON_SESSION_DATA]) | |
| [PSCustomObject]@{ | |
| PSTypeName = 'LsaLogonSessionData' | |
| ProcessId = $procId | |
| LogonId = (([int64]$sessionData.LogonId.HighPart) -shl 32) -bor $sessiondata.LogonId.LowPart | |
| UserName = $sessionData.UserName | ConvertFrom-LsaString | |
| LogonDomain = $sessionData.LogonDomain | ConvertFrom-LsaString | |
| AuthenticationPackage = $sessionData.AuthenticationPackage | ConvertFrom-LsaString | |
| LogonType = [Enum]::ToObject([SecurityLogonType], $sessionData.LogonType) | |
| Session = $sessionData.Session | |
| Sid = [SecurityIdentifier]::new($sessionData.Sid) | |
| LogonTime = $sessionData.LogonTime | ConvertFrom-FileTime | |
| LogonServer = $sessionData.LogonServer | ConvertFrom-LsaString | |
| DnsDomainName = $sessionData.DnsDomainName | ConvertFrom-LsaString | |
| Upn = $sessionData.Upn | ConvertFrom-LsaString | |
| UserFlags = [Enum]::ToObject([LogonUserFlags], $sessionData.UserFlags) | |
| LastSuccessfulLogon = $sessionData.LastLogonInfo.LastSuccessfulLogon | |
| LastFailedLogon = $sessionData.LastLogonInfo.LastFailedLogon | |
| FailedAttemptCountSinceLastSuccessfulLogon = $sessionData.LastLogonInfo.FailedAttemptCountSinceLastSuccessfulLogon | |
| LogonScript = $sessionData.LogonScript | ConvertFrom-LsaString | |
| ProfilePath = $sessionData.ProfilePath | ConvertFrom-LsaString | |
| HomeDirectory = $sessionData.HomeDirectory | ConvertFrom-LsaString | |
| HomeDirectoryDrive = $sessionData.HomeDirectoryDrive | ConvertFrom-LsaString | |
| LogoffTime = $sessionData.LogoffTime | ConvertFrom-FileTime | |
| KickOffTime = $sessionData.KickOffTime | ConvertFrom-FileTime | |
| PasswordLastSet = $sessionData.PasswordLastSet | ConvertFrom-FileTime | |
| PasswordCanChange = $sessionData.PasswordCanChange | ConvertFrom-FileTime | |
| PasswordMustChange = $sessionData.PasswordMustChange | ConvertFrom-FileTime | |
| } | |
| } | |
| finally { | |
| if ($sessionDataPtr -ne [IntPtr]::Zero) { | |
| $null = $secur32.LsaFreeReturnBuffer($sessionDataPtr) | |
| } | |
| if ($token -ne [IntPtr]::Zero) { | |
| $null = $kernel32.CloseHandle($token) | |
| } | |
| if ($procHandle -ne [IntPtr]-1 -and $procHandle -ne [IntPtr]::Zero) { | |
| $null = $kernel32.CloseHandle($procHandle) | |
| } | |
| } | |
| } | |
| } | |
| } | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment