Public/sessions/Remove-RdpSession.ps1

function Remove-RdpSession {
    <#
.SYNOPSIS
    Logs off (removes) an RDP session on local or remote computers
 
.DESCRIPTION
    Forces a logoff of specified RDP sessions by session ID on one or more computers.
    This terminates the session completely and closes all applications. Unsaved work
    will be lost. Use Disconnect-RdpSession for a graceful disconnect without logoff.
 
    Supports ShouldProcess for -WhatIf and -Confirm operations.
 
.PARAMETER ComputerName
    One or more computer names where sessions should be removed.
    Defaults to the local machine. Supports pipeline input by property name.
 
.PARAMETER SessionID
    The session ID(s) to remove. 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.
 
.PARAMETER Force
    Bypass confirmation prompts. Use with caution as this will forcefully
    terminate sessions and may result in data loss.
 
.EXAMPLE
    Remove-RdpSession -SessionID 2
    Logs off session ID 2 on the local computer after confirmation.
 
.EXAMPLE
    Get-ActiveRdpSession -ComputerName 'SRV01' | Where-Object { $_.IdleTime -gt (New-TimeSpan -Days 1) } | Remove-RdpSession -Force
    Forcefully removes all sessions idle for more than 1 day on SRV01 without confirmation.
 
.EXAMPLE
    Remove-RdpSession -ComputerName 'WEB01' -SessionID 3 -WhatIf
    Shows what would happen if session 3 were removed from WEB01.
 
.EXAMPLE
    'APP01' | Get-RdpSession | Where-Object { $_.UserName -eq 'DOMAIN\olduser' } | Remove-RdpSession -Credential $cred
    Removes all sessions for a specific user on APP01 using provided credentials.
 
.NOTES
    Author: Franck SALLET
    Version: 1.0.0
    Last Modified: 2026-03-11
    Requires: PowerShell 5.1+
    Permissions: Local Administrator on target machines
    WARNING: This operation terminates sessions forcefully and may cause data loss
 
.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,

        [Parameter(Mandatory = $false)]
        [switch]$Force
    )

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

        if ($Force -and -not $WhatIfPreference) {
            $ConfirmPreference = 'None'
        }
    }

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

            if ($PSCmdlet.ShouldProcess("$ComputerName - Session $session", 'Log off RDP session (FORCE TERMINATE)')) {
                # 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 LogoffSession method
                    $result = Invoke-CimMethod -InputObject $tsService -MethodName 'LogoffSession' -Arguments @{ SessionId = $session } -ErrorAction Stop

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

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

                    if ($success) {
                        Write-Verbose "[$($MyInvocation.MyCommand)] Successfully logged off session $session on $ComputerName"
                    } else {
                        Write-Warning "[$($MyInvocation.MyCommand)] Failed to log off 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 log off 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"
    }
}