FSLogix – Logoff sessions with loaded profiles

To be used with the ControlUp FSLogix Event Trigger to determine which session to log off to release the lock on a FSLogix container
Version 1.20.26
Created on 2025-06-25
Modified on 2025-07-11
Created by Trentent Tye
Downloads: 40

The Script Copy Script Copied to clipboard
<#
.SYNOPSIS
    Processes ControlUp trigger events to manage user sessions with FSLogix profile issues.

.DESCRIPTION
    This script processes ControlUp trigger objects to identify user sessions experiencing
    FSLogix profile container attachment failures and performs logoff actions as needed.
    Debug information can be written to a log file when WriteDebugInfo is enabled.

.NOTES
    Requires ControlUp PowerShell module and appropriate permissions to perform session actions.
#>

#region Configuration
# Set to $true to enable debug logging to file
$WriteDebugInfo = $false
#$CUTriggerObject = Import-Clixml "C:\swinst\CUTriggerObjectValues.xml"
# Debug configuration
$DebugPreference = "Continue"
$VerbosePreference = "Continue"
$OutputFile = "C:\swinst\CUTriggerObjectValues.txt"
#endregion

#region Helper Functions
function Write-DebugLog {
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$Message,
        
        [Parameter(Mandatory = $false)]
        [switch]$Clear
    )
    
    if ($WriteDebugInfo) {
        if ($Clear) {
            Clear-Content -Path $OutputFile -ErrorAction SilentlyContinue
        }
        
        # Handle empty or null messages
        $logMessage = if ([string]::IsNullOrEmpty($Message)) { "[EMPTY]" } else { $Message }
        Write-Host "$logMessage"
        Add-Content -Path $OutputFile -Value $logMessage -ErrorAction SilentlyContinue
    }
}

function Import-ControlUpModule {
    Write-Verbose "Importing ControlUp PowerShell module..."
    
    $pathToUserModule = (Get-ChildItem "C:\Program Files\Smart-X\ControlUpMonitor\*ControlUp.PowerShell.User.dll" -Recurse | 
                        Sort-Object LastWriteTime -Descending)[0]
    
    if (-not $pathToUserModule) {
        throw "ControlUp PowerShell module not found. Please ensure ControlUp Monitor is installed."
    }
    
    Import-Module $pathToUserModule -Force
    Write-Verbose "ControlUp module imported from: $($pathToUserModule.FullName)"
}

