diff --git a/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psd1 b/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psd1 index 95fd391..90dc421 100644 Binary files a/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psd1 and b/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psd1 differ diff --git a/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psm1 b/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psm1 index d53614f..37b357c 100644 --- a/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psm1 +++ b/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psm1 @@ -52,18 +52,14 @@ Add-Type -Assembly System.Windows.Forms Add-Type -Assembly System.Drawing Add-Type -MemberDefinition '[DllImport("User32.dll")] public static extern bool SetProcessDPIAware();[DllImport("User32.dll")] public static extern int LoadCursorA(int hInstance, int lpCursorName);[DllImport("User32.dll")] public static extern bool GetCursorInfo(IntPtr pci);' -Name User32 -Namespace W; -$global:PowerRemoteDesktopVersion = "1.0.5.beta.6" +$global:PowerRemoteDesktopVersion = "1.0.6" $global:HostSyncHash = [HashTable]::Synchronized(@{ host = $host ClipboardText = (Get-Clipboard -Raw) + RunningSession = $false }) -enum TransportMode { - Raw = 1 - Base64 = 2 -} - enum ClipboardMode { Disabled = 1 Receive = 2 @@ -600,14 +596,12 @@ class ClientIO { [System.IO.StreamWriter] $Writer = $null [System.IO.StreamReader] $Reader = $null [System.Net.Security.SslStream] $SSLStream = $null - [TransportMode] $TransportMode ClientIO( [System.Net.Sockets.TcpClient] $Client, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, - [bool] $TLSv1_3, - [TransportMode] $TransportMode + [bool] $TLSv1_3 ) { <# .SYNOPSIS @@ -621,9 +615,6 @@ class ClientIO { .PARAMETER TLSv1_3 Define whether or not SSL/TLS v1.3 must be used. - - .PARAMETER TransportMode - Define transport method for streams (Base64 or Raw) #> if ((-not $Client) -or (-not $Certificate)) @@ -632,7 +623,6 @@ class ClientIO { } $this.Client = $Client - $this.TransportMode = $TransportMode Write-Verbose "Create new SSL Stream..." @@ -783,7 +773,6 @@ class ClientIO { $sessionInformation = Get-LocalMachineInformation - $sessionInformation | Add-Member -MemberType NoteProperty -Name "TransportMode" -Value $this.TransportMode $sessionInformation | Add-Member -MemberType NoteProperty -Name "SessionId" -Value $session.Id $sessionInformation | Add-Member -MemberType NoteProperty -Name "Version" -Value $global:PowerRemoteDesktopVersion $sessionInformation | Add-Member -MemberType NoteProperty -Name "ViewOnly" -Value $ViewOnly @@ -854,7 +843,6 @@ class ServerIO { [string] $ListenAddress = "127.0.0.1" [int] $ListenPort = 2801 [bool] $TLSv1_3 = $false - [TransportMode] $TransportMode [string] $Password [bool] $ViewOnly = $false @@ -888,9 +876,6 @@ class ServerIO { .PARAMETER TLSv1_3 Define if TLS v1.3 must be used. - .PARAMETER TransportMode - Define stream transport method. - .PARAMETER ViewOnly Define if mouse / keyboard is authorized. #> @@ -898,7 +883,6 @@ class ServerIO { [string] $ListenAddress, [int] $ListenPort, [string] $Password, - [TransportMode] $TransportMode, [System.Security.Cryptography.X509Certificates.X509Certificate2] $Certificate, [bool] $TLSv1_3, [bool] $ViewOnly @@ -913,7 +897,6 @@ class ServerIO { $this.ListenPort = $ListenPort $this.TLSv1_3 = $TLSv1_3 $this.Password = $Password - $this.TransportMode = $TransportMode $this.ViewOnly = $ViewOnly if (-not $Certificate) @@ -997,8 +980,7 @@ class ServerIO { $client = [ClientIO]::New( $socket, $this.Certificate, - $this.TLSv1_3, - $this.TransportMode + $this.TLSv1_3 ) try { @@ -1059,11 +1041,6 @@ class ServerIO { $global:DesktopStreamScriptBlock = { - enum TransportMode { - Raw = 1 - Base64 = 2 - } - function Get-DesktopImage { <# .SYNOPSIS @@ -1093,11 +1070,16 @@ $global:DesktopStreamScriptBlock = { $Screen.Bounds.Location.Y ) - $bitmap = New-Object System.Drawing.Bitmap($size.Width, $size.Height) + $bitmap = New-Object System.Drawing.Bitmap( + $size.Width, + $size.Height, + [System.Drawing.Imaging.PixelFormat]::Format24bppRgb + ) + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) $graphics.CopyFromScreen($location, [System.Drawing.Point]::Empty, $size) - + return $bitmap } catch @@ -1130,13 +1112,13 @@ $global:DesktopStreamScriptBlock = { $encoderParameters = New-Object System.Drawing.Imaging.EncoderParameters(1) $encoderParameters.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, $imageQuality) - $packetSize = 4096 - - while ($true) - { + $packetSize = 9216 # 9KiB + + while ($global:HostSyncHash.RunningSession) + { try { - $desktopImage = Get-DesktopImage -Screen $Param.Screen + $desktopImage = Get-DesktopImage -Screen $Param.Screen $imageStream = New-Object System.IO.MemoryStream @@ -1163,50 +1145,20 @@ $global:DesktopStreamScriptBlock = { { $imageStream.position = 0 try - { - switch ([TransportMode] $Param.Client.TransportMode) - { - ([TransportMode]::Raw) - { - $Param.Client.SSLStream.Write([BitConverter]::GetBytes([int32] $imageStream.Length) , 0, 4) # SizeOf(Int32) - - $totalBytesSent = 0 - - $buffer = New-Object -TypeName byte[] -ArgumentList $packetSize - do - { - $bufferSize = ($imageStream.Length - $totalBytesSent) - if ($bufferSize -gt $packetSize) - { - $bufferSize = $packetSize - } - else - { - # Save some memory operations for creating objects. - # Usually, bellow code is call when last chunk is being sent. - $buffer = New-Object -TypeName byte[] -ArgumentList $bufferSize - } - - # (OPTIMIZATION IDEA): Try with BinaryStream to save the need of "byte[]"" buffer. - $null = $imageStream.Read($buffer, 0, $buffer.Length) - - $Param.Client.SSLStream.Write($buffer, 0, $buffer.Length) - - $totalBytesSent += $bufferSize - } until ($totalBytesSent -eq $imageStream.Length) - - break - } - - ([TransportMode]::Base64) - { - $Param.Client.Writer.WriteLine( - [System.Convert]::ToBase64String($imageStream.ToArray()) - ) + { + $Param.Client.SSLStream.Write([BitConverter]::GetBytes([int32] $imageStream.Length) , 0, 4) # SizeOf(Int32) + + $binaryReader = New-Object System.IO.BinaryReader($imageStream) + do + { + $bufferSize = ($imageStream.Length - $imageStream.Position) + if ($bufferSize -gt $packetSize) + { + $bufferSize = $packetSize + } - break - } - } + $Param.Client.SSLStream.Write($binaryReader.ReadBytes($bufferSize), 0, $bufferSize) + } until ($imageStream.Position -eq $imageStream.Length) } catch { break } @@ -1323,7 +1275,7 @@ $global:IngressEventScriptBlock = { $keyboardSim = [KeyboardSim]::New() - while ($true) + while ($global:HostSyncHash.RunningSession) { try { @@ -1626,7 +1578,7 @@ $global:EgressEventScriptBlock = { $stopWatch = [System.Diagnostics.Stopwatch]::StartNew() - while ($true) + while ($global:HostSyncHash.RunningSession) { # Events that occurs every seconds needs to be placed bellow. # If no event has occured during this second we send a Keep-Alive signal to @@ -1792,10 +1744,6 @@ function Invoke-RemoteDesktopServer .PARAMETER EncodedCertificate A valid X509 Certificate (With Private Key) encoded as a Base64 String. - .PARAMETER TransportMode - Tell server how to send desktop image to remote viewer. Best method is Raw Bytes but I decided to keep - the Base64 transport method as an alternative. - .PARAMETER TLSv1_3 Define whether or not TLS v1.3 must be used for communication with Viewer. @@ -1828,7 +1776,6 @@ function Invoke-RemoteDesktopServer # Or [string] $EncodedCertificate = "", # 2 - [TransportMode] $TransportMode = [TransportMode]::Raw, [switch] $TLSv1_3, [switch] $DisableVerbosity, [int] $ImageQuality = 100, @@ -1911,13 +1858,11 @@ function Invoke-RemoteDesktopServer } } - Write-Verbose $TransportMode # Create new server and listen $server = [ServerIO]::New( $ListenAddress, $ListenPort, $Password, - $TransportMode, $Certificate, $TLSv1_3, $ViewOnly @@ -1929,6 +1874,8 @@ function Invoke-RemoteDesktopServer { try { + $global:HostSyncHash.RunningSession = $false + Write-Verbose "Server waiting for new incomming session..." # Establish a new Remote Desktop Session. @@ -1939,7 +1886,9 @@ function Invoke-RemoteDesktopServer # Otherwise a Timeout Exception will be raised. # Actually, if someone else decide to connect in the mean time it will interrupt the whole session, # Remote Viewer will then need to establish a new session from scratch. - $clientEvents = $server.PullClient(10 * 1000); + $clientEvents = $server.PullClient(10 * 1000); + + $global:HostSyncHash.RunningSession = $true # Grab desired screen to capture $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object -FilterScript { $_.DeviceName -eq $server.Session.Screen } @@ -1983,16 +1932,19 @@ function Invoke-RemoteDesktopServer # Waiting for Runspaces to finish their jobs. while ($true) { - $completed = $true + $completed = $true # Probe each existing runspaces foreach ($runspace in $runspaces) { if (-not $runspace.AsyncResult.IsCompleted) { - $completed = $false - - break + $completed = $false + } + elseif ($global:HostSyncHash.RunningSession) + { + # Notifying other runspaces that a session integrity was lost + $global:HostSyncHash.RunningSession = $false } } diff --git a/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psd1 b/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psd1 index 6649e7b..a61f505 100644 Binary files a/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psd1 and b/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psd1 differ diff --git a/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psm1 b/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psm1 index fa051eb..200cb95 100644 --- a/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psm1 +++ b/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psm1 @@ -51,7 +51,7 @@ Add-Type -Assembly System.Windows.Forms Add-Type -MemberDefinition '[DllImport("User32.dll")] public static extern bool SetProcessDPIAware();' -Name User32 -Namespace W; -$global:PowerRemoteDesktopVersion = "1.0.5.beta.6" +$global:PowerRemoteDesktopVersion = "1.0.6" $global:HostSyncHash = [HashTable]::Synchronized(@{ host = $host @@ -72,11 +72,6 @@ enum ClipboardMode { Both = 4 } -enum TransportMode { - Raw = 1 - Base64 = 2 -} - function Write-Banner { <# @@ -366,6 +361,7 @@ class ClientIO { [System.Net.Security.SslStream] $SSLStream = $null [System.IO.StreamWriter] $Writer = $null [System.IO.StreamReader] $Reader = $null + [System.IO.BinaryReader] $BinaryReader = $null ClientIO( <# @@ -524,6 +520,8 @@ class ClientIO { $this.Reader = New-Object System.IO.StreamReader($this.SSLStream) + $this.BinaryReader = New-Object System.IO.BinaryReader($this.SSLStream) + Write-Verbose "Encrypted tunnel opened and ready for use." } @@ -616,7 +614,6 @@ class ClientIO { (-not ($sessionInformation.PSobject.Properties.name -contains "Username")) -or (-not ($sessionInformation.PSobject.Properties.name -contains "WindowsVersion")) -or (-not ($sessionInformation.PSobject.Properties.name -contains "SessionId")) -or - (-not ($sessionInformation.PSobject.Properties.name -contains "TransportMode")) -or (-not ($sessionInformation.PSobject.Properties.name -contains "Version")) -or (-not ($sessionInformation.PSobject.Properties.name -contains "Screens")) -or (-not ($sessionInformation.PSobject.Properties.name -contains "ViewOnly")) @@ -731,6 +728,11 @@ class ClientIO { $this.Reader.Close() } + if ($this.BinaryReader) + { + $this.BinaryReader.Close() + } + if ($this.SSLStream) { $this.SSLStream.Close() @@ -879,11 +881,6 @@ class ViewerSession $global:VirtualDesktopUpdaterScriptBlock = { - enum TransportMode { - Raw = 1 - Base64 = 2 - } - function Invoke-SmoothResize { <# @@ -899,6 +896,9 @@ $global:VirtualDesktopUpdaterScriptBlock = { .PARAMETER NewHeight Define the height of new bitmap version. + .PARAMETER HighQuality + Activate high quality image resizing with a serious performance cost. + .EXAMPLE Invoke-SmoothResize -OriginalImage $myImage -NewWidth 1920 -NewHeight 1024 #> @@ -910,7 +910,9 @@ $global:VirtualDesktopUpdaterScriptBlock = { [int] $NewWidth, [Parameter(Mandatory=$true)] - [int] $NewHeight + [int] $NewHeight, + + [bool] $HighQuality = $false ) try { @@ -918,10 +920,13 @@ $global:VirtualDesktopUpdaterScriptBlock = { $resizedImage = [System.Drawing.Graphics]::FromImage($bitmap) - $resizedImage.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality - $resizedImage.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality - $resizedImage.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic - $resizedImage.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality + if ($HighQuality) + { + $resizedImage.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality + $resizedImage.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality + $resizedImage.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $resizedImage.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality + } $resizedImage.DrawImage($OriginalImage, 0, 0, $bitmap.Width, $bitmap.Height) @@ -943,66 +948,39 @@ $global:VirtualDesktopUpdaterScriptBlock = { try { - $packetSize = 4096 - + $packetSize = 9216 # 9KiB + while ($true) - { + { $stream = New-Object System.IO.MemoryStream try - { - switch ([TransportMode] $Param.TransportMode) - { - ([TransportMode]::Raw) - { - $buffer = New-Object -TypeName byte[] -ArgumentList 4 # SizeOf(Int32) - - $Param.Client.SSLStream.Read($buffer, 0, $buffer.Length) - - [int32] $totalBufferSize = [BitConverter]::ToInt32($buffer, 0) - - $stream.SetLength($totalBufferSize) - - $stream.position = 0 + { + $buffer = New-Object -TypeName byte[] -ArgumentList 4 # SizeOf(Int32) - $totalBytesRead = 0 + $Param.Client.SSLStream.Read($buffer, 0, $buffer.Length) - $buffer = New-Object -TypeName Byte[] -ArgumentList $packetSize - do - { - $bufferSize = $totalBufferSize - $totalBytesRead - if ($bufferSize -gt $packetSize) - { - $bufferSize = $packetSize - } - else - { - # Save some memory operations for creating objects. - # Usually, bellow code is call when last chunk is being sent. - $buffer = New-Object -TypeName byte[] -ArgumentList $bufferSize - } - - $Param.Client.SSLStream.Read($buffer, 0, $bufferSize) - - $null = $stream.Write($buffer, 0, $buffer.Length) + [int32] $totalBufferSize = [BitConverter]::ToInt32($buffer, 0) - $totalBytesRead += $bufferSize - } until ($totalBytesRead -eq $totalBufferSize) - } + $stream.SetLength($totalBufferSize) - ([TransportMode]::Base64) - { - [byte[]] $buffer = [System.Convert]::FromBase64String(($Param.Client.Reader.ReadLine())) + $stream.position = 0 - $stream.Write($buffer, 0, $buffer.Length) - } - } + $buffer = New-Object -TypeName Byte[] -ArgumentList $packetSize + do + { + $bufferSize = $stream.Length - $stream.Position + if ($bufferSize -gt $packetSize) + { + $bufferSize = $packetSize + } + + $null = $stream.Write($Param.Client.BinaryReader.ReadBytes($bufferSize), 0, $bufferSize) + } until ($stream.Position -eq $stream.Length) $stream.Position = 0 if ($Param.RequireResize) - { - #$image = [System.Drawing.Image]::FromStream($stream) - + { $bitmap = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $stream $Param.VirtualDesktopSyncHash.VirtualDesktop.Picture.Image = Invoke-SmoothResize -OriginalImage $bitmap -NewWidth $Param.VirtualDesktopWidth -NewHeight $Param.VirtualDesktopHeight @@ -1019,9 +997,9 @@ $global:VirtualDesktopUpdaterScriptBlock = { finally { $stream.Close() - } - + } } + } finally { @@ -1453,7 +1431,8 @@ function Invoke-RemoteDesktopViewer }) $virtualDesktopSyncHash.VirtualDesktop.Form.Text = [string]::Format( - "Power Remote Desktop: {0}/{1} - {2}", + "Power Remote Desktop v{0}: {1}/{2} - {3}", + $global:PowerRemoteDesktopVersion, $session.SessionInformation.Username, $session.SessionInformation.MachineName, $session.SessionInformation.WindowsVersion @@ -1650,7 +1629,12 @@ function Invoke-RemoteDesktopViewer $aEvent = (New-MouseEvent -X $X -Y $Y -Button $Button -Type $Type) - $outputEventSyncHash.Writer.WriteLine(($aEvent | ConvertTo-Json -Compress)) + try + { + $outputEventSyncHash.Writer.WriteLine(($aEvent | ConvertTo-Json -Compress)) + } + catch + {} } function Send-VirtualKeyboard @@ -1673,7 +1657,12 @@ function Invoke-RemoteDesktopViewer $aEvent = (New-KeyboardEvent -Keys $KeyChain) - $outputEventSyncHash.Writer.WriteLine(($aEvent | ConvertTo-Json -Compress)) + try + { + $outputEventSyncHash.Writer.WriteLine(($aEvent | ConvertTo-Json -Compress)) + } + catch + {} } $virtualDesktopSyncHash.VirtualDesktop.Form.Add_KeyPress( @@ -1775,7 +1764,11 @@ function Invoke-RemoteDesktopViewer Delta = $_.Delta } - $outputEventSyncHash.Writer.WriteLine(($aEvent | ConvertTo-Json -Compress)) + try + { + $outputEventSyncHash.Writer.WriteLine(($aEvent | ConvertTo-Json -Compress)) + } + catch {} } ) } @@ -1787,8 +1780,7 @@ function Invoke-RemoteDesktopViewer VirtualDesktopSyncHash = $virtualDesktopSyncHash VirtualDesktopWidth = $virtualDesktopWidth VirtualDesktopHeight = $virtualDesktopHeight - RequireResize = $requireResize - TransportMode = [TransportMode] $session.SessionInformation.TransportMode + RequireResize = $requireResize } $newRunspace = (New-RunSpace -ScriptBlock $global:VirtualDesktopUpdaterScriptBlock -Param $param) diff --git a/README.md b/README.md index 7c14bf3..83d187e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Tested on: * Windows 10 - PowerShell Version: 5.1.19041.1320 * Windows 11 - PowerShell Version: 5.1.22000.282 +Current version: **1.0.6 Stable** + ## Features https://user-images.githubusercontent.com/2520298/150001915-0982fb1c-a729-4b21-b22c-a58e201bfe27.mp4 @@ -31,11 +33,29 @@ https://user-images.githubusercontent.com/2520298/150001915-0982fb1c-a729-4b21-b * Multi-Screen (Monitor) support. If remote computer have more than one desktop screen, you can choose which desktop screen to capture. * View Only mode for demonstration. You can disable remote control abilities and just show your screen to remote peer. -## What is still beta +## Development Roadmap + +### Version 1.x (Now marked as stable) + +Version `1.x` development is now over, only bug fix and improvements will be pushed to dedicated branch. + +### Version 2.x (In progress) + +Version `2.x` development is in progress with one new big feature and one huge improvement. + +#### Feature -Version 1.0.5 Beta 6 is the last beta before final version. +Motion detection for desktop capture. Instead of capturing the whole screen, only updated screen areas will be sent to viewer thus improving considerably the streaming speed and reducing CPU usage. -No more features will be added in 1.x version, just optimization and bug fix. +#### Improvement + +A huge part of the protocol will be updated. + +The whole handshake progress will be cleaner and both Server and Viewer will respectively acknowledge their desired configuration. + +For example, instead of setting the image quality in server option (which makes no sense), it will be available from viewer option and sent to server. + +Same thing for image resizing, instead of resizing desktop image viewer-side, image will be resized server-side accordingly with viewer constraints. ## Installation @@ -192,9 +212,6 @@ Supported options: * `Password` (**Mandatory**): Define password used during authentication process. * `CertificateFile` (Default: **None**): A valid X509 Certificate (With Private Key) File. If set, this parameter is prioritize. * `EncodedCertificate` (Default: **None**): A valid X509 Certificate (With Private Key) encoded as a Base64 String. -* `TransportMode`(Default: `Raw`): Define which method to use to transfer streams. - * `Raw`: Transfer streams as raw bytes (recommended) - * `Base64`: Transfer streams as base64 encoded string * `TLSv1_3` (Default: None): If this switch is present, server will use TLS v1.3 instead of TLS v1.2. Use this option only if both viewer and server support TLS v1.3. * `DisableVerbosity` (Default: None): If this switch is present, verbosity will be hidden from console. * `ImageQuality` (Default: `100`): JPEG Compression level from 0 to 100. 0 = Lowest quality, 100 = Highest quality. @@ -267,7 +284,7 @@ Detail Fingerprint ### 11 January 2022 (1.0.1 Beta 2) -* Desktop images are now transported in raw bytes instead of base64 string thus slightly improving performances. Base64 Transport Method is still available through an option but disabled by default. +* Desktop images are now transported in raw bytes instead of base64 string thus slightly improving performances. * Protocol has drastically changed. It is smoother to read and less prone to errors. * TLS v1.3 option added (Might not be supported by some systems). * Several code optimization, refactoring and fixes. @@ -308,6 +325,11 @@ Detail Fingerprint * Clipboard synchronization Viewer <-> Server added. * Server support a new option to only show desktop (Mouse moves, clicks, wheel and keyboard control is disabled in this mode). +### 21 January 2022 (1.0.6) + +* TransportMode option removed. +* Desktop streaming performance / speed increased. + ### List of ideas and TODO * 🟢 Support Password Protected external Certificates.