Examples/Get-NetRDPSession.ps1

function Get-NetRDPSession 
{
    <#
    .SYNOPSIS
 
    Returns remote desktop/session information for the local (or a remote) machine.
 
    Note: only members of the Administrators or Account Operators local group
    can successfully execute this functionality on a remote target.
 
    Author: Will Schroeder (@harmj0y)
    License: BSD 3-Clause
    Required Dependencies: PSReflect
 
    .DESCRIPTION
 
    This function will execute the WTSEnumerateSessionsEx and WTSQuerySessionInformation
    Win32API calls to query a given RDP remote service for active sessions and originating
    IPs. This is a replacement for qwinsta.
 
    .PARAMETER ComputerName
 
    Specifies the hostname to query for active sessions (also accepts IP addresses).
    Defaults to 'localhost'.
 
    .EXAMPLE
 
    Get-NetRDPSession
 
    Returns active RDP/terminal sessions on the local host.
 
    .EXAMPLE
 
    Get-NetRDPSession -ComputerName "sqlserver"
 
    Returns active RDP/terminal sessions on the 'sqlserver' host.
 
    .OUTPUTS
 
    RDPSessionInfo
 
    A PSCustomObject representing a combined WTS_SESSION_INFO_1 and WTS_CLIENT_ADDRESS structure,
    with the ComputerName added.
 
    .LINK
 
    https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
    #>


    [OutputType('RDPSessionInfo')]
    [CmdletBinding()]
    Param(
        [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
        [Alias('HostName', 'dnshostname', 'name')]
        [ValidateNotNullOrEmpty()]
        [String[]]
        $ComputerName = 'localhost'
    )

    PROCESS {
        ForEach ($Computer in $ComputerName) {

            # open up a handle to the Remote Desktop Session host
            $Handle = $Wtsapi32::WTSOpenServerEx($Computer)

            # if we get a non-zero handle back, everything was successful
            if ($Handle -ne 0) {

                # arguments for WTSEnumerateSessionsEx
                $ppSessionInfo = [IntPtr]::Zero
                $pCount = 0

                # get information on all current sessions
                $Result = $Wtsapi32::WTSEnumerateSessionsEx($Handle, [ref]1, 0, [ref]$ppSessionInfo, [ref]$pCount);$LastError = [Runtime.InteropServices.Marshal]::GetLastWin32Error()

                # locate the offset of the initial intPtr
                $Offset = $ppSessionInfo.ToInt64()

                if (($Result -ne 0) -and ($Offset -gt 0)) {

                    # work out how much to increment the pointer by finding out the size of the structure
                    $Increment = $WTS_SESSION_INFO_1::GetSize()

                    # parse all the result structures
                    for ($i = 0; ($i -lt $pCount); $i++) {

                        # create a new int ptr at the given offset and cast the pointer as our result structure
                        $NewIntPtr = New-Object System.Intptr -ArgumentList $Offset
                        $Info = $NewIntPtr -as $WTS_SESSION_INFO_1

                        $RDPSession = New-Object PSObject

                        if ($Info.pHostName) {
                            $RDPSession | Add-Member Noteproperty 'ComputerName' $Info.pHostName
                        }
                        else {
                            # if no hostname returned, use the specified hostname
                            $RDPSession | Add-Member Noteproperty 'ComputerName' $Computer
                        }

                        $RDPSession | Add-Member Noteproperty 'SessionName' $Info.pSessionName

                        if ($(-not $Info.pDomainName) -or ($Info.pDomainName -eq '')) {
                            # if a domain isn't returned just use the username
                            $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pUserName)"
                        }
                        else {
                            $RDPSession | Add-Member Noteproperty 'UserName' "$($Info.pDomainName)\$($Info.pUserName)"
                        }

                        $RDPSession | Add-Member Noteproperty 'ID' $Info.SessionID
                        $RDPSession | Add-Member Noteproperty 'State' $Info.State

                        $ppBuffer = [IntPtr]::Zero
                        $pBytesReturned = 0

                        # query for the source client IP with WTSQuerySessionInformation
                        # https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx
                        $Result2 = $Wtsapi32::WTSQuerySessionInformation($Handle, $Info.SessionID, 14, [ref]$ppBuffer, [ref]$pBytesReturned);$LastError2 = [Runtime.InteropServices.Marshal]::GetLastWin32Error()

                        if ($Result2 -eq 0) {
                            Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError2).Message)"
                        }
                        else {
                            $Offset2 = $ppBuffer.ToInt64()
                            $NewIntPtr2 = New-Object System.Intptr -ArgumentList $Offset2
                            $Info2 = $NewIntPtr2 -as $WTS_CLIENT_ADDRESS

                            $SourceIP = $Info2.Address
                            if ($SourceIP[2] -ne 0) {
                                $SourceIP = [String]$SourceIP[2]+'.'+[String]$SourceIP[3]+'.'+[String]$SourceIP[4]+'.'+[String]$SourceIP[5]
                            }
                            else {
                                $SourceIP = $Null
                            }

                            $RDPSession | Add-Member Noteproperty 'SourceIP' $SourceIP
                            $RDPSession.PSObject.TypeNames.Insert(0, 'PowerView.RDPSessionInfo')
                            $RDPSession

                            # free up the memory buffer
                            $Null = $Wtsapi32::WTSFreeMemory($ppBuffer)

                            $Offset += $Increment
                        }
                    }
                    # free up the memory result buffer
                    $Null = $Wtsapi32::WTSFreeMemoryEx(2, $ppSessionInfo, $pCount)
                }
                else {
                    Write-Verbose "[Get-NetRDPSession] Error: $(([ComponentModel.Win32Exception] $LastError).Message)"
                }
                # close off the service handle
                $Null = $Wtsapi32::WTSCloseServer($Handle)
            }
            else {
                Write-Verbose "[Get-NetRDPSession] Error opening the Remote Desktop Session Host (RD Session Host) server for: $ComputerName"
            }
        }
    }
}