Public/Get-FslSessionState.ps1

function Get-FslSessionState {
    <#
    .SYNOPSIS
        Reads the per-session profile state FSLogix records in the registry and
        translates Status/Reason/Error into plain English.
    .DESCRIPTION
        FSLogix writes three values per user session - Status, Reason and Error -
        to HKLM:\SOFTWARE\FSLogix\Profiles\Sessions\<SID> (and, for ODFC containers,
        HKLM:\SOFTWARE\Policies\FSLogix\ODFC\Sessions\<SID>). This function reads
        each session key, resolves the SID to an account and translates the values
        using the curated FSLogixDoctor database - the fastest way to answer
        'why did this user get a temp profile?'.
 
        Semantics per Microsoft: Status 0/100/200/300 are normal states; Status 1-28
        are error states. Reason clarifies normal states only (e.g. Reason 3 = a
        local profile blocked the attach). Error is a standard Windows system error
        code from the failing API call.
    .PARAMETER SessionsKeyPath
        Registry path(s) holding per-session state. Defaults to the Profile
        container path; add the ODFC path via -IncludeOdfc or override for testing.
    .PARAMETER IncludeOdfc
        Also read ODFC container session state
        (HKLM:\SOFTWARE\Policies\FSLogix\ODFC\Sessions).
    .EXAMPLE
        Get-FslSessionState
 
        Shows every session FSLogix knows about on this host, translated.
    .EXAMPLE
        Get-FslSessionState | Where-Object Healthy -eq $false
    .LINK
        https://learn.microsoft.com/en-us/fslogix/troubleshooting-error-codes
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [string]$SessionsKeyPath = 'HKLM:\SOFTWARE\FSLogix\Profiles\Sessions',

        [switch]$IncludeOdfc
    )

    $keyPaths = @(
        [pscustomobject]@{ Container = 'Profile'; Path = $SessionsKeyPath }
    )
    if ($IncludeOdfc) {
        $keyPaths += [pscustomobject]@{ Container = 'ODFC'; Path = 'HKLM:\SOFTWARE\Policies\FSLogix\ODFC\Sessions' }
    }

    $codes = Get-FslDataTable -Name SessionCodes

    $translate = {
        param([string]$prefix, [object]$value)
        if ($null -eq $value) { return $null }
        $key = '{0}:{1}' -f $prefix, $value
        if ($codes.ContainsKey($key)) { return $codes[$key].Meaning }
        "Undocumented $prefix value '$value'"
    }

    # Per Microsoft: 0=success, 100=waiting on profile service, 200=setup in
    # progress, 300=already attached (differencing disks).
    $normalStatuses = @(0, 100, 200, 300)
    # Reasons that mean 'the container did NOT attach because something is wrong'
    # (3=local profile exists, 7=temp profile, 9=profile load failed), as opposed
    # to deliberate exclusions (1/2/4/8).
    $problemReasons = @(3, 7, 9)

    $foundAny = $false
    foreach ($keyPath in $keyPaths) {
        if (-not (Test-Path -LiteralPath $keyPath.Path)) {
            Write-Verbose "Session state key '$($keyPath.Path)' not found."
            continue
        }
        $foundAny = $true

        foreach ($sessionKey in (Get-ChildItem -LiteralPath $keyPath.Path -ErrorAction SilentlyContinue)) {
            $sid = Split-Path -Leaf $sessionKey.PSPath
            $values = Get-ItemProperty -LiteralPath $sessionKey.PSPath -ErrorAction SilentlyContinue
            $account = Resolve-FslAccount -Sid $sid

            $status = $values.Status
            $reason = $values.Reason
            # The documented value name is 'Error'; some log lines say 'ErrorCode',
            # so read that defensively as fallback.
            $errorValue = $values.Error
            if ($null -eq $errorValue) { $errorValue = $values.ErrorCode }

            $errorText = $null
            if ($null -ne $errorValue -and 0 -ne $errorValue) {
                $decoded = Get-FslErrorCode -Code $errorValue
                if ($decoded) { $errorText = $decoded.Meaning }
            }

            $statusIsNormal = ($normalStatuses -contains $status)
            # Status 300 = 'already attached' (differencing-disk setups) counts as attached.
            $attached = (($status -eq 0 -or $status -eq 300) -and $reason -eq 0)
            $reasonIsClean = -not ($problemReasons -contains $reason)
            $errorIsClean = (($null -eq $errorValue) -or ($errorValue -eq 0))
            $healthy = ($statusIsNormal -and $reasonIsClean -and $errorIsClean)

            [pscustomobject]@{
                PSTypeName = 'FSLogixDoctor.SessionState'
                Container  = $keyPath.Container
                Sid        = $sid
                Account    = $account.Account
                Status     = $status
                StatusText = & $translate 'Status' $status
                Reason     = $reason
                ReasonText = & $translate 'Reason' $reason
                Error      = $errorValue
                ErrorText  = $errorText
                Attached   = $attached
                Healthy    = $healthy
            }
        }
    }

    if (-not $foundAny) {
        Write-Warning "No FSLogix session state found. FSLogix is not installed, has never attached a profile on this host, or you lack permissions."
    }
}