Script Center > Repository > Remote Desktop Services > VDI & RDSH : Monitor sessions
TechNet Script Center logo

Welcome to the TechNet Script Center Repository!

Each contribution is licensed to you under a License Agreement by its owner, not Microsoft. Microsoft does not guarantee the contribution or purport to grant rights to it.

VDI & RDSH : Monitor sessions

(Microsoft)
VERIFIED AND TESTED BY THE SCRIPT CENTER TEAM
Rate it:
 
 
 
 
 
Script Code
Windows PowerShell
# Type definition for interop

$WTSTypes = @"
using System;
using System.Text;
using System.Runtime.InteropServices;

namespace RDSManager.PowerShell
{
    [StructLayout(LayoutKind.Sequential)]
    public struct WTSSessionInfo
    {
        public Int32 SessionID;
        [MarshalAs(UnmanagedType.LPStr)]
        public String WinStationName;
        public WTSConnectState State;
    }

    public struct WTSSessionInfoEx
    {
        public Int32 ExecEnvId;
        public WTSConnectState State;
        public Int32 SessionID;

        [MarshalAs(UnmanagedType.LPStr)]
        public String SessionName;

        [MarshalAs(UnmanagedType.LPStr)]
        public String HostName;

        [MarshalAs(UnmanagedType.LPStr)]
        public String UserName;

        [MarshalAs(UnmanagedType.LPStr)]
        public String DomainName;

        [MarshalAs(UnmanagedType.LPStr)]
        public String FarmName;
    }

    public enum WTSConnectState
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WTSClient
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
        public String ClientName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 18)]
        public String Domain;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
        public String UserName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)]
        public String WorkDirectory;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)]
        public String InitialProgram;
        public byte EncryptionLevel;
        public UInt32 ClientAddressFamily;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 31)]
        public UInt16[] ClientAddress;
        public UInt16 HRes;
        public UInt16 VRes;
        public UInt16 ColorDepth;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)]
        public String ClientDirectory;
        public UInt32 BuildNumber;
        public UInt32 HardwareId;
        public UInt16 ProductId;
        public UInt16 OutBufCountHost;
        public UInt16 OutBufCountClient;
        public UInt16 OutBufLengh;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct WTSSessionStatus
    {
        public WTSConnectState State;
        public Int32 SessionId;
        public Int32 IncomingBytes;
        public Int32 OutgoingBytes;
        public Int32 IncomingFrames;
        public Int32 OutgoingFrames;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public String WinStationName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 17)]
        public String Domain;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
        public String User;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 56)]
        public String ConnectTime;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 56)]
        public String DisconnectTime;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 56)]
        public String LastInputTime;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 56)]
        public String LogonTime;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 56)]
        public String CurrentTime;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 56)]
        public String IdleTime;
    }

    public enum WTSInfoType
    {
        WTSInitialProgram = 0,
        WTSApplicationName = 1,
        WTSWorkingDirectory = 2,
        WTSOEMId = 3,
        WTSSessionId = 4,
        WTSUserName = 5,
        WTSWinStationName = 6,
        WTSDomainName = 7,
        WTSConnectState = 8,
        WTSClientBuildNumber = 9,
        WTSClientName = 10,
        WTSClientDirectory = 11,
        WTSClientProductId = 12,
        WTSClientHardwareId = 13,
        WTSClientAddress = 14,
        WTSClientDisplay = 15,
        WTSClientProtocolType = 16,
        WTSIdleTime = 17,
        WTSLogonTime = 18,
        WTSIncomingBytes = 19,
        WTSOutgoingBytes = 20,
        WTSIncomingFrames = 21,
        WTSOutgoingFrames = 22,
        WTSClientInfo = 23,
        WTSSessionInfo = 24
    }

    public class RDSession
    {
        public string Server;
        public string Session;
        public string User;
        public int SessionID;
        public WTSConnectState State;
        public string ProtocolType;
        public string Client;
        public string IdleTime;
        public string LogonTime;
        public string Host;
    }
    
    public class RDProcess
    {
        public string Server;
        public string Host;
        public string User;
        public string Session;
        public int SessionID;
        public int ProcessID;
        public string Name;
    }

    public class RDSessionStatus
    {
        public string Server;
        public string Host;
        public Int32 SessionID;
        public string User;
        public string ClientName;
        public string ClientAddress;
        public string ClientBuildNumber;
        public string ClientDirectory;
        public int ClientProductID;
        public string ClientColorDepth;
        public int ClientHardwareID;
        public string ClientResolution;
        public string EncryptionLevel;
        public int InputBytes;
        public int OutputBytes;
        public int InputFrames;
        public int OutputFrames;
    }

    public class RDSManager
    {
        [DllImport("wtsapi32.dll")]
        public static extern IntPtr WTSOpenServer(
            [MarshalAs(UnmanagedType.LPStr)] String pServerName
        );

        [DllImport("wtsapi32.dll")]
        public static extern IntPtr WTSOpenServerEx(
            [MarshalAs(UnmanagedType.LPStr)] String pServerName
        );

        [DllImport("wtsapi32.dll")]
        public static extern void WTSCloseServer(
            IntPtr hServer
        );

        [DllImport("wtsapi32.dll")]
        public static extern Int32 WTSEnumerateSessions(
            IntPtr hServer,
            [MarshalAs(UnmanagedType.U4)] Int32 Reserved,
            [MarshalAs(UnmanagedType.U4)] Int32 Version,
            ref IntPtr ppSessionInfo,
            [MarshalAs(UnmanagedType.U4)] ref Int32 pCount
        );

        [DllImport("wtsapi32.dll")]
        public static extern Int32 WTSEnumerateSessionsEx(
            IntPtr hServer,
            [MarshalAs(UnmanagedType.U4)] ref Int32 pLevel,
            [MarshalAs(UnmanagedType.U4)] Int32 Filter,
            ref IntPtr ppSessionInfo,
            [MarshalAs(UnmanagedType.U4)] ref Int32 pCount
        );

        [DllImport("wtsapi32.dll")]
        public static extern void WTSFreeMemory(
            IntPtr pMemory
        );

        [DllImport("wtsapi32.dll")]
        public static extern Int32 WTSQuerySessionInformation(
            IntPtr hServer,
            [MarshalAs(UnmanagedType.U4)] Int32 SessionId,
            [MarshalAs(UnmanagedType.U4)] Int32 WTSInfoClass,
            ref IntPtr ppBuffer,
            [MarshalAs(UnmanagedType.U4)] ref Int32 BytesReturned
        );

        [DllImport("Wts.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool QueryAllSessionInformation(
            IntPtr hServer,
            Int32 SessionId,
            ref WTSSessionStatus pSessionInfo
        );

        [DllImport("certpick.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern int TSEnumerateProcessInitialize(
            IntPtr hServer,
            ref int NumberOfProcesses
        );

        [DllImport("certpick.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern int TSGetNextProcessInfo(
            int Index,
            string ServerName,
            ref int SessionId,
            ref int ProcessId,
            StringBuilder ProcessName,
            int ProcessNameSize,
            StringBuilder UserName,
            int UserNameSize
        );

        [DllImport("certpick.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern int TSEnumerateProcessRelease();

        [DllImport("Wtsapi32.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern bool WTSTerminateProcess(
            IntPtr hServer,
            Int32 ProcessId,
            Int32 ExitCode
        );

        [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern int WTSGetActiveConsoleSessionId();

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentProcessId();

        [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern bool ProcessIdToSessionId(
            Int32 ProcessId,
            ref Int32 SessionId
        );

        [DllImport("Wtsapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool WTSSendMessage(
            IntPtr hServer,
            Int32 SessionId,
            string Title,
            Int32 TitleLength,
            string Message,
            Int32 MessageLength,
            Int32 Style,
            Int32 Timeout,
            ref IntPtr Response,
            bool Wait
        );

        [DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern bool WTSConnectSession(
            UInt32 LogonId,
            UInt32 TargetLogonId,
            string Password,
            bool Wait
        );

        [DllImport("Wtsapi32.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern bool WTSDisconnectSession(
            IntPtr hServer,
            int SessionId,
            bool Wait
        );

        [DllImport("Wtsapi32.dll", SetLastError = false, CharSet = CharSet.Auto)]
        public static extern bool WTSLogoffSession(
            IntPtr hServer,
            int SessionId,
            bool Wait
        );

        [DllImport("wtsapi32.dll")]
        public static extern bool WTSStartRemoteControlSession(
            [MarshalAs(UnmanagedType.LPStr)] String pServerName,
            UInt32 TargetLogonId,
            Byte HotkeyVk,
            UInt16 HotkeyModifiers
        );
    }
}
"@

# Create new types as per the definition above.
Add-Type -TypeDefinition $WTSTypes

# Valid states(values) for various operations(keys). Valid states are represented by the values of the Keys.
$StateOperations = @{
    "Send-Message" = @( "WTSActive" )
    "Get-RDSessionStatus" = @( "WTSActive" )
    "Connect-RDSession" = @( "WTSActive", "WTSDisconnected" )
    "Disconnect-RDSession" = @( "WTSActive" )
    "Start-RDRemoteControlSession" = @( "WTSActive" )
}

function Ping-Computer
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0, ValueFromPipeline=$TRUE)]
    [string]
    $ComputerName
)

	Get-WmiObject -ComputerName $ComputerName Win32_ComputerSystem -ErrorVariable exp -ErrorAction SilentlyContinue | Out-Null
	return ((-not $exp) -OR ($exp[0].Exception.ErrorCode -ne -2147023174))

}

function IsConsoleSession
{

param(    
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID = -1
)

    if ($SessionID -eq -1)
    {
        [RDSManager.PowerShell.RDSManager]::ProcessIdToSessionId([RDSManager.PowerShell.RDSManager]::GetCurrentProcessId(), [ref] $SessionID) | Out-Null
    }

    return ([RDSManager.PowerShell.RDSManager]::WTSGetActiveConsoleSessionId() -eq $SessionID)
}

function IsCurrentSession
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID
)

    $CurrentSessionID = -1
    [RDSManager.PowerShell.RDSManager]::ProcessIdToSessionId([RDSManager.PowerShell.RDSManager]::GetCurrentProcessId(), [ref] $CurrentSessionID) | Out-Null

    return ($CurrentSessionID -eq $SessionID)
}

function Handle-Error
{

param(
    [Parameter(Mandatory=$TRUE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $ErrorID,
	
	[Parameter(Mandatory=$TRUE, Position=1)]
    [string]
    $Message
)

Write-Warning ">>> $ErrorID : $Message"

    switch ($ErrorID)
    {
        5   {Write-Error "$Message : Access is denied.";break}
    }
}

function Get-RDSessionInfoEntry
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0)]
    [System.IntPtr]
    $ServerPtr,

    [Parameter(Mandatory=$TRUE, Position=1)]
    [int]
    $SessionID,
    
    [Parameter(Mandatory=$TRUE, Position=2)]
    [RDSManager.PowerShell.WTSInfoType]
    $Entry
)

    if ($ServerPtr -eq [System.IntPtr]::Zero)
    {
        return
    }
    
    $entryInfo = [System.IntPtr]::Zero
    $bytes = 0
    
    $retval = [RDSManager.PowerShell.RDSManager]::WTSQuerySessionInformation($ServerPtr, $SessionID, $Entry, [ref] $entryInfo, [ref] $bytes)
    $returnObj = $NULL
    
    if($retval -ne 0)
    {
        switch ($Entry)
        {
            "WTSClientName"             {$returnObj = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($entryInfo, $bytes); break}
            "WTSClientProtocolType"     {$returnObj = [System.Runtime.InteropServices.Marshal]::ReadInt16($entryInfo); break}
            "WTSIdleTime"               {$returnObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($entryInfo); break}
            "WTSLogonTime"              {$returnObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($entryInfo); break}
            "WTSClientDirectory"        {$returnObj = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($entryInfo, $bytes); break}
            "WTSClientInfo"             {$returnObj = [RDSManager.PowerShell.WTSClient][System.Runtime.InteropServices.Marshal]::PtrToStructure($entryInfo, [RDSManager.PowerShell.WTSClient]); break}
            "WTSSessionInfo"            {$returnObj = [RDSManager.PowerShell.WTSSessionStatus][System.Runtime.InteropServices.Marshal]::PtrToStructure($entryInfo, [RDSManager.PowerShell.WTSSessionStatus]); break}
        }
        
        [RDSManager.PowerShell.RDSManager]::WTSFreeMemory($entryInfo);
        $entryInfo = [System.IntPtr]::Zero
    }
    
    return $returnObj
}

function Get-ProtocolName
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0)]
    [int]
    $ProtocolID
)

    $protocolType = "Unknown"

    switch ($ProtocolID)
    {
        0   {$protocolType = "Console"; break}
        1   {$protocolType = "Citrix ICA"; break}
        2   {$protocolType = "Microsoft RDP"; break}
    }
    
    return $protocolType
}

