<< Back to Script Library
FSLogix log parser
Parses the Profile log for FSLogix event for a specific user and displays the results.
Version: 5.3.11
Created: 2019-12-03
Modified: 2023-01-18
Creator: Trentent Tye
Downloads: 2168
Tags: fslogix logon duration vdi
Created: 2019-12-03
Modified: 2023-01-18
Creator: Trentent Tye
Downloads: 2168
Tags: fslogix logon duration vdi
The Script
Copy Script
Copied to clipboard
<#
.SYNOPSIS
Finds all FSLogix Profile events for a user's logon
.DESCRIPTION
Finds all FSLogix Profile events for a user's logon for review and troubleshooting.
.EXAMPLE
. .\Get-FSLogixProfileLog.ps1 -User BOTTHEORY\amttye -SessionId 2
Gets all events for the user "amttye" from domain "bottheory" on this machine
.EXAMPLE
. .\Get-FSLogixProfileLog.ps1 -User BOTTHEORY\amttye -SessionId 2
Gets all events for the user "amttye" from domain "bottheory" on this machine
.NOTES
This script must be run on a machine where the user is currently logged on.
.CONTEXT
Session
.MODIFICATION_HISTORY
Created TTYE : 2019-11-19
Edit: Ton de Vreede 2022-9-15 - small bugfix for error handling
AUTHOR: Trentent Tye
#>
[CmdLetBinding()]
Param (
[Parameter(Mandatory=$true,HelpMessage='Enter the username in the format DOMAIN\Username')][ValidateNotNullOrEmpty()] [string]$User,
[Parameter(Mandatory=$true,HelpMessage='Enter the session ID')][ValidateNotNullOrEmpty()] [int]$SessionId
)
$ErrorActionPreference = "Stop"
###$VerbosePreference = "continue"
[int]$outputWidth = 800
# Altering the size of the PS Buffer
$PSWindow = (Get-Host).UI.RawUI
$WideDimensions = $PSWindow.BufferSize
$WideDimensions.Width = $outputWidth
$PSWindow.BufferSize = $WideDimensions
$userdomain = ($user -split "\\")[0]
$username = ($user -split "\\")[1]
function Get-FSLogixProfileEvents {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[DateTime]
$Start,
[Parameter(Mandatory=$true)]
[String]
$Username
)
Write-Verbose "Username: `"$($Username)`""
Write-Verbose "User Session StartTime: `"$($Start)`""
#FSLogix Log path is here: C:\ProgramData\FSLogix\Logs\Profile
#at the time of this testing version 2.9.7205.27375 of FSLogix provided all the necessary information
Write-Verbose "Looking for file with `"$($($start).ToString("yyyyMMdd"))`" in the file name"
try {
$FSLogixLogDir = Get-ItemPropertyValue -Path HKLM:\SOFTWARE\FSLogix\Logging -Name Logdir
Write-Verbose "LogDir value configured. LogDir set to $FSLogixLogDir"
}
Catch {
#LogDir registry value not found. Set to default:
Write-Verbose "LogDir value not set. Setting LogDir to default path"
$FSLogixLogDir = "C:\ProgramData\FSLogix\Logs"
}
$profileLog = ls "$FSLogixLogDir\Profile" | Where {$_.Name -like "*$($($start).ToString("yyyyMMdd"))*"}
try {
Test-Path $profileLog.FullName | out-null
} catch {
Write-Error "Unable to determine or find FSLogix profile log file."
break
}
Write-Verbose "Found Profile Log file: $($profileLog.FullName)"
$FSLogixLog = Get-Content "$($profileLog.fullname)"
$CurrentTimezone = ((Get-TimeZone).baseUtcOffset)
Write-Verbose "Current Timezone Offset: $($CurrentTimezone.TotalHours)"
Write-Verbose "Searching for FSLogix Timezone offsets"
[timespan]$UTCOffset = (($FSLogixLog -match "^UTC")[0]) -replace "^UTC\+{0,1}",''
Write-Verbose "FSLogix Timezone Offset: $($UTCOffset.TotalHours)"
$TimeZoneOffset = $CurrentTimezone.Subtract($UTCOffset)
Write-Verbose "Timezone Offset for log file processing: $($TimeZoneOffset.TotalHours)"
$FSLogixLogObject = New-Object System.Collections.ArrayList
#Create powershell object out of the FSLogix Log.
Foreach ($line in $FSLogixLog) {
$line | Select-String -Pattern "\[(.*?)\]|.+" -AllMatches | ForEach-Object{
if ( $_.Matches.count -eq 4) { #ignore all lines that don't conform to the grid table
$MMddyyyy = $(($start).ToString("MM/dd/yyyy"))
$time = $($_.Matches[0].Value -replace ("\[","") -replace ("\]",""))
$FSLogixTime = ([datetime]"$MMddyyyy $time").AddMinutes($TimeZoneOffset.TotalMinutes) ## Adding by TotalMinutes for those tricky 30 min timezone offsets
if ($FSLogixTime -ge $start) {
$obj = [PSCustomObject]@{
Time = $FSLogixTime
ThreadId = $_.Matches[1].Value -replace ("\[","") -replace ("\]","")
LogLevel = $_.Matches[2].Value -replace ("\[","") -replace ("\]","")
Message = $_.Matches[3].Value.Trim()
}
$FSLogixLogObject.Add($obj)|Out-Null
}
}
}
}
$SessionEvents = $FSLogixLogObject | Where {$_.Message -like "*LoadProfile: $username*"}
Write-Verbose "Number of SessionEvents: $($SessionEvents.Count)"
$FSLogixStartEvent = $SessionEvents[0].time
$FSLogixEndEvent = $SessionEvents[1].time
$returnObject = New-Object System.Collections.ArrayList
foreach ($FSLogixLogLine in $FSLogixLogObject) {
if (($FSLogixLogLine.Time -le $FSLogixEndEvent) -and ($FSLogixLogLine.Time -ge $FSLogixStartEvent)) {
$returnObject.Add($FSLogixLogLine)|Out-Null
}
}
return $returnObject
}
#region Login Information Gathering
$LSADefinitions = @'
[DllImport("secur32.dll", SetLastError = false)]
public static extern uint LsaFreeReturnBuffer(IntPtr buffer);
[DllImport("Secur32.dll", SetLastError = false)]
public static extern uint LsaEnumerateLogonSessions
(out UInt64 LogonSessionCount, out IntPtr LogonSessionList);
[DllImport("Secur32.dll", SetLastError = false)]
public static extern uint LsaGetLogonSessionData(IntPtr luid,
out IntPtr ppLogonSessionData);
[StructLayout(LayoutKind.Sequential)]
public struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public UInt32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_LOGON_SESSION_DATA
{
public UInt32 Size;
public LUID LoginID;
public LSA_UNICODE_STRING Username;
public LSA_UNICODE_STRING LoginDomain;
public LSA_UNICODE_STRING AuthenticationPackage;
public UInt32 LogonType;
public UInt32 Session;
public IntPtr PSiD;
public UInt64 LoginTime;
public LSA_UNICODE_STRING LogonServer;
public LSA_UNICODE_STRING DnsDomainName;
public LSA_UNICODE_STRING Upn;
}
public enum SECURITY_LOGON_TYPE : uint
{
Interactive = 2, //The security principal is logging on
//interactively.
Network, //The security principal is logging using a
//network.
Batch, //The logon is for a batch process.
Service, //The logon is for a service account.
Proxy, //Not supported.
Unlock, //The logon is an attempt to unlock a workstation.
NetworkCleartext, //The logon is a network logon with cleartext
//credentials.
NewCredentials, //Allows the caller to clone its current token and
//specify new credentials for outbound connections.
RemoteInteractive, //A terminal server session that is both remote
//and interactive.
CachedInteractive, //Attempt to use the cached credentials without
//going out across the network.
CachedRemoteInteractive,// Same as RemoteInteractive, except used
// internally for auditing purposes.
CachedUnlock // The logon is an attempt to unlock a workstation.
}
'@
if( ! ( ([System.Management.Automation.PSTypeName]'Win32.Secure32').Type ) )
{
Add-Type -MemberDefinition $LSADefinitions -Name 'Secure32' -Namespace 'Win32' -UsingNamespace System.Text -Debug:$false
}
$count = [UInt64]0
$luidPtr = [IntPtr]::Zero
[uint64]$ntStatus = [Win32.Secure32]::LsaEnumerateLogonSessions( [ref]$count , [ref]$luidPtr )
$count = [UInt64]0
$luidPtr = [IntPtr]::Zero
[uint64]$ntStatus = [Win32.Secure32]::LsaEnumerateLogonSessions( [ref]$count , [ref]$luidPtr )
if( $ntStatus )
{
Write-Error "LsaEnumerateLogonSessions failed with error $ntStatus"
}
elseif( ! $count )
{
Write-Error "No sessions returned by LsaEnumerateLogonSessions"
}
elseif( $luidPtr -eq [IntPtr]::Zero )
{
Write-Error "No buffer returned by LsaEnumerateLogonSessions"
}
else
{
Write-Debug "$count sessions retrieved from LSASS"
[IntPtr] $iter = $luidPtr
$earliestSession = $null
[array]$lsaSessions = @( For ([uint64]$i = 0; $i -lt $count; $i++)
{
$sessionData = [IntPtr]::Zero
$ntStatus = [Win32.Secure32]::LsaGetLogonSessionData( $iter , [ref]$sessionData )
if( ! $ntStatus -and $sessionData -ne [IntPtr]::Zero )
{
$data = [System.Runtime.InteropServices.Marshal]::PtrToStructure( $sessionData , [type][Win32.Secure32+SECURITY_LOGON_SESSION_DATA] )
if ($data.PSiD -ne [IntPtr]::Zero)
{
$sid = New-Object -TypeName System.Security.Principal.SecurityIdentifier -ArgumentList $Data.PSiD
#extract some useful information from the session data struct
[datetime]$loginTime = [datetime]::FromFileTime( $data.LoginTime )
$thisUser = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($data.Username.buffer) #get the account name
$thisDomain = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($data.LoginDomain.buffer) #get the domain name
try
{
$secType = [Win32.Secure32+SECURITY_LOGON_TYPE]$data.LogonType
}
catch
{
$secType = 'Unknown'
}
if( ! $earliestSession -or $loginTime -lt $earliestSession )
{
$earliestSession = $loginTime
}
if( $thisUser -eq $Username -and $thisDomain -eq $UserDomain -and $secType -match 'Interactive' )
{
$authPackage = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($data.AuthenticationPackage.buffer) #get the authentication package
$session = $data.Session # get the session number
if( $session -eq $SessionId )
{
$logonServer = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($data.LogonServer.buffer) #get the logon server
$DnsDomainName = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($data.DnsDomainName.buffer) #get the DNS Domain Name
$upn = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($data.upn.buffer) #get the User Principal Name
[pscustomobject]@{
'Sid' = $sid
'Username' = $thisUser
'Domain' = $thisDomain
'Session' = $session
'LoginId' = [uint64]( $loginID = [Int64]("0x{0:x8}{1:x8}" -f $data.LoginID.HighPart , $data.LoginID.LowPart) )
'LogonServer' = $logonServer
'DnsDomainName' = $DnsDomainName
'UPN' = $upn
'AuthPackage' = $authPackage
'SecurityType' = $secType
'Type' = $data.LogonType
'LoginTime' = [datetime]$loginTime
}
}
}
}
[void][Win32.Secure32]::LsaFreeReturnBuffer( $sessionData )
$sessionData = [IntPtr]::Zero
}
$iter = $iter.ToInt64() + [System.Runtime.InteropServices.Marshal]::SizeOf([type][Win32.Secure32+LUID]) # move to next pointer
}) | Sort-Object -Descending -Property 'LoginTime'
[void]([Win32.Secure32]::LsaFreeReturnBuffer( $luidPtr ))
$luidPtr = [IntPtr]::Zero
Write-Debug "Found $(if( $lsaSessions ) { $lsaSessions.Count } else { 0 }) LSA sessions for $UserDomain\$Username, earliest session $(if( $earliestSession ) { Get-Date $earliestSession -Format G } else { 'never' })"
}
if( $lsaSessions -and $lsaSessions.Count )
{
## get all logon ids for logons that happened at the same time
[array]$loginIds = @( $lsaSessions | Where-Object { $_.LoginTime -eq $lsaSessions[0].LoginTime } | Select-Object -ExpandProperty LoginId )
if( ! $loginIds -or ! $loginIds.Count )
{
Write-Error "Found no login ids for $username at $(Get-Date -Date $lsaSessions[0].LoginTime -Format G)"
}
$Logon = New-Object -TypeName psobject -Property @{
LogonTime = $lsaSessions[0].LoginTime
LogonTimeFileTime = $lsaSessions[0].LoginTime.ToFileTime()
FormatTime = $lsaSessions[0].LoginTime.ToString( 'HH:mm:ss.fff' )
LogonID = $loginIds
UserSID = $lsaSessions[0].Sid
Type = $lsaSessions[0].Type
UserName = $Username
UserDomain = $UserDomain
}
}
else
{
Throw "Failed to retrieve logon session for $UserDomain\$Username from LSASS"
}
#endregion
Get-FSLogixProfileEvents -Start $logon.LogonTime -Username $logon.UserName