KMaks.RDShadow.psm1

<#
 .Synopsis
  Gets current setting for shadowing Remote Desktop sessions.
 
 .Description
  The Get-RDShadowConfiguration cmdlet queries target system using PSRemoting. It returns an object
  containing of a computer name and it's current session shadowing setting.
 
 .Parameter ComputerName
  Specifies the remote computer.
 
 .Example
   # Get Remote Desktop session shadowing configuration on local system.
   C:\PS>Get-RDShadowConfiguration
 
   ComputerName Access
   ------------ ------
   LOCALHOST Disable
 
 .Example
   # Get Remote Desktop session shadowing configuration on a remote system.
   C:\PS>Get-RDShadowConfiguration -ComputerName Workstation01
    
   ComputerName Access
   ------------ ------
   Workstation1 Disable
#>

Function Get-RDShadowConfiguration {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]$ComputerName = $Env:COMPUTERNAME
    )

    $AccessDictionary = @{
        "Disable"                       = $Null
        "FullControllRequireConsent"    = 1
        "FullControlNoConsent"          = 2
        "ViewOnlyRequireConsent"        = 3
        "ViewOnlyNoConsent"             = 4
    }

    $ScriptBlock = {
        $OldValue = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\'

        Return [PsCustomObject]@{
            OldValue = $OldValue.Shadow
        }
    }

    try {
        Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock -ErrorAction Stop |
        ForEach-Object {
            [PSCustomObject]@{
                ComputerName = $ComputerName
                Access       = $AccessDictionary.GetEnumerator() | Where-Object Value -eq $PSItem.OldValue | Select-Object -ExpandProperty Name
            }
        }
    }
    catch {
        Write-Error "Unable to execute commands on $ComputerName."
    }
}

<#
 .Synopsis
  Modifies current setting for shadowing Remote Desktop sessions.
 
 .Description
  The Set-RDShadowConfiguration cmdlet modifies the setting responsible for shadowing Remote Desktop sessions.
   
 .Parameter ComputerName
  Specifies the remote computer.
 
 .Parameter Access
  Specifies the desired access level.
 
 .Example
   # Disable Remote Desktop session shadowing on local system.
   C:\PS>Set-RDShadowConfiguration -Access Disable
 
   ComputerName Access
   ------------ ------
   LOCALHOST Disable
 
 .Example
   # Allow Remote Desktop session shadowing without consent.
   C:\PS>Set-RDShadowConfiguration -ComputerName Workstation01 -Access FullControlNoConsent
    
   ComputerName Access
   ------------ ------
   Workstation1 FullControlNoConsent
#>

Function Set-RDShadowConfiguration {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]$ComputerName = $Env:COMPUTERNAME,
        
        [Parameter(Mandatory)]
        [ValidateSet("Disable","FullControllRequireConsent","FullControlNoConsent","ViewOnlyRequireConsent","ViewOnlyNoConsent")]
        [string]$Access
    )

    $AccessDictionary = @{
        "Disable"                       = $Null
        "FullControllRequireConsent"    = 1
        "FullControlNoConsent"          = 2
        "ViewOnlyRequireConsent"        = 3
        "ViewOnlyNoConsent"             = 4
    }

    $ScriptBlock = {
        param($AccessValue)
        $OldValue = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\'

        If ($AccessValue) {
            New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\' -Name 'Shadow' -Value $AccessValue -PropertyType DWORD -Force | Out-Null
        }
        Else {
            Remove-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\' -Name 'Shadow' -ErrorAction SilentlyContinue | Out-Null
        }

        $NewValue = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\'

        Return [PsCustomObject]@{
            OldValue = $OldValue.Shadow
            NewValue = $NewValue.Shadow
        }
    }

    

    try {
        Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock -ArgumentList $AccessDictionary[$Access] |
        ForEach-Object {
            [PSCustomObject]@{
                ComputerName    = $ComputerName
                Access          = $AccessDictionary.GetEnumerator() | Where-Object Value -eq $PSItem.NewValue | Select-Object -ExpandProperty Name
                OldAccess       = $AccessDictionary.GetEnumerator() | Where-Object Value -eq $PSItem.OldValue | Select-Object -ExpandProperty Name
            }
        }
    }
    catch {
        Write-Error "Unable to execute commands on $ComputerName."
    }
}