function Get-ClientAddress
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0)]
    [System.UInt32]
    $ClientAddressFamily,
    
    [Parameter(Mandatory=$TRUE, Position=1)]
    [System.UInt16[]]
    $ClientAddress
)
    $address = "Unknown"

    switch ($ClientAddressFamily)
    {
        23  {
                if (($ClientAddress[6] -eq 0) -AND ($ClientAddress[7] -eq 0))
                {
                    $address = [string]::Join(":", ($ClientAddress[0..5] | %{$_.ToString("X")}))                    
                }
                else
                {
                    $address = [string]::Join(":", ($ClientAddress[0..7] | %{$_.ToString("X")}))                    
                }                
                break
            }
        2   {
                $address = [string]::Join(".", $ClientAddress[0..3])
                break
            }
    }

    return $address
}

function Get-ColorDepth
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0)]
    [int]
    $ColorDepth
)

    $colorDepthString = "Unknown"

    switch ($ColorDepth)
    {
        2   {$colorDepthString = "8 bit"; break}
        16  {$colorDepthString = "15 bit"; break}
        4   {$colorDepthString = "16 bit"; break}
        8   {$colorDepthString = "24 bit"; break}
        32  {$colorDepthString = "32 bit"; break}
    }
    
    return $colorDepthString
}

