Public/sessions/Disconnect-RdpSession.ps1

function Disconnect-RdpSession {
    <#
.SYNOPSIS
    Disconnects an active RDP session on local or remote computers
 
.DESCRIPTION
    Disconnects specified RDP sessions by session ID on one or more computers.
    The session remains in a disconnected state and can be reconnected by the user.
    This function does not log the user off - use Remove-RdpSession for logoff.
 
    Supports ShouldProcess for -WhatIf and -Confirm operations.
 
.PARAMETER ComputerName
    One or more computer names where sessions should be disconnected.
    Defaults to the local machine. Supports pipeline input by property name.
 
.PARAMETER SessionID
    The session ID(s) to disconnect. Can be retrieved using Get-ActiveRdpSession.
    Supports pipeline input by value and by property name.
 
.PARAMETER Credential
    Credential to use when connecting to remote computers. If not specified,
    uses the current user's credentials.
 
.EXAMPLE
    Disconnect-RdpSession -SessionID 2
    Disconnects session ID 2 on the local computer.
 
.EXAMPLE
    Get-ActiveRdpSession -ComputerName 'SRV01' | Where-Object { $_.IdleTime -gt (New-TimeSpan -Hours 2) } | Disconnect-RdpSession
    Disconnects all sessions idle for more than 2 hours on SRV01.
 
.EXAMPLE
    Disconnect-RdpSession -ComputerName 'WEB01' -SessionID 3, 5 -WhatIf
    Shows what would happen if sessions 3 and 5 were disconnected on WEB01.
 
.EXAMPLE
    'APP01', 'APP02' | Get-ActiveRdpSession | Where-Object { $_.UserName -eq 'DOMAIN\testuser' } | Disconnect-RdpSession -Confirm:$false
    Disconnects all sessions for a specific user on multiple servers without confirmation.
 
.NOTES
    Author: Franck SALLET
    Version: 1.0.0
    Last Modified: 2026-03-11
    Requires: PowerShell 5.1+
    Permissions: Remote Desktop Users group or local Administrator on target machines
 
.LINK
    https://docs.microsoft.com/en-us/windows/win32/termserv/win32-terminalservice
#>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateRange(0, 65536)]
        [int[]]$SessionID,

        [Parameter(Mandatory = $false)]
        [ValidateNotNull()]
        [System.Management.Automation.PSCredential]$Credential
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting - PowerShell $($PSVersionTable.PSVersion)"
    }

    process {
        foreach ($session in $SessionID) {
            Write-Verbose "[$($MyInvocation.MyCommand)] Processing session ID $session on $ComputerName"

            if ($PSCmdlet.ShouldProcess("$ComputerName - Session $session", 'Disconnect RDP session')) {
                # Build CIM session parameters
                $cimSessionParams = @{
                    ComputerName = $ComputerName
                    ErrorAction  = 'Stop'
                }

                if ($PSBoundParameters.ContainsKey('Credential')) {
                    $cimSessionParams['Credential'] = $Credential
                }

                $cimSession = $null

                try {
                    # Create CIM session
                    $cimSession = New-CimSession @cimSessionParams
                    Write-Verbose "[$($MyInvocation.MyCommand)] CIM session established to $ComputerName"

                    # Get Terminal Service instance
                    $tsService = Get-CimInstance -CimSession $cimSession -ClassName 'Win32_TerminalService' -Namespace 'root\cimv2\TerminalServices' -ErrorAction Stop

                    # Invoke DisconnectSession method
                    $result = Invoke-CimMethod -InputObject $tsService -MethodName 'DisconnectSession' -Arguments @{ SessionId = $session } -ErrorAction Stop

                    # Check return value
                    $success = ($result.ReturnValue -eq 0)

                    [PSCustomObject]@{
                        PSTypeName   = 'PSWinOps.RdpSessionAction'
                        ComputerName = $ComputerName
                        SessionID    = $session
                        Action       = 'Disconnect'
                        Success      = $success
                        ReturnCode   = $result.ReturnValue
                        Timestamp    = Get-Date
                    }

                    if ($success) {
                        Write-Verbose "[$($MyInvocation.MyCommand)] Successfully disconnected session $session on $ComputerName"
                    } else {
                        Write-Warning "[$($MyInvocation.MyCommand)] Failed to disconnect session $session on $ComputerName - Return code: $($result.ReturnValue)"
                    }

                } catch [Microsoft.Management.Infrastructure.CimException] {
                    Write-Error "[$($MyInvocation.MyCommand)] CIM error on $ComputerName - $_"
                } catch [System.UnauthorizedAccessException] {
                    Write-Error "[$($MyInvocation.MyCommand)] Access denied to $ComputerName - Requires administrative permissions"
                } catch {
                    Write-Error "[$($MyInvocation.MyCommand)] Failed to disconnect session $session on $ComputerName - $_"
                } finally {
                    if ($null -ne $cimSession) {
                        Remove-CimSession -CimSession $cimSession
                        Write-Verbose "[$($MyInvocation.MyCommand)] CIM session closed for $ComputerName"
                    }
                }
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand)] Completed"
    }
}