Analyze RDP Disconnection Logs using PowerShell

The PowerShell script is designed to extract information about Remote Desktop Protocol (RDP) local session manager events from the Windows event logs on a RDS host and save it to a CSV file.

Script: Get events with EventID 40 from Microsoft-Windows-TerminalServices-LocalSessionManager/Operational Event Log

$RDPAuths = Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'-FilterXPath '<QueryList><Query Id="0"><Select>*[System[EventID=40]]</Select></Query></QueryList>'

[xml[]]$xml = $RDPAuths | ForEach-Object { $_.ToXml() }

$EventData = $xml.Event | ForEach-Object {
    [PSCustomObject]@{
        TimeCreated = (Get-Date $_.System.TimeCreated.SystemTime -Format 'yyyy-MM-dd hh:mm:ss K')
        Session     = $_.UserData.EventXML.Session
        Reason      = $_.UserData.EventXML.Reason
        EventID     = $_.System.EventID
        User        = $_.UserData.EventXML.User
        SessionID   = $_.UserData.EventXML.SessionID
        Address     = $_.UserData.EventXML.Address
    }
}

$EventData | Export-Csv -Path c:\rdlog-LSM-Operational.csv -Encoding ASCII

Here is an explanation of how the code works.

  1. First, the script uses the Get-WinEvent cmdlet to retrieve events from the ‘Microsoft-Windows-TerminalServices-LocalSessionManager/Operational’ log. This log contains information about RDP disconnections. The events are stored in the $RDPAuths variable.
  2. The $RDPAuths variable is then piped to the ForEach-Object cmdlet to convert each event to XML format using the ToXml() method. The resulting array of XML objects is stored in the $xml variable.
  3. Next, the script uses the ForEach-Object cmdlet again to iterate over the $xml.Event array. For each event, a new custom object is created using the [PSCustomObject]@{} syntax. This custom object contains the following properties: TimeCreatedSessionReasonEventIDUserSessionID, and Address. These properties are extracted from the event XML data using dot notation.
  4. Finally, the custom objects are piped to the Export-Csv cmdlet, which saves the objects as a CSV file at the specified path (c:\rdlog-LSM-Operational.csv) with ASCII encoding.

This PowerShell script is useful for extracting and analyzing RDP local session manager events, such as monitoring for disconnect reasons. The resulting CSV file provides a simple, convenient way to view and analyze the RDP events.

RDS Session Host Server Disconnect Codes

RDS server client disconnect codeDisconnect reason
0x00000001The disconnection was initiated by an administrative tool on the server in another session.
0x00000002The disconnection was due to a forced logoff initiated by an administrative tool on the server in another session.
0x00000003The idle session limit timer on the server has elapsed.
0x00000004The active session limit timer on the server has elapsed.
0x00000005Another user connected to the server, forcing the disconnection of the current connection.
0x00000006The server ran out of available memory resources.
0x00000007The server denied the connection.
0x00000009The user cannot connect to the server due to insufficient access privileges.
0x0000000A (10)The server does not accept saved user credentials and requires that the user enter their credentials for each connection.
0x0000000B (11)The disconnection was initiated by the user disconnecting his or her session on the server or by an administrative tool on the server.
0x0000000C (12)The disconnection was initiated by the user logging off his or her session on the server.

Extended Disconnect Reason Codes

Reference: https://learn.microsoft.com/en-us/windows/win32/termserv/extendeddisconnectreasoncode

Extended ReasonCode
NoInfo0
APIInitiatedDisconnect1
APIInitiatedLogoff2
ServerIdleTimeout3
ServerLogonTimeout4
ReplacedByOtherConnection5
OutOfMemory6
ServerDeniedConnection7
ServerDeniedConnectionFips8
ServerInsufficientPrivileges9
ServerFreshCredsRequired10
RpcInitiatedDisconnectByUser11
LogoffByUser2
LicenseInternal256
LicenseNoLicenseServer257
LicenseNoLicense258
LicenseErrClientMsg259
LicenseHwidDoesntMatchLicense260
LicenseErrClientLicense261
LicenseCantFinishProtocol262
LicenseClientEndedProtocol263
LicenseErrClientEncryption264
LicenseCantUpgradeLicense265
LicenseNoRemoteConnections266
LicenseCreatingLicStoreAccDenied267
RdpEncInvalidCredentials768
ProtocolRangeStart4096
ProtocolRangeEnd32767