function Get-EncryptionLevel
{

param(    
    [Parameter(Mandatory=$TRUE, Position=0)]
    [int]
    $EncryptionLevel
)

    $encryptionLevelString = ""

    switch ($EncryptionLevel)
    {
        1   {$encryptionLevelString = "Low"; break}
        2   {$encryptionLevelString = "Client Compatible"; break}
        3   {$encryptionLevelString = "High"; break}
        4   {$encryptionLevelString = "FIPS Compliant"; break}
    }
    
    return $colorDepthString
}

function Get-RDSHSession
{

param(    
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$FALSE, HelpMessage="List all sessions, including services, listener etc")]
    [Switch]
    $ListAll
)

	if (($Server -ne "localhost") -AND (!(Ping-Computer $Server))) {
        Write-Error ("'{0}' is not reachable." -f $Server)
	    return
    }

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)

    if ($ServerPtr -eq [System.IntPtr]::Zero) {
        Write-Error ("Failed to connect to {0}" -f $Server)
	    return
    }
    
    $sessInfo = [System.IntPtr]::Zero 
    $count = 0

    $result = [RDSManager.PowerShell.RDSManager]::WTSEnumerateSessions($ServerPtr, 0, 1, [ref] $sessInfo, [ref] $count)
    
	if (($result -eq 0) -and ($count -eq 0))
	{
		Write-Warning "You might not have permissions to enumerate sessions on server $Server"
		return
	}
	
    if(($result -ne 0) -AND ($sessInfo -ne [System.IntPtr]::Zero))
    {
        $structSize = [System.Runtime.InteropServices.Marshal]::sizeof([RDSManager.PowerShell.WTSSessionInfo])

        for ($ind = 0; $ind -lt $count; $ind++)
        {
            $sessionInfo = ([RDSManager.PowerShell.WTSSessionInfo]([System.Runtime.InteropServices.Marshal]::PtrToStructure([int]$sessInfo + $ind * $structSize, [RDSManager.PowerShell.WTSSessionInfo])))
            
            $sessionDetails = New-Object RDSManager.PowerShell.WTSSessionStatus
            $result = [RDSManager.PowerShell.RDSManager]::QueryAllSessionInformation($ServerPtr, $sessionInfo.SessionId, [ref] $sessionDetails)
            
            if ((!$ListAll) -AND ([string]::IsNullOrEmpty($sessionDetails.User)))
            {
                continue;
            }
            
            $sessionObj = New-Object RDSManager.PowerShell.RDSession # Can use with -Set
            
            $sessionObj.Session = $sessionInfo.WinStationName
            $sessionObj.User = $sessionDetails.User
            $sessionObj.SessionID = $sessionInfo.SessionId
            $sessionObj.State = $sessionInfo.State
            $sessionObj.ProtocolType = (Get-ProtocolName (Get-RDSessionInfoEntry $ServerPtr $sessionInfo.SessionId WTSClientProtocolType))
            $sessionObj.Client = (Get-RDSessionInfoEntry $ServerPtr $sessionInfo.SessionId WTSClientName)
            $sessionObj.IdleTime = $sessionDetails.IdleTime.Replace('+','.').Trim('.')
            $sessionObj.LogonTime = $sessionDetails.LogonTime
            $sessionObj.Server = $Server
            
            $sessionObj
        }

        [RDSManager.PowerShell.RDSManager]::WTSFreeMemory($sessInfo);
        $sessInfo = [System.IntPtr]::Zero
    }
    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
}

function Get-RDSHProcess
{

param(    
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipeline=$TRUE, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$FALSE,
        HelpMessage="List all process, from all sessions")]
    [Switch]
    $ListAll
)

	if (($Server -ne "localhost") -AND (!(Ping-Computer $Server))) {
        Write-Error ("'{0}' is not reachable." -f $Server)
	    return
    }

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)
    
    if ($ServerPtr -eq [System.IntPtr]::Zero)
    {
        Write-Error ("Failed to connect to {0}" -f $Server)
	    return
    }

    $processCount = 0
    $result = [RDSManager.PowerShell.RDSManager]::TSEnumerateProcessInitialize($ServerPtr, [ref] $processCount)
	
	switch($result)
	{
		-2147024891 {Write-Error "Access denied. Cannot enumerate processes on: $Server."; return}
		0			{break}
		default		{Write-Error "Error! Cannot list Processes on: $Server."; return}
	}
    
    $sessions = @{}
    foreach ($session in (Get-RDSHSession $Server -ListAll:$ListAll))
    {
        $sessions[$session.SessionId] = $session
    }
    
    foreach ($i in 1..$processCount)
    {
        $userName       = New-Object System.Text.StringBuilder 260
        $processName    = New-Object System.Text.StringBuilder 260
        $sessionId = 0
        $processId = 0
        
        $result = [RDSManager.PowerShell.RDSManager]::TSGetNextProcessInfo($i, $Server, [ref] $sessionId, [ref] $processId, $processName, $processName.Capacity, $userName, $userName.Capacity)
        
        if (($result -ne 0) -OR (!$sessions.ContainsKey($sessionId))) { continue }
        
        $processObj = New-Object RDSManager.PowerShell.RDProcess
        
        $processObj.ProcessID = $processId
        $processObj.SessionId = $sessionId
        $processObj.Name = $processName
        $processObj.User = $userName
        $processObj.Session = $sessions[$sessionId].Session
        $processObj.Server = $Server
        
        $processObj
    }
    
    [RDSManager.PowerShell.RDSManager]::TSEnumerateProcessRelease() | Out-Null    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
}

