Public/Get-HorizonIdleSessions.ps1

#Requires -Version 5.1

<#
===============================================================================
 
File Name : Get-HorizonIdleSessions.ps1
Project : Enterprise-HorizonToolkit
Author : Malik Oseni
Version : 1.1.0
 
.SYNOPSIS
    Returns idle VMware Horizon sessions.
 
.DESCRIPTION
    Retrieves Horizon sessions and returns those that have been idle
    for longer than the specified threshold.
 
.PARAMETER IdleMinutes
    Minimum idle time in minutes. Defaults to 240 (4 hours).
 
.PARAMETER UserName
    Filter by username.
 
.PARAMETER MachineName
    Filter by machine name.
 
.PARAMETER State
    Optional session state filter. Accepts CONNECTED or DISCONNECTED.
 
.EXAMPLE
    Get-HorizonIdleSessions
 
.EXAMPLE
    Get-HorizonIdleSessions -IdleMinutes 240
 
.EXAMPLE
    Get-HorizonIdleSessions -MachineName <MachineName>
 
.NOTES
 
Enterprise-HorizonToolkit
Copyright (c) 2026 Malik Oseni
 
Change log
----------
1.1.0 Bug #1 begin/process/end blocks were nested inside begin instead of
               being direct children of the function body. PowerShell only
               executed the begin block; process and end were never reached.
       Bug #2 Get-HorizonSessions was called twice with the same params and
               the log/verbose lines were duplicated. Duplicate call removed.
       Bug #3 $IdleSessions.Count fails when the foreach returns $null
               (zero matches). Wrapped in @() to guarantee an array so
               .Count is always safe.
       Bug #4 Missing closing brace for the function body added.
 
===============================================================================
#>


Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

function Get-HorizonIdleSessions {

    [OutputType('Enterprise.Horizon.IdleSession')]
    [CmdletBinding()]

    param(

        [Parameter()]
        [ValidateRange(1, 10080)]
        [int]
        $IdleMinutes = 240,

        [Parameter()]
        [string]
        $UserName,

        [Parameter()]
        [string]
        $MachineName,

        [Parameter()]
        [ValidateSet(
            'CONNECTED',
            'DISCONNECTED'
        )]
        [string]
        $State

    )

    # BUG #1 FIX: begin/process/end are now direct children of the function
    # body, not nested inside begin. In the original, PowerShell saw the outer
    # begin block and treated the inner begin/process/end as ordinary
    # statements inside it, meaning process and end never executed as
    # pipeline blocks.

    begin {

        Write-HorizonLog `
            -Message 'Starting Get-HorizonIdleSessions.' `
            -Level Information

    }

    process {

        Write-Verbose 'Retrieving Horizon sessions.'

        $Params = @{
            UserName    = $UserName
            MachineName = $MachineName
        }

        if ($PSBoundParameters.ContainsKey('State')) {

            $Params.State = $State
        }

        # BUG #2 FIX: Get-HorizonSessions was called twice back-to-back with
        # identical parameters, and the log/verbose lines that followed were
        # also duplicated. The second call was retrieving a fresh set of
        # sessions and silently discarding the first, which wasted an API
        # round-trip and could return inconsistent counts between the two
        # log lines. Removed the duplicate.
        $Sessions = @(Get-HorizonSessions @Params)

        Write-HorizonLog `
            -Message "$($Sessions.Count) Horizon session(s) retrieved." `
            -Level Information

        Write-Verbose 'Filtering idle sessions.'

        # BUG #3 FIX: When no sessions match the idle threshold the foreach
        # expression returns $null rather than an empty array. Calling .Count
        # on $null throws a property-not-found error under Set-StrictMode.
        # Wrapping in @() guarantees an array with a safe .Count in all cases.
        $IdleSessions = @(

            foreach ($Session in @($Sessions)) {

                # Ignore sessions that have no idle value
                if ($null -eq $Session.IdleMinutes) {
                    continue
                }

                # Apply idle threshold
                if ($Session.IdleMinutes -lt $IdleMinutes) {
                    continue
                }

                # Emit the enterprise session object
                [PSCustomObject] @{
                    PSTypeName              = 'Enterprise.Horizon.IdleSession'
                    UserName                = $Session.UserName
                    MachineName             = $Session.MachineName
                    MachineDNS              = $Session.MachineDNS
                    DesktopPool             = $Session.DesktopPool
                    DesktopName             = $Session.DesktopName
                    DesktopType             = $Session.DesktopType
                    DesktopSource           = $Session.DesktopSource
                    SessionState            = $Session.SessionState
                    SessionType             = $Session.SessionType
                    Protocol                = $Session.Protocol
                    StartTime               = $Session.StartTime
                    DisconnectTime          = $Session.DisconnectTime
                    DurationMinutes         = $Session.DurationMinutes
                    IdleMinutes             = $Session.IdleMinutes
                    ClientName              = $Session.ClientName
                    ClientType              = $Session.ClientType
                    ClientAddress           = $Session.ClientAddress
                    ClientVersion           = $Session.ClientVersion
                    AgentVersion            = $Session.AgentVersion
                    SecurityGateway         = $Session.SecurityGateway
                    SecurityGatewayAddress  = $Session.SecurityGatewayAddress
                    SecurityGatewayLocation = $Session.SecurityGatewayLocation
                    BrokeredRemotely        = $Session.BrokeredRemotely
                    ResourcedRemotely       = $Session.ResourcedRemotely
                    ForeverSession          = $Session.ForeverSession
                    Unauthenticated         = $Session.Unauthenticated
                }

            }

        )

        Write-HorizonLog `
            -Message "$($IdleSessions.Count) idle session(s) found." `
            -Level Information

        Write-Verbose 'Returning enterprise idle session objects.'

        Write-HorizonLog `
            -Message "Returning $($IdleSessions.Count) idle session(s)." `
            -Level Information

        return $IdleSessions

    }

    end {

        Write-HorizonLog `
            -Message 'Get-HorizonIdleSessions completed successfully.' `
            -Level Information

        Write-Verbose 'Get-HorizonIdleSessions completed.'

    }

    # BUG #4 FIX: The original file was missing the closing brace for the
    # function body entirely, which would cause a MissingEndCurlyBrace parse
    # error when the file was loaded.

}