Could not establish trust relationship for the SSL/TLS secure channel.

Working with some older Cisco ASA devices, I’m trying to access the ASDM interface. The browser isn’t giving me luck, so I turned to PowerShell to help me, but I get the following error when trying an Invoke-WebRequest to grab the asdm.jnlp file I need.

The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

Eh, ok. My first thought was to somehow avoid a certificate check but I did not see a native way of doing this with Invoke-WebRequest (at least from an old Server 2008 box with PowerShell v4.0).

StackOverflow to the rescue. Here’s the solution that worked for me.

if (-not("dummy" -as [type])) {
    add-type -TypeDefinition @"
using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

public static class Dummy {
    public static bool ReturnTrue(object sender,
        X509Certificate certificate,
        X509Chain chain,
        SslPolicyErrors sslPolicyErrors) { return true; }

    public static RemoteCertificateValidationCallback GetDelegate() {
        return new RemoteCertificateValidationCallback(Dummy.ReturnTrue);
    }
}
"@
}

[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [dummy]::GetDelegate()

Now I can add on my Invoke-WebRequest and everything works.

Get Enabled AD Users with Last Logon Time and Organizational Unit Information

This PowerShell script retrieves information about enabled Active Directory (AD) users, including their SAM account name, last logon time, and organizational unit (OU). The script makes use of several cmdlets and concepts that are common in PowerShell, including filtering, selecting, sorting, and transforming data.

Get-ADUser -Filter * -Properties lastLogon |
    Where-Object { $_.Enabled -eq $True } |
    Select-Object samaccountname, @{
        Name="lastLogon";
        Expression={[datetime]::FromFileTime($_.lastLogon)}
    }, @{
        Name="OU";
        Expression={( $_.distinguishedname -split ',' )[1].Split('=')[1]}
    } |
    Sort-Object OU |
    Where-Object { $_.OU -notmatch "CN=" }

Here is a detailed explanation of each part of the code:

  1. Get-ADUser cmdlet:

The Get-ADUser cmdlet is used to retrieve information about AD user objects. The -Filter parameter is used to specify that I want to retrieve all user objects, and the -Properties parameter is used to specify that I want to retrieve the lastLogon property.

  1. Where-Object cmdlet:

The Where-Object cmdlet is used to filter the results of the Get-ADUser cmdlet based on the Enabled property. In this case, I want to retrieve only those users that have their Enabled property set to $True.

  1. Select-Object cmdlet:

The Select-Object cmdlet is used to select specific properties from the filtered results. In this case, I want to select the samaccountnamelastLogon, and OU properties. The @{Name="lastLogon";Expression={[datetime]::FromFileTime($_.lastLogon)}} expression is used to convert the lastLogon property from a file time format to a more readable date/time format. The @{Name="OU";Expression={( $_.distinguishedname -split ',' )[1].Split('=')[1]}} expression is used to extract the name of the OU from the DistinguishedName property.

  1. Sort-Object cmdlet:

The Sort-Object cmdlet is used to sort the selected results based on the distinguishedname property. In this case, I want to sort the results in ascending order by the distinguishedname property.

  1. Where-Object cmdlet:

The final Where-Object cmdlet is used to further filter the sorted results based on the distinguishedname property. In this case, I want to retrieve only those results where the distinguishedname property does not match the string “CN=”.

Check Windows Servers Activation Status

I needed a quick way to check activation status of Windows Servers in a domain. This is the solution I came up with using PowerShell to run the slmgr.vbs script for output. I’m not great with PowerShell, and I’m sure this can be cleaned up or made more efficient, but this ‘hack’ worked for me.

$computers = get-adcomputer -filter "OperatingSystem -Like '*Windows Server*' -and Enabled -eq 'True'" | select-object name

foreach($computer in $computers) {
	write-host $computer.name
	Invoke-Command { (cscript /Nologo "C:\Windows\System32\slmgr.vbs" /xpr) -join '' } -ComputerName $computer.name
}

The output will be one of the following, with variation to the edition:

Windows(R), ServerStandard edition:    Windows is in Notification mode
Windows Server(R), ServerDatacenter edition:    The machine is permanently activated.