filter Get-RDSessionStatus
{
<# 
.Synopsis 
    Gets additional information about a user session on a Remote Desktop server.
    
.Description 
    This function retrieves additional information about a user session on a Remote Desktop server. The information can be queried through either the session ID of the session or the session object.
    
    Considerations : 
        1. To view status information for a session other than the logged in user's session, the script is to be executed with "full control" or "query information" special access permissions.
        2. The script does not fetch status information for the console or listener sessions.
    
.Parameter Server
    Optional parameter to specify the name of the Remote Desktop server for which the session information is to be enumerated. If not specified, the value is defaulted to localhost. The parameter accepts either the NETBIOS name or the fully-qualified domain name of the Remote Desktop server.
	
.Parameter SessionID
    Session ID of the session for which the information is to be retrieved. To find the session ID of a session, type "Get-RDSession".
    
.Example 
    PS C:\> Get-RDSessionStatus -SessionID 2
    
    Gets additional details for the session with session ID 2.
    
.Example 
    PS C:\> Get-RDUser -Server RDServer01 -SessionID 3
    
    Gets additional details for the session with session ID 3, on the Remote Desktop server named RDServer01.
    
.Inputs
    Session object with "Server" and "SessionID" property specified.
    
.Outputs 
    Status Object.
    RDSManager.PowerShell.RDSessionStatus object is returned. This object contains following information of a session:
    
    Property            Description 
    ----------------------------------------------------
    Server              The name of the Remote Desktop server to which the user is logged on.
    Host                The name of the RD virtualization host server on which the VM is running.
    SessionID           The numeric ID that identifies the session to the Remote Desktop server.
    User                The name of the user account that is logged on to the session on the Remote Desktop server.
    ClientName          The name of the client computer.
    ClientAddress       The IP address of the client.
    ClientBuildNumber   The version of the software installed on the client computer.    
    ClientDirectory     The directory in which the client is installed.
    ClientProductID     The product identifier of the client computer.
    ClientColorDepth    The number of colors in the color palette used for the remote session.
    ClientHardwareID    The specific hardware identifier of the client computer.
    ClientResolution    The video resolution of the remote session.
    EncryptionLevel     The encryption level being used for this session.
    InputBytes          The uncompressed Remote Desktop Protocol (RDP) data from the client to the server.
    OutputBytes         The uncompressed Remote Desktop Protocol (RDP) data from the server to the client.
    InputFrames         The Remote Desktop Protocol (RDP) frames from the client to the server.
    OutputFrames        The Remote Desktop Protocol (RDP) frames from the server to the client.
    

.Link 
    Get-RDSession
#>

param(    
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$TRUE, Position=1, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID
)

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)
    
    if ($ServerPtr -eq [System.IntPtr]::Zero) {
        Write-Error ("Failed to connect to: {0}" -f $Server)
	    return
    }

    $session = Get-RDSHSession $Server -ListAll | ? {$_.SessionID -eq $SessionID}
    if ($session -eq $NULL)
    {
        Write-Error ("Failed to get status of session: {0} on server: {1}. Check if the server and session ID are valid." -f $SessionID, $Server)
        return
    }

    if ($session.Session -eq "Console")
    {
        Write-Error ("The session {0} on server {1} is a console session. The script does not fetch status information for the console or listener sessions." -f $SessionID, $Server)
        return    
    }

    if ($StateOperations['Get-RDSessionStatus'] -notcontains $session.State)
    {
        Write-Error ("Failed to get status of session {0} on server {1}. The session is not active." -f $SessionID, $Server)
        return
    }

    $clientInfo = Get-RDSessionInfoEntry $ServerPtr $SessionID WTSClientInfo
    $sessionInfo = Get-RDSessionInfoEntry $ServerPtr $SessionID WTSSessionInfo
    
    $sessionStatus = New-Object RDSManager.PowerShell.RDSessionStatus
    
	$sessionStatus.Server = $Server
    $sessionStatus.Host = $session.Host
	$sessionStatus.SessionID = $SessionID
    $sessionStatus.User = $session.User
    $sessionStatus.ClientName = $clientInfo.ClientName
    $sessionStatus.ClientAddress = (Get-ClientAddress $clientInfo.ClientAddressFamily $clientInfo.ClientAddress)
    $sessionStatus.ClientBuildNumber = $clientInfo.BuildNumber
    $sessionStatus.ClientDirectory = (Get-RDSessionInfoEntry $ServerPtr $SessionID WTSClientDirectory)
    $sessionStatus.ClientProductID = $clientInfo.ProductId
    $sessionStatus.ClientColorDepth = (Get-ColorDepth $clientInfo.ColorDepth)
    $sessionStatus.ClientHardwareID = $clientInfo.HardwareId
    $sessionStatus.ClientResolution = ("{0} x {1}" -f $clientInfo.HRes, $clientInfo.VRes)
    $sessionStatus.EncryptionLevel = (Get-EncryptionLevel $clientInfo.EncryptionLevel)
    $sessionStatus.InputBytes = $sessionInfo.IncomingBytes
    $sessionStatus.OutputBytes = $sessionInfo.OutgoingBytes
    $sessionStatus.InputFrames = $sessionInfo.IncomingFrames
    $sessionStatus.OutputFrames = $sessionInfo.OutgoingFrames    
    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
    
    return $sessionStatus
}

filter Stop-RDProcess
{
<# 
.Synopsis 
    Stops a process running in the specified user session.
    
.Description 
    This function kills a process in the specified user session. You can specify a process by process ID or pass a RDProcess object to the Stop-RDProcess function. 
    Note: Killing a process running in a user session without warning the user can result in potential loss of data.

    Considerations : 
        1. The script is expected to be executed with Full Control permission to kill a process running in the specified user session.        
        2. When all processes running in a session end, the session also ends.
    
.Parameter Server
    Optional parameter to specify the name of the Remote Desktop server on which the process is running. If not specified, the value is defaulted to localhost. The parameter accepts either the NETBIOS name or the fully-qualified domain name of the Remote Desktop server.

.Parameter ProcessID
    Process ID of the process to be killed. To find the process ID of a process, execute "Get-RDProcess" function.
    
.Example 
    PS C:\> Stop-RDProcess -ProcessID 1234
    
    Kills the process identified by the process ID 1234, on local server.
    
.Example 
    PS C:\> Stop-RDProcess -Server RDServer01 -ProcessID 1234
    
    Kills the process identified by process ID 1234, on Remote Desktop server named RDServer01.
    
.Inputs
    Objects with the "Server" and "ProcessID" property specified.
    
.Outputs
    None.

.Link 
    Get-RDProcess
#>

param(
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$TRUE, Position=1, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $ProcessID
)

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)
    
    if ($ServerPtr -eq [System.IntPtr]::Zero)
    {
        Write-Error ("Failed to connect to server: {0}." -f $Server)
        return
    }
    
    if (![RDSManager.PowerShell.RDSManager]::WTSTerminateProcess($ServerPtr, $ProcessID, 0))
    {
        Write-Error ("Failed to terminate the process: {0} on server: {1}." -f $ProcessID, $Server)
    }
    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
}

