src/RdpToolkit.psm1
<# Part of module 'RdpToolkit' RdpToolkit is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. RdpToolkit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with RdpToolkit. If not, see <https://www.gnu.org/licenses/>. #> Function New-RdcFile { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Medium')] [Alias('New-RdpFile')] Param( [Parameter(Mandatory, Position=0)] [Alias('File', 'RdcFile', 'RdpFile')] [ValidateNotNullOrEmpty()] [ValidatePattern('\.rdp$')] [IO.FileInfo] $Path, [Parameter(Mandatory, Position=1, ValueFromPipelineByPropertyName)] [Alias('DnsHostName', 'HostName', 'IPAddress', 'Name', 'RdSessionHostName', 'ServerName')] [ValidateNotNullOrEmpty()] [String] $ComputerName, [Alias('User')] [String] $UserName, [Alias('Domain')] [String] $DomainName, [Uri] $GatewayServerName, [Switch] $UseLoggedOnUserCredentials, [ValidateSet('AudioCapture', 'Cameras', 'Drives', 'PnPDevices', 'Printers', 'SerialPorts', 'SmartCards', 'UsbDevices')] [String[]] $Redirect = @('AudioCapture', 'Cameras', 'Drives', 'PnPDevices', 'Printers', 'SerialPorts', 'UsbDevices'), [Alias('Drives')] [ValidatePattern('(DynamicDrives|[A-Za-z]:?)')] [String[]] $DrivesToRedirect, [Switch] $SingleScreen, [Switch] $Force, [Switch] $Sign, [Switch] $PassThru ) $RdpFileContents = [String[]]@( "full address:s:$ComputerName", 'singlemoninwindowedmode:i:1' ) Write-Debug -Message "Computer name = $ComputerName" Write-Debug -Message 'Single monitor in windowed mode = yes' If ($null -ne $UserName) { Write-Debug -Message "User name = $UserName" $RdpFileContents += "username:s:$UserName" } If ($null -ne $DomainName) { Write-Debug -Message "Domain name = $DomainName" $RdpFileContents += "domain:s:$DomainName" } If ($null -ne $GatewayServerName) { Write-Debug -Message "Gateway server name = $GatewayServerName" $RdpFileContents += "gatewayhostname:s:$GatewayServerName" Write-Debug -Message 'Gateway usage method = 1' $RdpFileContents += 'gatewayusagemethod:i:1' Write-Debug -Message 'Use same credentials for gateway and PC = yes' $RdpFileContents += 'promptcredentialonce:i:1' If ($UseLoggedOnUserCredentials) { Write-Debug -Message 'Gateway credentials = logged on user' $RdpFileContents += 'gatewaycredentialssource:i:2' } Else { Write-Debug -Message 'Gateway credentials = specify' } } Switch ($Redirect) { 'AudioCapture' { Write-Debug -Message 'Redirected devices += microphones' $RdpFileContents += 'audiocapturemode:i:1' } 'Cameras' { Write-Debug -Message 'Redirected devices += cameras (all)' $RdpFileContents += 'camerastoredirect:s:*' } 'Drives' { If ($null -eq $DrivesToRedirect) { Write-Debug -Message 'Redirected devices += drives (all)' $RdpFileContents += 'drivestoredirect:s:*' } Else { Write-Debug -Message "Redirected devices += drives $($DrivesToRedirect -Join ', ')" $drives = [String[]]@() $DrivesToRedirect | ForEach-Object { If ($_.Length -eq 1) { $drives += "$($_.ToUpper()):" } Else { $drives += $_ } } $RdpFileContents += "drivestoredirect:s:$($drives -Join ';')" } } 'PnPDevices' { Write-Debug -Message 'Redirected devices += devices (all)' $RdpFileContents += 'devicestoredirect:s:*' } 'Printers' { Write-Debug -Message 'Redirected devices += printers' $RdpFileContents += 'redirectprinters:i:1' } 'SerialPorts' { Write-Debug -Message 'Redirected devices += COM: ports' $RdpFileContents += 'redirectcomports:i:1' } 'SmartCards' { Write-Debug -Message 'Redirected devices += smart cards and Windows Hello for Business' $RdpFileContents += 'redirectsmartcards:i:1' } 'UsbDevices' { Write-Debug -Message 'Redirected devices += USB devices (all)' $RdpFileContents += 'usbdevicestoredirect:s:*' } default { Write-Warning -Message "The redirection item $_ was not recognized and will be ignored." } } If ($RdpFileContents -NotContains 'redirectsmartcards:i:1') { $RdpFileContents += 'redirectsmartcards:i:0' } If ($SingleScreen) { Write-Debug -Message 'Multi-monitor support = off' $RdpFileContents += 'use multimon:i:0' } Else { Write-Debug -Message 'Multi-monitor support = on' $RdpFileContents += 'use multimon:i:1' } Write-Debug -Message 'Saving the .rdp file' $SetContentParameters = @{ 'Confirm' = $false 'Encoding' = 'UTF8' 'Path' = $Path 'WhatIf' = $false } $FileExists = Test-Path -Path $Path -PathType Leaf If ($FileExists) { If ($Force -or $PSCmdlet.ShouldProcess($Path, 'Overwrite')) { $RdpFileContents | Sort-Object | Set-Content @SetContentParameters -Force } } Else { $RdpFileContents | Sort-Object | Set-Content @SetContentParameters } If ($Sign) { Write-Debug -Message 'Applying a digital signature to the .rdp file' Try { # Only pass through -WhatIf if the file already existed and the user # specified -WhatIf. In all other cases, this is either a new file, # or the user did not specify -WhatIf. Add-RdcFileSignature -Files $Path -Confirm:$false -WhatIf:($WhatIfPreference -and $FileExists) } Catch { Write-Warning -Message 'The .rdp file could not be signed due to an error.' } } If ($PassThru) { Return (Get-File -Path $Path) } } Function Remove-RdcFileSignature { [OutputType([void])] Param( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [Alias('File', 'Files', 'RdcFile', 'RdcFiles')] [ValidateNotNullOrEmpty()] [ValidatePattern("\.rdp$")] [IO.FileInfo[]] $Path, [Switch] $KeepBlankLines ) Process { # The nested loops allow this cmdlet to operate on wildcards. ForEach ($Argument in $Path) { ForEach ($File in (Get-Item $Argument)) { $content = Get-Content -Path $File If (-Not $KeepBlankLines) { $content = ($content | Where-Object {$_.trim() -ne ""}) } $content = ($content | Select-String -NotMatch -Pattern '^sign(ature|scope):*') Set-Content -Path $File -Value $content -Force } } } } Function Add-RdcFileSignature { [CmdletBinding(SupportsShouldProcess, ConfirmImpact='Low')] [OutputType([void])] Param( [Parameter(Mandatory, Position=0, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [ValidatePattern("\.rdp$")] [IO.FileInfo[]] $Files, [Alias('Certificate', 'Thumbprint')] [AllowNull()] [String] $CertificateThumbprint = $null, [IO.FileInfo] $PathToRDPSign = (Join-Path -Path $env:WinDir -ChildPath 'System32' -AdditionalChildPath 'rdpsign.exe') ) Begin { If (-Not $IsWindows) { Throw [PlatformNotSupportedException]::new('Signing .rdp files can only be done under Microsoft Windows.') } # If the user specified a certificate's thumbprint, we'll use that one. # Otherwise, call Get-CodeSigningCertificates to pick one automatically. If ("" -eq $CertificateThumbprint) { $CertificateThumbprint = Get-CodeSigningCertificates } Write-Verbose -Message "Signing with the certificate $CertificateThumbprint." } Process { # The nested loops allow this cmdlet to operate on wildcards. ForEach ($Argument in $Files) { ForEach ($File in (Get-Item $Argument)) { $File = Get-Item $File Write-Verbose "Signing the file $($File.Name)" If ($PSCmdlet.ShouldProcess($File, 'Add digital signature')) { $result = Invoke-RdpSign -Thumbprint $CertificateThumbprint -File $File -PathToRDPSign $PathToRDPSign If ($result -eq 0) { Write-Information "Signed $($File.Name)" } Else { Write-Warning "Did not sign the file $($File.Name)." } } } } } } Function Get-CodeSigningCertificates { [OutputType([String])] [CmdletBinding()] Param() $Thumbprint = $null $Certificates = Get-ChildItem (Join-Path -Path 'Cert:' -ChildPath 'CurrentUser' -AdditionalChildPath 'My') -CodeSigning -ErrorAction Stop ` | Where-Object {$_.NotBefore -le (Get-Date) -and $_.NotAfter -ge (Get-Date)} If ($Certificates.Count -gt 0) { $Thumbprint = $Certificates | Select-Object -First 1 -ExpandProperty Thumbprint Write-Verbose "Using the certificate with thumbprint $Thumbprint." Write-Debug ($Certificates | Select-Object -First 1 -ExpandProperty Thumbprint) } Else { Throw [PowerShell.Commands.CertificateNotFoundException]::new('A valid code signing certificate could not be found.') } Return $Thumbprint } Function Invoke-RdpSign { [OutputType([bool])] Param( [Parameter(Mandatory, Position=0)] [ValidateNotNullOrEmpty()] [ValidatePattern("\.rdp$")] [IO.FileInfo] $File, [ValidateNotNullOrEmpty()] [String] $Thumbprint, [IO.FileInfo] $PathToRDPSign = (Join-Path -Path $env:WinDir -ChildPath 'System32' -AdditionalChildPath 'rdpsign.exe') ) If (Test-Path -Path $PathToRDPSign -PathType Leaf) { $output = Start-Process -FilePath $PathToRDPSign -ArgumentList @("/sha256 $Thumbprint", "`"$($File.FullName)`"") -PassThru -Wait -WindowStyle Hidden | Out-Null Return ($output.ExitCode -eq 0) } Else { Throw [IO.FileNotFoundException]::new("rdpsign was not found at $PathToRdpSign") } } |