Public/sessions/Get-RdpSessionLock.ps1
|
function Get-RdpSessionLock { <# .SYNOPSIS Retrieves RDP session lock and unlock event history from Windows Event Log .DESCRIPTION Queries the Microsoft-Windows-TerminalServices-LocalSessionManager/Operational and Security event logs on one or more computers to retrieve session lock (workstation lock) and unlock events for RDP sessions. Returns structured objects with user, timestamp, and lock/unlock action details. The function filters events by ID: - Event 4800 (Security): Workstation was locked - Event 4801 (Security): Workstation was unlocked .PARAMETER ComputerName One or more computer names to query. Defaults to the local machine. Supports pipeline input by value and by property name. .PARAMETER StartTime The earliest event timestamp to retrieve. Events older than this time are excluded from results. Defaults to 7 days ago. .PARAMETER Credential Credential to use when querying remote computers. If not specified, uses the current user's credentials. .EXAMPLE Get-RdpSessionLock Retrieves all RDP session lock/unlock events from the local computer for the last 7 days. .EXAMPLE Get-RdpSessionLock -ComputerName 'SRV01' -StartTime (Get-Date).AddDays(-30) Retrieves 30 days of lock/unlock history from SRV01. .EXAMPLE 'WEB01', 'APP01' | Get-RdpSessionLock -Credential $cred | Where-Object { $_.Action -eq 'Locked' } Pipeline example: retrieves only lock events (not unlocks) from multiple servers. .EXAMPLE Get-ADComputer -Filter "OperatingSystem -like '*Server*'" | Get-RdpSessionLock -StartTime (Get-Date).AddHours(-24) | Group-Object -Property UserName Retrieves last 24 hours of lock events from all domain servers and groups by user. .NOTES Author: Franck SALLET Version: 1.1.0 Last Modified: 2026-03-19 Requires: PowerShell 5.1+ Permissions: Event Log Readers group or local Administrator on target machines Note: Security log access requires elevated permissions .LINK https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4800 #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'Name', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateNotNull()] [datetime]$StartTime = (Get-Date).AddDays(-7), [Parameter(Mandatory = $false)] [ValidateNotNull()] [System.Management.Automation.PSCredential]$Credential ) begin { Write-Verbose "[$($MyInvocation.MyCommand)] Starting - PowerShell $($PSVersionTable.PSVersion)" # Event ID to Action mapping $lockActionMap = @{ 4800 = 'Locked' 4801 = 'Unlocked' } # Filter configuration for Security log $securityFilter = @{ LogName = 'Security' ID = 4800, 4801 StartTime = $StartTime } Write-Verbose "[$($MyInvocation.MyCommand)] Querying events since: $($StartTime.ToString('yyyy-MM-dd HH:mm:ss'))" } process { foreach ($computer in $ComputerName) { Write-Verbose "[$($MyInvocation.MyCommand)] Processing: $computer" # Build Get-WinEvent parameters $winEventParams = @{ FilterHashtable = $securityFilter ComputerName = $computer ErrorAction = 'Stop' } if ($PSBoundParameters.ContainsKey('Credential')) { $winEventParams['Credential'] = $Credential } try { # Query events from target computer $lockEvents = Get-WinEvent @winEventParams if ($null -eq $lockEvents -or $lockEvents.Count -eq 0) { Write-Verbose "[$($MyInvocation.MyCommand)] No lock/unlock events found on $computer" continue } Write-Verbose "[$($MyInvocation.MyCommand)] Retrieved $($lockEvents.Count) event(s) from $computer" # Process each event foreach ($lockEvent in $lockEvents) { try { $eventXml = [xml]$lockEvent.ToXml() # Extract user information from Security event $eventData = $eventXml.Event.EventData.Data $targetUserSid = ($eventData | Where-Object { $_.Name -eq 'TargetUserSid' }).'#text' $targetUserName = ($eventData | Where-Object { $_.Name -eq 'TargetUserName' }).'#text' $targetDomainName = ($eventData | Where-Object { $_.Name -eq 'TargetDomainName' }).'#text' $sessionName = ($eventData | Where-Object { $_.Name -eq 'SessionName' }).'#text' $fullUserName = if ($targetDomainName -and $targetUserName) { "$targetDomainName\$targetUserName" } elseif ($targetUserName) { $targetUserName } else { $targetUserSid } # Emit structured object [PSCustomObject]@{ PSTypeName = 'PSWinOps.RdpSessionLock' TimeCreated = $lockEvent.TimeCreated ComputerName = $computer UserName = $fullUserName SessionName = $sessionName Action = $lockActionMap[[int]$lockEvent.Id] EventID = $lockEvent.Id UserSID = $targetUserSid Timestamp = Get-Date -Format 'o' } } catch { Write-Warning "[$($MyInvocation.MyCommand)] Failed to parse event ID $($lockEvent.Id) on $computer - $_" } } } catch [System.Diagnostics.Eventing.Reader.EventLogException] { Write-Error "[$($MyInvocation.MyCommand)] Event log error on $computer - $_" } catch [System.UnauthorizedAccessException] { Write-Error "[$($MyInvocation.MyCommand)] Access denied to Security log on $computer - Requires elevated permissions" } catch [System.Exception] { Write-Error "[$($MyInvocation.MyCommand)] Failed to query $computer - $_" } } } end { Write-Verbose "[$($MyInvocation.MyCommand)] Completed" } } |