filter Send-RDMessage
{
<# 
.Synopsis 
    Sends a message to a user session on the Remote Desktop Server.
    
.Description 
    This function sends a message to a user session on the Remote Desktop Server. The user session is identified by session ID or Session or user object.
    
    Considerations : 
        1. The script is to be executed with "message special access permission" to send a message to a user.
        2. The script can be used only to send messages to users whose sessions are in the active or connected state.        
		3. The script cannot be used to send messages to services or listener sessions.
    
.Parameter Server
    Optional parameter to specify the name of the Remote Desktop server on which the process is running. If not specified, the value is defaulted to localhost. The parameter accepts either the NETBIOS name or the fully-qualified domain name of the Remote Desktop server.

.Parameter SessionID
    Session ID of the session. To find the session ID of a session, execute "Get-RDSession" function.

.Parameter Message
    Message that needs to be sent to the user.

.Parameter Title 
 	Optional parameter to specify title of the message box. If not specified the value defaults to the sender's name and the time when the message is being sent.    
    
.Example 
    PS C:\> Send-RDMessage -SessionID 2 -Message "Welcome!!!"
    
    Sends the message, "Welcome!!!", to session identified by session ID 2, on local server.
    
.Example 
    PS C:\> Send-RDMessage -Server RDServer01 -SessionID 3 -Message "Please save your work and log out... the system needs a reboot." -Title "Urgent..."
    
    Sends the specified message "Please save your work and log out... the system needs a reboot." with title "Urgent..." to session identified by session ID 3, on the Remote Desktop server named RDServer01.
    
.Inputs
    Objects with the "Server" and "SessionID" property specified.
    
.Outputs 
    None.

.Link 
    Get-RDSession
#>

param(
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$TRUE, Position=1, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID,
    
    [Parameter(Mandatory=$TRUE, Position=2)]
    [string]
    $Message,

    [Parameter(Mandatory=$FALSE)]
    [string]
    $Title = ("Message from: {0}, Sent: {1}" -f $Env:USERNAME, (Get-Date))
)

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)
    
    if ($ServerPtr -eq [System.IntPtr]::Zero)
    {
        Write-Error ("Failed to connect to {0}" -f $Server)
        return
    }
    
    $session = Get-RDSHSession $Server | ? {$_.SessionID -eq $SessionID}
    if ($session -eq $NULL)
    {
        Write-Error ("Failed to send message to session {0} on server {1}. Check if the server and session id are valid." -f $SessionID, $Server)
        return
    }

    if (($StateOperations['Send-Message'] -notcontains $session.State) -AND !(IsConsoleSession $SessionID))
    {
        Write-Error ("Failed to send message to session {0} on server {1}. Session is not active." -f $SessionID, $Server)
        return
    }
    
    $Response = [System.IntPtr]::Zero    
    if (![RDSManager.PowerShell.RDSManager]::WTSSendMessage($ServerPtr, $SessionID, $Title, $Title.Length * 2, $Message, $Message.Length * 2, 0, 0, [ref] $Response, $FALSE)) {
        Handle-Error ([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
    }
    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
}

function Connect-RDSession
{
<# 
.Synopsis 
    Connects to a user session on the local Server.
    
.Description 
    This function connects to a user session, on the local server. The user session is identified by session ID or Session or user object.
    
    Considerations :         
        1. To connect to the session of a user other than the logged in user, the script must be executed with "full control" or "connect special" access permission.
        3. The script can be used to connect to a user session only if the user session is either in active or disconnected state.
        4. The script can be used to connect to a user session only when executed from an existing remote session on the same RD Session Host server.
        5. The script cannot be used to connect to services or console or listener sessions.
    
.Parameter SessionID
    Session ID of the session. To get the session ID of a session, execute "Get-RDSession" function.

.Parameter Password
    Password to authorize the connection.

.Example 
    PS C:\> Connect-RDSession -SessionID 2 -Password $pwd
    
    Connects to the session identified by session ID 2, on local server.
    
.Inputs
    Objects with "SessionID" property specified.
    
.Outputs 
    None.

.Link 
    Disconnect-RDSession
    Start-RDRemoteControlSession
    Get-RDSession    
#>

param(    
    [Parameter(Mandatory=$TRUE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID,
    
    [Parameter(Mandatory=$TRUE)]
    [System.Security.SecureString]
    $Password = (Read-Host -AsSecureString -Prompt "Password")
)

    if (IsConsoleSession)
    {
        Write-Error "The script cannot connect to a session from a console session. To connect to a user session please excute the script from an existing remote session on the same RD Session Host server."
        return
    }
    
    $Server = "localhost"

    $session = Get-RDSHSession $Server | ? {$_.SessionID -eq $SessionID}

    if ($session -eq $NULL)
    {
        Write-Error ("Failed to connect to session: {0}. Please verify that the session ID is valid." -f $SessionID)
        return
    }

    if ($StateOperations['Connect-RDSession'] -notcontains $session.State)
    {
        Write-Error ("Failed to connect to session: {0}. The target session is not active or not disconnected." -f $SessionID)
        return
    }

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)
    
    if ($ServerPtr -eq [System.IntPtr]::Zero)
    {
        Write-Error ("Failed to connect to: {0}" -f $Server)
	    return
    }

    $pwdString = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($Password)
    $result = [RDSManager.PowerShell.RDSManager]::WTSConnectSession($SessionID, [System.UInt32]::MaxValue, [System.Runtime.InteropServices.Marshal]::PtrToStringUni($pwdString), $TRUE)
    if (!$result)    
    {
        Write-Error ("Failed to connect to session: {0}. Please verify that the credentials are valid." -f $SessionID)
	    return
    }
    
    Handle-Error ([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
    [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($pwdString)

    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
}

filter Disconnect-RDSession
{
<# 
.Synopsis 
    Disconnects or Logs off a user session on a Remote Desktop Server.
    
.Description 
    This function disconnects or Logs off a user session on a Remote Desktop Server. The user session is identified by session ID or Session or user object.
    
    Considerations :         
        1. To disconnect a user other than the logged user from a session, the script must be executed with "full control" or "disconnect" special access permission.
        2. All processes running in the session, including applications, will continue to run even after the session is disconnected.        
        3. To log off a user other than the logged user from a session, the script must be executed with "full control" access permission.
        4. All processes end, and the session is deleted from the RD Session Host server when the session is logged off.        
		5. The script cannot be used to disconnect services or listener sessions.

.Parameter Server
    Optional parameter to specify the name of the Remote Desktop server on which the process is running. If not specified, the value is defaulted to localhost. The parameter accepts either the NETBIOS name or the fully-qualified domain name of the Remote Desktop server.

.Parameter SessionID
    Session ID of the session. To get the session ID of a session, execute "Get-RDSession" function.

.Parameter Logoff
    Switch: If specified, user is logged off from his session. Defaults to disconnecting the session if not specified.

.Example 
    PS C:\> Disconnect-RDSession -SessionID 2
    
    Disconnects user from the session identified by session ID 2, on local server.
    
.Example 
    PS C:\> Disconnect-RDSession -Server RDServer01 -SessionID 3 -Logoff
    
    Logs off user from the session identified by session ID 3, on the Remote Desktop server named RDServer01.

.Inputs
    Objects with "Server" and "SessionID" property specified.
    
.Outputs 
    None.

.Link 
    Connect-RDSession
    Start-RDRemoteControlSession
    Get-RDSession
#>

param(    
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$TRUE, Position=1, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID,
    
    [switch]
    $Logoff
)

    $session = Get-RDSHSession $Server | ? {$_.SessionID -eq $SessionID}
    $action = if ($Logoff) {"log-off"} else {"disconnect"}
    
    if ($session -eq $NULL)
    {
        Write-Error ("Failed to $action session {0} on server {1}. Check if the server and session id are valid." -f $SessionID, $Server)
        return
    }
    
    if (!$Logoff -AND ($StateOperations['Disconnect-RDSession'] -notcontains $session.State))
    {
        Write-Error ("Failed to disconnect session {0} on server {1}. Session is not active." -f $SessionID, $Server)
        return
    }

    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServer($Server)
    
    $result = $TRUE
    if ($Logoff)
    {
        $result = [RDSManager.PowerShell.RDSManager]::WTSLogoffSession($ServerPtr, $SessionID, $FALSE)
    }
    else
    {
        $result = [RDSManager.PowerShell.RDSManager]::WTSDisconnectSession($ServerPtr, $SessionID, $FALSE)
    }
    
    if (!$result)
    {
        Write-Error ("Failed to $action session {0} on server {1}. Check if you have required permissions." -f $SessionID, $Server)
        return
    }
    Handle-Error ([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
}

function Start-RDRemoteControlSession
{
<# 
.Synopsis 
    Mirrors a user session to be controlled remotely.
    
.Description 
    This function facilitates remote control of a user session. The user session is identified by session ID or Session or user object.
    To end the remote control session, hit Control<Ctrl> key along with product<*> sign.
    
    Considerations :         
        1. A session other than the logged in user session can be remotely controled only from within a remote session.
        2. The script can be used to remotely control a session other than the logged in user's session, only when executed with "full control" or "remote control" special access permission.                
        3. Before monitoring begins, the server warns the user that the session is about to be remotely controlled, unless this warning is disabled, the granted session might appear to be frozen for a few seconds while it waits for a response from the user.
        4. The logged in session must be capable of supporting the video resolution used at the session that you are remotely controlling, or the operation fails.
		5. Remotely controling services or listener sessions is not supported.

.Parameter Server
    Optional parameter to specify the name of the Remote Desktop server on which the process is running. If not specified, the value is defaulted to localhost. The parameter accepts either the NETBIOS name or the fully-qualified domain name of the Remote Desktop server.

.Parameter SessionID
    Session ID of the session. To get the session ID of a session, execute "Get-RDSession" function.

.Example 
    PS C:\> Start-RDRemoteControlSession -SessionID 2
    
    Facilitates remotely controling session with ID 2, on local server.
    
.Example 
    PS C:\> Start-RDRemoteControlSession -Server RDServer01 -SessionID 3 -Logoff
    
    Facilitates remotely controling session with ID 3, on the Remote Desktop server named RDServer01.

.Inputs
    Objects with "Server" and "SessionID" property specified.
    
.Outputs 
    None.

.Link 
    Connect-RDSession
    Disconnect-RDSession
    Get-RDSession
#>

param(    
    [Parameter(Mandatory=$FALSE, Position=0, ValueFromPipelineByPropertyName=$TRUE)]
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$TRUE, Position=1, ValueFromPipelineByPropertyName=$TRUE)]
    [int]
    $SessionID
)

    $session = Get-RDSHSession $Server | ? {$_.SessionID -eq $SessionID}

    if ($session -eq $NULL)
    {
        Write-Error ("Failed to connect to session: {0} on server: {1}. Please check if the server and session id are valid." -f $SessionID, $Server)
        return
    }
    
    if ($StateOperations['Start-RDRemoteControlSession'] -notcontains $session.State)
    {
        Write-Error ("Failed to connect to session {0} on server {1}. The target session is not active or disconnected." -f $SessionID, $Server)
        return
    }

	$localHostname = hostname
	$localMachineFQDN = "{0}.{1}" -f $localHostname, (Get-WmiObject Win32_ComputerSystem).Domain
	if (($localMachineFQDN, $localHostname, "localhost" -contains $Server) -and (IsCurrentSession $SessionID))
	{
		Write-Error "Failed to remote control the session: $SessionID on server: $Server. You cannot remote control the session you are connected to."
        return
	}

    $VK_MULTIPLY = [System.Byte]106
    $VK_LCONTROL = [System.UInt16]0x2

    $result = [RDSManager.PowerShell.RDSManager]::WTSStartRemoteControlSession($Server, $SessionID, $VK_MULTIPLY, $VK_LCONTROL)
    if (!$result)
    {
        Write-Error ("Failed to connect to session: {0} on server: {1}. Please check if you have the required permissions." -f $SessionID, $Server)
        return
    }
    Handle-Error ([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())
}

function Get-VMFQDN()
{
param(
    [string]
    $Server = "localhost"
)
    $tmpObj = Get-Service -ComputerName $Server -Name vmms -EA SilentlyContinue
    if ($tmpObj.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
    {
        Write-Error "Hyper-V service is not running on server: $Server"
        return
    }
    
    $VMs = @(Get-WmiObject -Computer $Server -Namespace root\virtualization -Query "Select * From Msvm_ComputerSystem Where Caption='Virtual Machine'" -EA Stop)
	$VMs | % {
        $Kvp = Get-WmiObject -Computer $Server -Namespace root\virtualization -Query "Associators of {$_} Where AssocClass=Msvm_SystemDevice ResultClass=Msvm_KvpExchangeComponent"  -EA Stop
        $xml = [xml]($Kvp.GuestIntrinsicExchangeItems | ? {$_ -match "FullyQualifiedDomainName"})
        $entry = $xml.Instance.Property | ?{$_.Name -eq "Data"}
        Write-Output $entry.Value
    }
}

function Get-RDVHSession
{
param(
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$FALSE)]
    [Switch]
    $Force,
    
    [Parameter(Mandatory=$FALSE, HelpMessage="Lists all sessions, including services, listener etc")]
    [Switch]
    $ListAll
)

    if ($Force)
    {
        Get-RDVHSessionEx $Server -ListAll:$ListAll
    }
    else
    {
        Get-RDSHSession $Server -ListAll:$ListAll
        Get-VMFQDN $Server | %{Get-RDSHSession $_ -ListAll:$ListAll} | %{$_.Host = $Server; Write-Output $_}
    }
    
}

function Get-RDVHSessionEx
{
param(    
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$FALSE, HelpMessage="Lists all sessions, including services, listener etc")]
    [Switch]
    $ListAll
)

	if (($Server -ne "localhost") -AND (!(Ping-Computer $Server))) {
        Write-Error ("'{0}' is not reachable." -f $Server)
	    return
    }
	
    $ServerPtr = [RDSManager.PowerShell.RDSManager]::WTSOpenServerEx($Server)
    
    if ($ServerPtr -eq [System.IntPtr]::Zero) {
        Write-Error ("Failed to connect to {0}" -f $Server)
	    return
    }
    
    $sessInfo = [System.IntPtr]::Zero 
    $count = 0
    $level = 1

    $result = [RDSManager.PowerShell.RDSManager]::WTSEnumerateSessionsEx($ServerPtr, [ref] $level, 2, [ref] $sessInfo, [ref] $count)
	
	if (($result -eq 0) -and ($count -eq 0))
	{
		Write-Warning "You might not have permissions to enumerate sessions on the Remote Desktop server: $Server"
		return
	}
	
    if(($result -ne 0) -AND ($sessInfo -ne [System.IntPtr]::Zero))
    {
        $structSize = [System.Runtime.InteropServices.Marshal]::sizeof([RDSManager.PowerShell.WTSSessionInfoEx])

        for ($ind = 0; $ind -lt $count; $ind++)
        {
            $sessionInfo = ([RDSManager.PowerShell.WTSSessionInfoEx]([System.Runtime.InteropServices.Marshal]::PtrToStructure([int]$sessInfo + $ind * $structSize, [RDSManager.PowerShell.WTSSessionInfoEx])))

            if ((!$ListAll) -AND ([string]::IsNullOrEmpty($sessionInfo.UserName)))
            {
                continue;
            }
            
            $sessObj = New-Object RDSManager.PowerShell.RDSession
                       
            $sessObj.Session = $sessionInfo.SessionName
            $sessObj.User = $sessionInfo.UserName
            $sessObj.SessionID = $sessionInfo.SessionId
            $sessObj.State = $sessionInfo.State
            if ([string]::IsNullOrEmpty($sessionInfo.HostName)) 
            {
                $sessObj.Server = $Server
            }
            else
            {
                $sessObj.Server = $sessionInfo.HostName 
                $sessObj.Host = $Server
            }            
            
            Write-Output $sessObj
        }

        [RDSManager.PowerShell.RDSManager]::WTSFreeMemory($sessInfo);
        $sessInfo = [System.IntPtr]::Zero
    }
    
    [RDSManager.PowerShell.RDSManager]::WTSCloseServer($ServerPtr)
    
}

function Get-RDVHProcess
{
param(    
    [string]
    $Server = "localhost",
    
    [Parameter(Mandatory=$FALSE,
        HelpMessage="Lists all process, from all sessions")]
    [Switch]
    $ListAll
)

    Get-RDSHProcess $Server -ListAll:$ListAll
    Get-VMFQDN $Server | %{Get-RDSHProcess $_ -ListAll:$ListAll} | %{$_.Host = $Server; Write-Output $_}
    
}

function Get-FarmMember
{
param(
    [String]
    $ConnectionBroker,

    [String]
    $FarmName
)

    $tmpObj = Get-Service -ComputerName $ConnectionBroker -Name tssdis -EA SilentlyContinue
    if ($tmpObj.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
    {
        Write-Error "Either you do not have the required privileges or the Connection Broker service is not running on RD Connection Broker server: $ConnectionBroker"
        return
    }
    
    if (-not (Get-WMIObject -ComputerName $ConnectionBroker -Class Win32_SessionDirectoryCluster -Filter "ClusterName='$FarmName'"))
    {
        Write-Error "Either you do not have the required privileges or the specified farm: $FarmName does not exist on the RD Connection Broker server: $ConnectionBroker."
        return
    }

    $RDSHServers = @(Get-WMIObject -ComputerName $ConnectionBroker -Class Win32_SessionDirectoryServer -Filter "ClusterName='$FarmName'" | Select-Object ServerName)
    return $RDSHServers
}

function Get-PoolMember
{
param(
    [String]
    $ConnectionBroker,

    [String]
    $PoolName
)

    $tmpObj = Get-Service -ComputerName $ConnectionBroker -Name tssdis -EA SilentlyContinue
    if ($tmpObj.Status -ne [System.ServiceProcess.ServiceControllerStatus]::Running)
    {
        Write-Error "Either you do not have the required privileges or the Connection Broker service is not running on the RD Connection Broker server: $ConnectionBroker"
        return
    }

    $pool = Get-WMIObject -ComputerName $ConnectionBroker -Namespace root\cimv2\terminalservices -Class Win32_TSRemoteDesktop -Filter "Name='$PoolName'" -Authentication PacketPrivacy
    if (-not $pool)
    {
        Write-Error "Specified VM pool $PoolName does not exist on the RD Connection Broker server: $ConnectionBroker"
        return
    }
    
    $hosts = @(Get-WmiObject -ComputerName $ConnectionBroker -Class Win32_SessionBrokerTarget -Filter "pluginname='VmResource' AND FarmName='$($pool.Alias)'" | Select-Object TargetName, Environment)
    return $hosts
}

function Get-RDSession
{
<# 
.Synopsis 
    Gets the sessions for the specified Remote Desktop resource. The Remote Desktop resource can be the RD Session Host server, RD Virtualization Host Server, RD Farm or VM Pool.
    
.Description 
    This function gets sessions for the specified Remote Desktop resource. The Remote Desktop resource can be the RD Session Host server, RD Virtualization Host Server, RD Farm or VM Pool.
    
    By default, it returns only user sessions. Use the parameter ListAll to list all sessions. Non-users sessions include:
    1. Services : The session that contains various system processes on the Remote Desktop server.
    2. Listener : The session that listens for and accepts new Remote Desktop Protocol (RDP) client connections, thereby creating new sessions on the Remote Desktop server.
    3. Console  : The session that you connect to if you log on to the physical console of the computer, instead of connecting remotely.

    Considerations : 
        1. To query sessions other than the logged on, execute the script with "query information" special access permission.
		
	Note : Using Force parameter will return objects with values set only for following properties : Server, Session, User, SessionID and State.

.Parameter Farm
    Name of the farm whose sessions are to be listed.

.Parameter Pool
    Name of the pool whose sessions are to be listed.

.Parameter ConnectionBroker
    Name of the RD Connection Broker server to get the details of pool or farm.

.Parameter RDVHost
    Name of the RD Virtualization Host Server to get the sessions from.

.Parameter RDSHost
    Name of the RD Session Host Server whose sessions are to be listed.

.Parameter Force
    Enumerates sessions from RD Virtualization host, as reported by the VMHostAgent service. By default, sessions are queried from VMs. This is helpful when the script is beinge executed with permissions that are not adquate to enumerate sessions on the Virtual Machiness.

.Parameter ListAll
    Lists all sessions.

.Example 
    PS C:\> Get-RDSession
    
    Gets the user sessions on local machine.
    
.Example 
    PS C:\> Get-RDSession -RDSHost RDServer01
    
    Gets the user sessions on server named RDServer01.
    
.Example 
    PS C:\> Get-RDSession -RDVHost RDVServer01
    
    Gets the user sessions on server named RDVServer01 and user sessions on all Virtual Machines hosted by the server.

.Example 
    PS C:\> Get-RDSession -RDVHost RDVServer01 -Force
    
    Gets the user sessions on Remote Desktop server named RDVServer01 and user sessions on all Virtual Machines hosted by the server, as reported by the VMHostAgent service. This is helpful when the script is beinge executed with permissions that are not adquate to enumerate sessions on the Virtual Machiness.

.Example 
    PS C:\> Get-RDSession -Farm RDFarm -ConnectionBroker CB01
    
    Gets the user sessions on all servers belonging to farm RDFarm, managed by the RD Connection Broker Server CB01.

.Example 
    PS C:\> Get-RDSession -Pool RDPool -ConnectionBroker CB01
    
    Gets the user sessions on all Virtual Machines belonging to VM Pool RDPool, managed by the RD Connection Broker Server CB01.

.Example 
    PS C:\> Get-RDSession -Pool RDPool -ConnectionBroker CB01 -ListAll
    
    Gets all sessions(user and non-user) on all Virtual Machines belonging to VM Pool RDPool, managed by the RD Connection Broker Server CB01.

.Inputs
    Remote Desktop resource name. Defaults to local machine if not specified.
    
.Outputs 
    Session Object(s).
    RDSManager.PowerShell.RDSession objects are returned. Returns the following information of a session:
    
    Property        Description 
    -----------------------------------------------------------------
    Server          The Remote Desktop server with which the session is associated.
    Session         The session running on the Remote Desktop server.
    User            The user account that is associated with the session.
    SessionID       The numeric ID that identifies the session to the Remote Desktop server.
    State           The status of a session. For more information, see Session States.
    ProtocolType    The type of remote desktop client using the session.
    Client          The name of the client computer using the session, if applicable.
    IdleTime        The number of minutes that have elapsed since the last keyboard or mouse input to a session.
    LogOnTime       The date and time at which the user logged on, if applicable.
    Host            The name of the remote desktop virtualization host server on which the VM is running.
    
.Link 
    Get-RDSessionStatus
    Send-RDMessage
    Connect-RDSession
    Disconnect-RDSession
    Start-RDRemoteControlSession
#>

[CmdletBinding(DefaultParametersetName="RDSHost")]
param(
    [Parameter(Mandatory=$TRUE, 
        ParameterSetName="RDSHFarm", 
        ValueFromPipeline=$TRUE, 
        HelpMessage="Remote Desktop farm name.")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $Farm,
    
    [Parameter(Mandatory=$TRUE, 
        ParameterSetName="RDVFarm", 
        ValueFromPipeline=$TRUE,
        HelpMessage="Remote Desktop Virtualization pool name.")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $Pool,
    
    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDSHFarm",
        HelpMessage="Connection Broker managing the farm or pool.")]
    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDVFarm",
        HelpMessage="Connection Broker managing the farm or pool.")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $ConnectionBroker = "localhost",

    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDSHost", 
        ValueFromPipeline=$TRUE,
        HelpMessage="Remote Desktop Session Host server or client name")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $RDSHost = "localhost",

    [Parameter(Mandatory=$TRUE, 
        ParameterSetName="RDVHost", 
        ValueFromPipeline=$TRUE,
        HelpMessage="Remote Desktop Virtualization Host server")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $RDVHost,
        
    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDVHost",
        HelpMessage="List sessions from information maintained by VM host agent")]
    [Switch]
    $Force,
    
    [Parameter(Mandatory=$FALSE,
        HelpMessage="List all sessions, including services, listener etc")]
    [Switch]
    $ListAll
)

    switch ($PsCmdlet.ParameterSetName)
    {
        "RDSHFarm"  {
                        Get-FarmMember $ConnectionBroker $Farm -ListAll:$ListAll | %{Get-RDSHSession $_.ServerName}
                        break
                    }
        "RDVFarm"   {
                        Get-PoolMember $ConnectionBroker $Pool | %{
                            foreach ($session in (Get-RDSHSession $_.TargetName -ListAll:$ListAll)) 
                            {
                                $session.Host = $_.Environment
                                Write-Output $session
                            } 
                        } 
                        break
                    }
        "RDSHost"   { Get-RDSHSession $RDSHost -ListAll:$ListAll; break}
        "RDVHost"   { Get-RDVHSession $RDVHost -Force:$Force -ListAll:$ListAll; break}
    }

}

function Get-RDProcess
{
<# 
.Synopsis 
    Gets the processes that are running on a Remote Desktop resource. The Remote Desktop resource can be a RD Session Host server, RD Virtualization Host Server, RD Farm or VM Pool.
    
.Description 
    This function gets processes running on a Remote Desktop resource. The Remote Desktop resource can be a RD Session Host server, RD Virtualization Host Server, RD Farm or VM Pool.
    The script by default, only lists the processes from user sessions. Please specify the ListAll switch to list processes from all sessions.

    Considerations : 
        1. To query processes of sessions other than the logged on session, the script is to be executed with "query information" or "full access" special permission.

.Parameter Farm
    Name of the farm whose sessions are to be listed.

.Parameter Pool
    Name of the pool whose sessions are to be listed.

.Parameter ConnectionBroker
    Name of the RD Connection Broker server to get the details of pool or farm.

.Parameter RDVHost
    Name of the RD Virtualization Host Server to get the sessions from.

.Parameter RDSHost
    Name of the RD Session Host Server whose sessions are to be listed.

.Parameter ListAll
    List all processes from all sessions.

.Example 
    PS C:\> Get-RDProcess
    
    Gets the processes running in user sessions on local machine.
    
.Example 
    PS C:\> Get-RDProcess -RDSHost RDServer01
    
    Gets the processes running in user sessions on Remote Desktop server named RDServer01.
    
.Example 
    PS C:\> Get-RDProcess -RDVHost RDVServer01
    
    Gets the processes running in user sessions on Remote Desktop server named RDVServer01 and on all virtual machines hosted by the server.

.Example 
    PS C:\> Get-RDProcess -Farm RDFarm -ConnectionBroker CB01
    
    Gets the processes running in user sessions on all Remote Desktop servers belonging to farm RDFarm, managed by the RD Connection Broker server CB01.

.Example 
    PS C:\> Get-RDProcess -Pool RDPool -ConnectionBroker CB01
    
    This command gets the processes running in user sessions on all VMs belonging to VM Pool RDPool, managed by the RD Connection Broker server CB01.

.Example 
    PS C:\> Get-RDProcess -Pool RDPool -ConnectionBroker CB01 -ListAll
    
    This command gets the processes running in all sessions on all virtual machines belonging to VM Pool RDPool,  managed by the RD Connection Broker server CB01.

.Inputs
    Remote Desktop resource name. Defaults to local machine if not specified.
    
.Outputs 
    Process Object(s).
    RDSManager.PowerShell.RDProcess objects are returned. These objects contain following information of a process:
    
    Property    Description 
    ----------------------------------------------
    Server      The Remote Desktop server with which the process is associated.
    Host        The name of the RD Virtualization Host server on which the VM is running.
    User        The user account that is associated with the process.
    Session     The session on the Remote Desktop server that is associated with the process.
    SessionID   The numeric ID that identifies the session on the Remote Desktop server.
    ProcessID   The numeric ID that identifies the process on the Remote Desktop server.
    Name        The name of the executable that created the process on the Remote Desktop server.
    
.Link 
    Get-RDSession
    Stop-RDProcess
#>

[CmdletBinding(DefaultParametersetName="RDSHost")]
param(
    [Parameter(Mandatory=$TRUE, 
        ParameterSetName="RDSHFarm", 
        ValueFromPipeline=$TRUE, 
        HelpMessage="Remote Desktop farm name.")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $Farm,
    
    [Parameter(Mandatory=$TRUE, 
        ParameterSetName="RDVFarm", 
        ValueFromPipeline=$TRUE,
        HelpMessage="Remote Desktop Virtualization pool name.")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $Pool,
    
    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDSHFarm",
        HelpMessage="RD Connection Broker server managing the farm or pool.")]
    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDVFarm",
        HelpMessage="RD Connection Broker server managing the farm or pool.")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $ConnectionBroker = "localhost",

    [Parameter(Mandatory=$FALSE, 
        ParameterSetName="RDSHost", 
        ValueFromPipeline=$TRUE,
        HelpMessage="RD Session Host server")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $RDSHost = "localhost",

    [Parameter(Mandatory=$TRUE, 
        ParameterSetName="RDVHost", 
        ValueFromPipeline=$TRUE,
        HelpMessage="RD Virtualization Host server")]
    [ValidateNotNullOrEmpty()]
    [System.String]
    $RDVHost,
    
    [Parameter(Mandatory=$FALSE,
        HelpMessage="Lists all process, from all sessions")]
    [Switch]
    $ListAll
)

    switch ($PsCmdlet.ParameterSetName)
    {
        "RDSHFarm"  {
                        Get-FarmMember $ConnectionBroker $Farm | %{Get-RDSHProcess $_.ServerName -ListAll:$ListAll}
                        break
                    }
        "RDVFarm"   {
                        Get-PoolMember $ConnectionBroker $Pool | %{
                            foreach ($process in (Get-RDSHProcess $_.TargetName -ListAll:$ListAll)) 
                            {
                                $process.Host = $_.Environment
                                Write-Output $process
                            } 
                        } 
                        break
                    }
        "RDSHost"   { Get-RDSHProcess $RDSHost -ListAll:$ListAll; break}
        "RDVHost"   { Get-RDVHProcess $RDVHost -ListAll:$ListAll; break}
    }

}

Export-ModuleMember -Function Get-RDSession, Get-RDProcess, Get-RDSessionStatus, Stop-RDProcess, Send-RDMessage, Connect-RDSession, Disconnect-RDSession, Start-RDRemoteControlSession
Platforms
Windows Server 2008 R2 Yes
Windows Server 2008 No
Windows Server 2003 No
Windows 7 No
Windows Vista No
Windows XP No
Windows 2000 No
For online peer support, join The Official Scripting Guys Forum! To provide feedback or report bugs in sample scripts, please start a new discussion on the Discussions tab for this script.
Disclaimer The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.
Be the first to create a discussion.