<#
 .Synopsis
  Lists all user sessions on the target system.
 
 .Description
  The Get-RDShadowSession cmdlet gets a list of all user sessions on the target machine.
   
 .Parameter ComputerName
  Specifies the remote computer.
 
 .Example
   # Get Remote Desktop sessions on local system.
   C:\PS>Set-RDShadowSession
 
   ComputerName : LOOCALHOST
   UserName : localuser
   SessionName : console
   ID : 1
   State : Active
   IdleTime : none
   LogonTime : 4/22/2021 7:19:00 PM
 
 .Example
   # Get Remote Desktop sessions on a remote system.
   C:\PS>Get-RDShadowSession -ComputerName Workstation01
    
   ComputerName : RDServer1
   UserName : User1
   SessionName :
   ID : 2
   State : Disc
   IdleTime : 20:13:00
   LogonTime : 8/4/2021 10:06:00 AM
 
   ComputerName : RDServer1
   UserName : User2
   SessionName :
   ID : 4
   State : Active
   IdleTime : 20:26:00
   LogonTime : 9/4/2021 9:07:00 AM
#>

Function Get-RDShadowSession {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string]$ComputerName = $Env:COMPUTERNAME
    )
    $ScriptBlock = {
        $Return = & query.exe user |
        Select-Object -Skip 1 |
        ForEach-Object {
            If ($PSItem -Match '^.(?<UserName>.{22})(?<SessionName>.{18})(?<ID>.{5})(?<State>.{8})(?<IdleTime>.{11})(?<LogonTime>.*)$') {
                [PsCustomObject]@{
                    UserName    = [String]$Matches.UserName.Trim()
                    SessionName = [String]$Matches.SessionName.Trim()
                    ID          = [int]$Matches.ID.Trim()
                    State       = [String]$Matches.State.Trim()
                    IdleTime    = If ($Matches.IdleTime.Trim() -eq "none") {"none"} Else { [TimeSpan]$Matches.IdleTime.Trim().Replace('+','.') }
                    LogonTime   = $Matches.LogonTime.Trim()
                }
            }
        }
        Return $Return
    }

    try {
        Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock |
        ForEach-Object {
            [PSCustomObject]@{
                ComputerName = $PSItem.PSComputerName
                UserName     = $PSItem.UserName
                SessionName  = $PSItem.SessionName
                ID           = $PSItem.ID
                State        = $PSItem.State
                IdleTime     = $PSItem.IdleTime
                LogonTime    = $PSItem.LogonTime
            }
        }
    }
    catch {
        Write-Error "Unable to execute commands on $ComputerName."
    }
}

<#
 .Synopsis
  Shadow target session.
 
 .Description
  The Invoke-RDShadow cmdlet launches Remote Desktop Client with specific parameters to shadow
  desired user session.
   
 .Parameter ComputerName
  Specifies the remote computer.
 
 .Parameter SessionId
  Specifies the user session ID to shadow on the target system.
 
 .Parameter NoConsentPrompt
  Switch hiding a connection consent prompt form the target session owner.
 
 .Parameter Control
  Switch required to control the target session. By default the session is view only.
 
 .Example
   # Get Remote Desktop sessions on local system.
   C:\PS>Set-RDShadowSessions
 
   ComputerName : LOOCALHOST
   UserName : localuser
   SessionName : console
   ID : 1
   State : Active
   IdleTime : none
   LogonTime : 4/22/2021 7:19:00 PM
 
 .Example
   # Get Remote Desktop sessions on a remote system.
   C:\PS>Get-RDShadowSessions -ComputerName Workstation01
    
   ComputerName : RDServer1
   UserName : User1
   SessionName :
   ID : 2
   State : Disc
   IdleTime : 20:13:00
   LogonTime : 8/4/2021 10:06:00 AM
 
   ComputerName : RDServer1
   UserName : User2
   SessionName :
   ID : 4
   State : Active
   IdleTime : 20:26:00
   LogonTime : 9/4/2021 9:07:00 AM
#>

Function Invoke-RDShadow {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$ComputerName,

        [Parameter(Mandatory)]
        [int]$SessionId,

        [Parameter()]
        [switch]$ConsentPrompt,

        [Parameter()]
        [switch]$Admin,

        [Parameter()]
        [switch]$Control
    )
    [System.Collections.ArrayList]$ProcessParameters = @()

    If ($ComputerName)          { [void]$ProcessParameters.Add("/v:$ComputerName") }
                                  [void]$ProcessParameters.Add("/shadow:$SessionId")
    If ($Admin)                 { [void]$ProcessParameters.Add("/Admin") }
    If (-not $ConsentPrompt)    { [void]$ProcessParameters.Add("/NoConsentPrompt") }
    If ($Control)               { [void]$ProcessParameters.Add("/Control") }

    Start-Process -FilePath "mstsc.exe" -ArgumentList $ProcessParameters
}