function Get-UserSessionsFromEvent {
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$EventMessage,
        
        [Parameter(Mandatory = $true)]
        [string]$MachineName
    )
    
    Write-Verbose "Querying user sessions for machine: $MachineName"
    
    # Validate event message
    if ([string]::IsNullOrWhiteSpace($EventMessage)) {
        throw "Event message is empty or null. Cannot parse machine name and username."
    }
    
    # Parse machine name and username from event message
    $eventParts = $EventMessage.Split(",")
    
    if ($eventParts.Count -lt 2) {
        throw "Event message format is invalid. Expected format: 'MachineName,Username' but got: '$EventMessage'"
    }
    
    $parsedMachineName = $eventParts[0].Trim()
    $username = $eventParts[1].Trim()
    
    if ([string]::IsNullOrWhiteSpace($parsedMachineName) -or [string]::IsNullOrWhiteSpace($username)) {
        throw "Unable to parse valid machine name or username from event message: '$EventMessage'"
    }
    
    Write-DebugLog "Parsed machineName: $parsedMachineName"
    Write-DebugLog "Parsed username: $username"
    
    # Query all user sessions matching the username
    $allUserSessions = (Invoke-CUQuery -Scheme Main `
                                      -Table Sessions `
                                      -Fields sUserAccount, eConnectState, sServerName, FQDN `
                                      -TranslateEnums `
                                      -Search "*$username*" `
                                      -Sort eConnectState).data
    
    Write-DebugLog "AllUserSessions: $($allUserSessions | Format-Table | Out-String)"
    
    foreach ($session in $allUserSessions) {
        Write-DebugLog "All Sessions - Key: $($session.key), User: $($session.sUserAccount), Server: $($session.sServerName), FQDN: $($session.FQDN)"
    }
    
    # Filter sessions for the specific machine
    $userSessions = $allUserSessions | Where-Object { $_.FQDN -like $parsedMachineName }
    
    Write-DebugLog "Filtered userSessions: $($userSessions | Format-Table | Out-String)"
    
    foreach ($session in $userSessions) {
        Write-DebugLog "Filtered Sessions - Key: $($session.key), User: $($session.sUserAccount), Server: $($session.sServerName), FQDN: $($session.FQDN)"
    }
    
    return @{
        MachineName = $parsedMachineName
        Username = $username
        AllSessions = $allUserSessions
        FilteredSessions = $userSessions
    }
}

function Invoke-SessionLogoff {
    param(
        [Parameter(Mandatory = $true)]
        [array]$UserSessions
    )
    
    Write-Verbose "Preparing to log off user sessions..."
    
    # Get the logoff action
    $logoffAction = (Get-CUAvailableActions) | Where-Object { $_.Title -eq "LogOff Session" }
    
    if (-not $logoffAction) {
        Write-Error "LogOff Session action not found in available ControlUp actions."
        return
    }
    
    Write-DebugLog "LogOff Action: $logoffAction"
    
    foreach ($session in $UserSessions) {
        try {
            Write-Verbose "Logging off session: $($session.sUserAccount) on $($session.FQDN)"
            
            $commandString = "Invoke-CUAction -ActionId $($logoffAction.Id) -Table $($logoffAction.Table) -RecordsGuids $($session.key)"
            Write-DebugLog "Executing command: $commandString"
            
            # Execute the logoff action
            $actionResult = Invoke-CUAction -ActionId $logoffAction.Id `
                                          -Table $logoffAction.Table `
                                          -RecordsGuids $session.key
            
            Write-DebugLog "Action Result: $($actionResult | Out-String)"
            
            # Check running actions
            $runningActions = Get-CURunningActions
            Write-DebugLog "Running Actions after logoff: $($runningActions | Out-String)"
            
            Write-Output "Successfully initiated logoff for user '$($session.sUserAccount)' on machine '$($session.FQDN)'"
            
        }
        catch {
            Write-Error "Failed to log off session for user '$($session.sUserAccount)': $($_.Exception.Message)"
            Write-DebugLog "Error logging off session: $($_.Exception.Message)"
        }
    }
}
#endregion

#region Main Script Logic
try {
    # Initialize debug logging
    Write-DebugLog -Message "=== ControlUp FSLogix Session Management Script Started ===" -Clear
    Write-DebugLog "Script execution started at: $(Get-Date)"
    
    # Validate that CUTriggerObject exists
    if (-not $CUTriggerObject) {
        throw "CUTriggerObject is not available. This script must be run from a ControlUp trigger."
    }
    
    # Log trigger object details
    Write-DebugLog "=== CUTriggerObject Values ==="
    Write-DebugLog "TriggerName: $(if ($CUTriggerObject.TriggerName) { $CUTriggerObject.TriggerName } else { '[NULL]' })"
    Write-DebugLog "ReportedBy: $(if ($CUTriggerObject.ReportedBy) { $CUTriggerObject.ReportedBy } else { '[NULL]' })"
    Write-DebugLog "Timestamp: $(if ($CUTriggerObject.Timestamp) { $CUTriggerObject.Timestamp } else { '[NULL]' })"
    Write-DebugLog "UTCTimestamp: $(if ($CUTriggerObject.UTCTimestamp) { $CUTriggerObject.UTCTimestamp } else { '[NULL]' })"
    Write-DebugLog "Timezone: $(if ($CUTriggerObject.Timezone) { $CUTriggerObject.Timezone } else { '[NULL]' })"
    Write-DebugLog ""
    Write-DebugLog "=== Event Details ==="
    Write-DebugLog "EventSource: $(if ($CUTriggerObject.Event.EventSource) { $CUTriggerObject.Event.EventSource } else { '[NULL]' })"
    Write-DebugLog "EventType: $(if ($CUTriggerObject.Event.EventType) { $CUTriggerObject.Event.EventType } else { '[NULL]' })"
    Write-DebugLog "EventID: $(if ($CUTriggerObject.Event.EventID) { $CUTriggerObject.Event.EventID } else { '[NULL]' })"
    Write-DebugLog "EventFullMsg: '$(if ($CUTriggerObject.Event.EventFullMsg) { $CUTriggerObject.Event.EventFullMsg } else { '[NULL]' })'"
    Write-DebugLog "EventFullMsg Length: $(if ($CUTriggerObject.Event.EventFullMsg) { $CUTriggerObject.Event.EventFullMsg.Length } else { 0 })"
    Write-DebugLog "EventFullMsg IsNull: $($null -eq $CUTriggerObject.Event.EventFullMsg)"
    Write-DebugLog ""
    
    # Validate event message before processing
    if ([string]::IsNullOrWhiteSpace($CUTriggerObject.Event.EventFullMsg)) {
        $errorMsg = "Event message is empty, null, or whitespace. Cannot process user sessions."
        Write-Error $errorMsg
        Write-DebugLog "ERROR: $errorMsg"
        Write-Output "Script completed with error - no event message to process."
        return
    }
    
    # Import ControlUp module
    Import-ControlUpModule
    
    # Parse event message and get user sessions
    Write-DebugLog "=== Processing Event Message ==="
    $sessionData = Get-UserSessionsFromEvent -EventMessage $CUTriggerObject.Event.EventFullMsg `
                                            -MachineName $CUTriggerObject.ReportedBy
    $sessionData | Export-Clixml "C:\swinst\sessiondata.xml"
    # Analyze session count and take appropriate action
    if (($sessionData.FilteredSessions | Measure).count -eq 0) {
        Write-Output "No user sessions found for user '$($sessionData.Username)' on machine '$($sessionData.MachineName)'"
        Write-DebugLog "No sessions found to process."
    }
    elseif (($sessionData.FilteredSessions | Measure).count -eq 1) {
        Write-Output "Single user session found for user '$($sessionData.Username)' on machine '$($sessionData.MachineName)'"
        Write-DebugLog "Single session found - proceeding with logoff."
        
        Invoke-SessionLogoff -UserSessions $sessionData.FilteredSessions
    }
    elseif (($sessionData.FilteredSessions | Measure).count -ge 2) {
        Write-Warning "Multiple user sessions found on machine '$($sessionData.MachineName)' for user '$($sessionData.Username)'!"
        Write-DebugLog "Multiple sessions detected - proceeding with logoff of all sessions."
        
        foreach ($session in $sessionData.FilteredSessions) {
            Write-Output "Session found: $($session.sUserAccount) on $($session.sServerName) (State: $($session.eConnectState))"
        }
        
        Invoke-SessionLogoff -UserSessions $sessionData.FilteredSessions
    }
    
    # Final status check
    $finalRunningActions = Get-CURunningActions
    Write-DebugLog "=== Final Status ==="
    Write-DebugLog "Final Running Actions: $($finalRunningActions | Out-String)"
    Write-DebugLog "Script execution completed at: $(Get-Date)"
    
    Write-Output "FSLogix session management completed successfully."
    
}
catch {
    $errorMessage = "Script execution failed: $($_.Exception.Message)"
    Write-Error $errorMessage
    Write-DebugLog "=== ERROR ==="
    Write-DebugLog $errorMessage
    Write-DebugLog "Stack Trace: $($_.ScriptStackTrace)"
    
    exit 1
}
#endregion