diff --git a/Assets/PowerShell_Core_6.0_icon.png b/Assets/PowerShell_Core_6.0_icon.png new file mode 100644 index 0000000..54d1e96 Binary files /dev/null and b/Assets/PowerShell_Core_6.0_icon.png differ diff --git a/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psd1 b/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psd1 index 6568472..704fdfe 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 d2a6c1a..317a4df 100644 --- a/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psm1 +++ b/PowerRemoteDesktop_Server/PowerRemoteDesktop_Server.psm1 @@ -19,16 +19,6 @@ Version 2.0, January 2004 http://www.apache.org/licenses/ - .Why - - Prove PowerShell is as "PowerFul" as compiled language. - - Improve my PowerShell skills. - - Because Remote Desktop Powershell Scripts doesn't exists so far. - - .Important - This PowerShell Application is not yet marked as Stable / Final. It is not recommended to use - it in a production environment at this time. - Wait for final 1.0 version. - .Disclaimer We are doing our best to prepare the content of this app. However, PHROZEN SASU and / or Jean-Pierre LESUEUR cannot warranty the expressions and suggestions of the contents, @@ -49,10 +39,51 @@ -------------------------------------------------------------------------------#> 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 = "2.0.0" +Add-Type @" + using System; + using System.Security; + using System.Runtime.InteropServices; + + public static class User32 + { + [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); + + [DllImport("user32.dll")] + public static extern void mouse_event(int flags, int dx, int dy, int cButtons, int info); + + [DllImport("user32.dll")] + public static extern int GetSystemMetrics(int nIndex); + } + + public static class Kernel32 + { + [DllImport("Kernel32.dll")] + public static extern uint SetThreadExecutionState(uint esFlags); + + [DllImport("kernel32.dll", SetLastError = true, EntryPoint="RtlMoveMemory"), SuppressUnmanagedCodeSecurity] + public static extern void CopyMemory( + IntPtr dest, + IntPtr src, + uint count + ); + } + + public static class MSVCRT + { + [DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity] + public static extern int memcmp(IntPtr p1, IntPtr p2, UInt64 count); + } +"@ + +$global:PowerRemoteDesktopVersion = "3.0.0" $global:HostSyncHash = [HashTable]::Synchronized(@{ host = $host @@ -81,6 +112,32 @@ enum WorkerKind { Events = 2 } +enum LogKind { + Information + Warning + Success + Error +} + +enum BlockSize { + Size32 = 32 + Size64 = 64 + Size96 = 96 + Size128 = 128 + Size256 = 256 + Size512 = 512 +} + +enum PacketSize { + Size1024 = 1024 + Size2048 = 2048 + Size4096 = 4096 + Size8192 = 8192 + Size9216 = 9216 + Size12288 = 12288 + Size16384 = 16384 +} + function Write-Banner { <# @@ -108,6 +165,132 @@ function Write-Banner Write-Host "" } +function Write-Log +{ + <# + .SYNOPSIS + Output a log message to terminal with associated "icon". + + .PARAMETER Message + Type: String + Default: None + + Description: The message to write to terminal. + + .PARAMETER LogKind + Type: LogKind Enum + Default: Information + + Description: Define the logger "icon" kind. + #> + param( + [Parameter(Mandatory=$True)] + [string] $Message, + + [LogKind] $LogKind = [LogKind]::Information + ) + + switch ($LogKind) + { + ([LogKind]::Warning) + { + $icon = "!!" + $color = [System.ConsoleColor]::Yellow + + break + } + + ([LogKind]::Success) + { + $icon = "OK" + $color = [System.ConsoleColor]::Green + + break + } + + ([LogKind]::Error) + { + $icon = "KO" + $color = [System.ConsoleColor]::Red + + break + } + + default + { + $color = [System.ConsoleColor]::Cyan + $icon = "i" + } + } + + Write-Host "[ " -NoNewLine + Write-Host $icon -ForegroundColor $color -NoNewLine + Write-Host " ] $Message" +} + +function Write-OperationSuccessState +{ + param( + [Parameter(Mandatory=$True)] + $Result, + + [Parameter(Mandatory=$True)] + $Message + ) + + if ($Result) + { + $kind = [LogKind]::Success + } + else + { + $kind = [LogKind]::Error + } + + Write-Log -Message $Message -LogKind $kind +} + +function Invoke-PreventSleepMode +{ + <# + .SYNOPSIS + Prevent computer to enter sleep mode while server is running. + + .DESCRIPTION + Function returns thread execution state old flags value. You can use this old flags + to restore thread execution to its original state. + #> + + $ES_AWAYMODE_REQUIRED = [uint32]"0x00000040" + $ES_CONTINUOUS = [uint32]"0x80000000" + $ES_DISPLAY_REQUIRED = [uint32]"0x00000002" + $ES_SYSTEM_REQUIRED = [uint32]"0x00000001" + $ES_USER_PRESENT = [uint32]"0x00000004" + + return [Kernel32]::SetThreadExecutionState( + $ES_CONTINUOUS -bor + $ES_SYSTEM_REQUIRED -bor + $ES_AWAYMODE_REQUIRED + ) +} + +function Update-ThreadExecutionState +{ + <# + .SYNOPSIS + Update current thread execution state flags. + + .PARAMETER Flags + Execution state flags. + #> + param( + [Parameter(Mandatory=$True)] + $Flags + ) + + return [Kernel32]::SetThreadExecutionState($Flags) -ne 0 +} + function Get-PlainTextPassword { <# @@ -604,156 +787,101 @@ function Resolve-AuthenticationChallenge return $solution } -$global:DesktopStreamScriptBlock = { +$global:DesktopStreamScriptBlock = { + $BlockSize = [int]$Param.SafeHash.ViewerConfiguration.BlockSize + $HighQualityResize = $Param.SafeHash.ViewerConfiguration.HighQualityResize + $packetSize = [int]$Param.SafeHash.ViewerConfiguration.PacketSize - function Invoke-SmoothResize + $WidthConstrainsts = 0 + $HeightConstrainsts = 0 + + $ResizeDesktop = $Param.SafeHash.ViewerConfiguration.ResizeDesktop() + if ($ResizeDesktop) { - <# - .SYNOPSIS - Output a resized version of input bitmap. The resize quality is quite fair. - - .PARAMETER OriginalImage - Input bitmap to resize. - - .PARAMETER NewWidth - Define the width of new bitmap version. + $WidthConstrainsts = $Param.SafeHash.ViewerConfiguration.ExpectDesktopWidth + $HeightConstrainsts = $Param.SafeHash.ViewerConfiguration.ExpectDesktopHeight + } - .PARAMETER NewHeight - Define the height of new bitmap version. + $bitmapPixelFormat = [System.Drawing.Imaging.PixelFormat]::Format32bppPArgb - .PARAMETER HighQuality - Activate high quality image resizing with a serious performance cost. + $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object -FilterScript { + $_.DeviceName -eq $Param.SafeHash.ViewerConfiguration.ScreenName + } + if (-not $screen) + { + $screen = [System.Windows.Forms.Screen]::PrimaryScreen + } - .EXAMPLE - Invoke-SmoothResize -OriginalImage $myImage -NewWidth 1920 -NewHeight 1024 - #> - param ( - [Parameter(Mandatory=$true)] - [System.Drawing.Bitmap] $OriginalImage, + $virtualScreenBounds = [System.Drawing.Rectangle]::New( + 0, + 0, + $screen.Bounds.Width, + $screen.Bounds.Height + ) - [Parameter(Mandatory=$true)] - [int] $NewWidth, + if ($ResizeDesktop) + { + $virtualScreenBounds.Width = $WidthConstrainsts + $virtualScreenBounds.Height = $HeightConstrainsts + } - [Parameter(Mandatory=$true)] - [int] $NewHeight, + $SpaceGrid = $null + $horzBlockCount = [math]::ceiling($virtualScreenBounds.Width / $BlockSize) + $vertBlockCount = [math]::ceiling($virtualScreenBounds.Height / $BlockSize) - [bool] $HighQuality = $false - ) - try - { - $bitmap = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $NewWidth, $NewHeight + $encoderParameters = New-Object System.Drawing.Imaging.EncoderParameters(1) + $encoderParameters.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter( + [System.Drawing.Imaging.Encoder]::Quality, + $Param.SafeHash.ViewerConfiguration.ImageCompressionQuality + ) - $resizedImage = [System.Drawing.Graphics]::FromImage($bitmap) + $encoder = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.MimeType -eq 'image/jpeg' }; - 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) + $SpaceGrid = New-Object IntPtr[][] $vertBlockCount, $horzBlockCount - return $bitmap - } - finally - { - if ($OriginalImage) - { - $OriginalImage.Dispose() - } + $firstIteration = $true - if ($resizedImage) - { - $resizedImage.Dispose() - } - } - } - - function Get-DesktopImage { - <# - .SYNOPSIS - Return a snapshot of primary screen desktop. - - .PARAMETER Screen - Define target screen to capture (if multiple monitor exists). - Default is primary screen - #> - param ( - [System.Windows.Forms.Screen] $Screen = $null - ) - try - { - if (-not $Screen) - { - $Screen = [System.Windows.Forms.Screen]::PrimaryScreen - } + $bmpBlock = New-Object System.Drawing.Bitmap( + $BlockSize, + $BlockSize, + $bitmapPixelFormat + ) - $size = New-Object System.Drawing.Size( - $Screen.Bounds.Size.Width, - $Screen.Bounds.Size.Height - ) + $desktopImage = New-Object System.Drawing.Bitmap( + $virtualScreenBounds.Width, + $virtualScreenBounds.Height, + $bitmapPixelFormat + ) - $location = New-Object System.Drawing.Point( - $Screen.Bounds.Location.X, - $Screen.Bounds.Location.Y - ) + if ($ResizeDesktop) + { + $fullSizeDesktop = New-Object System.Drawing.Bitmap( + $screen.Bounds.Width, + $screen.Bounds.Height, + $bitmapPixelFormat + ) - $bitmap = New-Object System.Drawing.Bitmap( - $size.Width, - $size.Height, - [System.Drawing.Imaging.PixelFormat]::Format24bppRgb - ) + $fullSizeDesktopGraphics = [System.Drawing.Graphics]::FromImage($fullSizeDesktop) - $graphics = [System.Drawing.Graphics]::FromImage($bitmap) - - $graphics.CopyFromScreen($location, [System.Drawing.Point]::Empty, $size) - - return $bitmap - } - catch - { - if ($bitmap) - { - $bitmap.Dispose() - } - } - finally + if ($HighQualityResize -and $ResizeDesktop) { - if ($graphics) - { - $graphics.Dispose() - } + $fullSizeDesktopGraphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality + $fullSizeDesktopGraphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality + $fullSizeDesktopGraphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic + $fullSizeDesktopGraphics.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality } - } - - # Initialize locally desktop streaming configuration - # "SafeHash" is a synchronized object, we must avoid accessing this object regularly to improve performance. - $imageQuality = $Param.SafeHash.ViewerConfiguration.ImageCompressionQuality + } - $screen = [System.Windows.Forms.Screen]::AllScreens | Where-Object -FilterScript { - $_.DeviceName -eq $Param.SafeHash.ViewerConfiguration.ScreenName - } + # SizeOf(DWORD) * 3 (SizeOf(Desktop) + SizeOf(Left) + SizeOf(Top)) + $struct = New-Object -TypeName byte[] -ArgumentList (([Runtime.InteropServices.Marshal]::SizeOf([System.Type][UInt32])) * 3) - $requireResize = $Param.SafeHash.ViewerConfiguration.ResizeDesktop() - $resizeWidth = $Param.SafeHash.ViewerConfiguration.ExpectDesktopWidth - $resizeHeight = $Param.SafeHash.ViewerConfiguration.ExpectDesktopHeight + $graphics = [System.Drawing.Graphics]::FromImage($desktopImage) try - { - [System.IO.MemoryStream] $oldImageStream = New-Object System.IO.MemoryStream - - $jpegEncoder = [System.Drawing.Imaging.ImageCodecInfo]::GetImageEncoders() | Where-Object { $_.MimeType -eq 'image/jpeg' }; - - $encoderParameters = New-Object System.Drawing.Imaging.EncoderParameters(1) - $encoderParameters.Param[0] = New-Object System.Drawing.Imaging.EncoderParameter([System.Drawing.Imaging.Encoder]::Quality, $imageQuality) - - $packetSize = 9216 # 9KiB - - $stopWatch = [System.Diagnostics.Stopwatch]::StartNew() + { + $stopWatch = [System.Diagnostics.Stopwatch]::StartNew() while ($true) - { + { # Using a stopwatch instead of replacing main loop "while ($true)" by "while($this.SafeHash.SessionActive)" # sounds strange but this is done to avoid locking our SafeHash to regularly and loosing some # performance. If you think this is useless, just use while($this.SafeHash.SessionActive) in main @@ -769,96 +897,243 @@ $global:DesktopStreamScriptBlock = { $stopWatch.Restart() } - - try - { - $desktopImage = Get-DesktopImage -Screen $screen - if ($requireResize) - { - $desktopImage = Invoke-SmoothResize -OriginalImage $desktopImage -NewWidth $resizeWidth -NewHeight $resizeHeight + + # /// + + if ($firstIteration) + { + $updatedRect = $virtualScreenBounds + } + else + { + $updatedRect = New-Object -TypeName System.Drawing.Rectangle -ArgumentList 0, 0, 0, 0 + } + + if ($ResizeDesktop) + { + try + { + $fullSizeDesktopGraphics.CopyFromScreen( + $screen.Bounds.Location, + [System.Drawing.Point]::Empty, + [System.Drawing.Size]::New( + $fullSizeDesktop.Width, + $fullSizeDesktop.Height + ) + ) + } + catch + { + continue + } + + $graphics.DrawImage( + $fullSizeDesktop, + 0, + 0, + $virtualScreenBounds.Width, + $virtualScreenBounds.Height + ) + } + else + { + try + { + $graphics.CopyFromScreen( + $screen.Bounds.Location, + [System.Drawing.Point]::Empty, + [System.Drawing.Size]::New( + $virtualScreenBounds.Width, + $virtualScreenBounds.Height + ) + ) + } + catch + { + continue } + } - $imageStream = New-Object System.IO.MemoryStream + for ($y = 0; $y -lt $vertBlockCount; $y++) + { + for ($x = 0; $x -lt $horzBlockCount; $x++) + { + $rect = New-Object -TypeName System.Drawing.Rectangle - $desktopImage.Save($imageStream, $jpegEncoder, $encoderParameters) + $rect.X = ($x * $BlockSize) + $rect.Y = ($y * $BlockSize) + $rect.Width = $BlockSize + $rect.Height = $BlockSize - $sendUpdate = $true + $rect = [System.Drawing.Rectangle]::Intersect($rect, $virtualScreenBounds) + + $bmpBlock = $desktopImage.Clone($rect, $bitmapPixelFormat) + + $bmpBlockData = $bmpBlock.LockBits( + [System.Drawing.Rectangle]::New(0, 0, $bmpBlock.Width, $bmpBlock.Height), + [System.Drawing.Imaging.ImageLockMode]::ReadOnly, + $bitmapPixelFormat + ) + try + { + $blockMemSize = ($bmpBlockData.Stride * $bmpBlock.Height) + if ($firstIteration) + { + # Big bang occurs, tangent univers is getting created, where is Donnie? + $SpaceGrid[$y][$x] = [Runtime.InteropServices.Marshal]::AllocHGlobal($blockMemSize) + + [Kernel32]::CopyMemory($SpaceGrid[$y][$x], $bmpBlockData.Scan0, $blockMemSize) + } + else + { + if ([MSVCRT]::memcmp($bmpBlockData.Scan0, $SpaceGrid[$y][$x], $blockMemSize) -ne 0) + { + [Kernel32]::CopyMemory($SpaceGrid[$y][$x], $bmpBlockData.Scan0, $blockMemSize) - # Check both stream size. - $sendUpdate = ($oldImageStream.Length -ne $imageStream.Length) + if ($updatedRect.IsEmpty) + { + $updatedRect.X = $x * $BlockSize + $updatedRect.Width = $BlockSize - # If sizes are equal, compare both Fingerprint to confirm finding. - if (-not $sendUpdate) - { - $imageStream.position = 0 - $oldImageStream.position = 0 + $updatedRect.Y = $y * $BlockSize + $updatedRect.Height = $BlockSize + } + else + { + if ($x * $BlockSize -lt $updatedRect.X) + { + $updatedRect.X = $x * $BlockSize + } + + if (($x+1) * $BlockSize -gt $updatedRect.Right) + { + $updatedRect.Width = (($x + 1) * $BlockSize) - $updatedRect.X + } + + if ($y * $BlockSize -lt $updatedRect.Y) + { + $updatedRect.Y = $y * $BlockSize + } + + if (($y+1) * $BlockSize -gt $updatedRect.Bottom) + { + $updatedRect.Height = (($y + 1) * $BlockSize) - $updatedRect.Y + } + } + } + } + } + finally + { + if ($bmpBlockData) + { + $bmpBlock.UnlockBits($bmpBlockData) + } + } + } + } + + if (-not $updatedRect.IsEmpty -and $desktopImage) + { + try + { + $updatedRect = [System.Drawing.Rectangle]::Intersect($updatedRect, $virtualScreenBounds) - $md5_1 = (Get-FileHash -InputStream $imageStream -Algorithm MD5).Hash - $md5_2 = (Get-FileHash -InputStream $oldImageStream -Algorithm MD5).Hash + $updatedDesktop = $desktopImage.Clone( + $updatedRect, + $bitmapPixelFormat + ) + + $desktopStream = New-Object System.IO.MemoryStream - $sendUpdate = ($md5_1 -ne $md5_2) - } + $updatedDesktop.Save($desktopStream, $encoder, $encoderParameters) - if ($sendUpdate) - { - $imageStream.position = 0 + $desktopStream.Position = 0 try - { - $Param.Client.SSLStream.Write([BitConverter]::GetBytes([int32] $imageStream.Length) , 0, 4) # SizeOf(Int32) - - $binaryReader = New-Object System.IO.BinaryReader($imageStream) + { + # One call please + [System.Runtime.InteropServices.Marshal]::WriteInt32($struct, 0x0, $desktopStream.Length) + [System.Runtime.InteropServices.Marshal]::WriteInt32($struct, 0x4, $updatedRect.Left) + [System.Runtime.InteropServices.Marshal]::WriteInt32($struct, 0x8, $updatedRect.Top) + + $Param.Client.SSLStream.Write($struct , 0, $struct.Length) + + $binaryReader = New-Object System.IO.BinaryReader($desktopStream) do { - $bufferSize = ($imageStream.Length - $imageStream.Position) + $bufferSize = ($desktopStream.Length - $desktopStream.Position) if ($bufferSize -gt $packetSize) { $bufferSize = $packetSize } $Param.Client.SSLStream.Write($binaryReader.ReadBytes($bufferSize), 0, $bufferSize) - } until ($imageStream.Position -eq $imageStream.Length) + } until ($desktopStream.Position -eq $desktopStream.Length) } catch - { break } - - # Update Old Image Stream for Comparison - $imageStream.position = 0 - - $oldImageStream.SetLength(0) - - $imageStream.CopyTo($oldImageStream) + { + break + } } - else {} - } - catch - { } - finally - { - if ($desktopImage) + finally { - $desktopImage.Dispose() - } + if ($desktopStream) + { + $desktopStream.Dispose() + } - if ($imageStream) - { - $imageStream.Close() + if ($updatedDesktop) + { + $updatedDesktop.Dispose() + } } } + + if ($firstIteration) + { + $firstIteration = $false + } } } finally - { - if ($oldImageStream) + { + if ($graphics) { - $oldImageStream.Close() + $graphics.Dispose() } - } -} -$global:IngressEventScriptBlock = { + if ($desktopImage) + { + $desktopImage.Dispose() + } + + if ($fullSizeDesktopGraphics) + { + $fullSizeDesktopGraphics.Dispose() + } - Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern void mouse_event(int flags, int dx, int dy, int cButtons, int info);[DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);' -Name U32 -Namespace W; + if ($fullSizeDesktop) + { + $fullSizeDesktop.Dispose() + } + + if ($bmpBlock) + { + $bmpBlock.Dispose() + } + # Tangent univers big crunch + for ($y = 0; $y -lt $vertBlockCount; $y++) + { + for ($x = 0; $x -lt $horzBlockCount; $x++) + { + [Runtime.InteropServices.Marshal]::FreeHGlobal($SpaceGrid[$y][$x]) + } + } + } +} + +$global:IngressEventScriptBlock = { enum MouseFlags { MOUSEEVENTF_ABSOLUTE = 0x8000 MOUSEEVENTF_LEFTDOWN = 0x0002 @@ -893,45 +1168,30 @@ $global:IngressEventScriptBlock = { Receive = 2 Send = 3 Both = 4 - } - - class KeyboardSim { - <# - .SYNOPSIS - Class to simulate Keyboard Events using a WScript.Shell - Instance. - #> - [System.__ComObject] $WShell = $null - - KeyboardSim () - <# - .SYNOPSIS - Class constructor - #> - { - $this.WShell = New-Object -ComObject WScript.Shell - } - - [void] SendInput([string] $String) - { - <# - .SYNOPSIS - Simulate Keyboard Strokes. It can contain a single char or a complex string. + } + + $SM_CXSCREEN = 0 + $SM_CYSCREEN = 1 - .PARAMETER String - Char or String to be simulated as pressed. + function Set-MouseCursorPos + { + param( + [int] $X = 0, + [int] $Y = 0 + ) - .EXAMPLE - .SendInput("Hello, World") - .SendInput("J") - #> + $x_screen = [User32]::GetSystemMetrics($SM_CXSCREEN) + $y_screen = [User32]::GetSystemMetrics($SM_CYSCREEN) - # Simulate - $this.WShell.SendKeys($String) - } + [User32]::mouse_event( + [int][MouseFlags]::MOUSEEVENTF_MOVE -bor [int][MouseFlags]::MOUSEEVENTF_ABSOLUTE, + (65535 * $X) / $x_screen, + (65535 * $Y) / $y_screen, + 0, + 0 + ); + } - - $keyboardSim = [KeyboardSim]::New() while ($Param.SafeHash.SessionActive) { @@ -966,7 +1226,8 @@ $global:IngressEventScriptBlock = { if (-not ($aEvent.PSobject.Properties.name -match "Keys")) { break } - $keyboardSim.SendInput($aEvent.Keys) + [System.Windows.Forms.SendKeys]::SendWait($aEvent.Keys) + break } @@ -984,7 +1245,8 @@ $global:IngressEventScriptBlock = { # Mouse Down/Up {($_ -eq ([MouseState]::Down)) -or ($_ -eq ([MouseState]::Up))} { - [W.U32]::SetCursorPos($aEvent.X, $aEvent.Y) + #[User32]::SetCursorPos($aEvent.X, $aEvent.Y) + Set-MouseCursorPos -X $aEvent.X -Y $aEvent.Y $down = ($_ -eq ([MouseState]::Down)) @@ -1022,7 +1284,7 @@ $global:IngressEventScriptBlock = { } } } - [W.U32]::mouse_event($mouseCode, 0, 0, 0, 0); + [User32]::mouse_event($mouseCode, 0, 0, 0, 0); break } @@ -1033,7 +1295,8 @@ $global:IngressEventScriptBlock = { if ($Param.ViewOnly) { continue } - [W.U32]::SetCursorPos($aEvent.X, $aEvent.Y) + #[User32]::SetCursorPos($aEvent.X, $aEvent.Y) + Set-MouseCursorPos -X $aEvent.X -Y $aEvent.Y break } @@ -1047,7 +1310,7 @@ $global:IngressEventScriptBlock = { if ($Param.ViewOnly) { continue } - [W.U32]::mouse_event([int][MouseFlags]::MOUSEEVENTF_WHEEL, 0, 0, $aEvent.Delta, 0); + [User32]::mouse_event([int][MouseFlags]::MOUSEEVENTF_WHEEL, 0, 0, $aEvent.Delta, 0); break } @@ -1119,7 +1382,7 @@ $global:EgressEventScriptBlock = { $cursors = @{} foreach ($cursorType in [CursorType].GetEnumValues()) { - $result = [W.User32]::LoadCursorA(0, [int]$cursorType) + $result = [User32]::LoadCursorA(0, [int]$cursorType) if ($result -gt 0) { @@ -1165,7 +1428,7 @@ $global:EgressEventScriptBlock = { [System.Runtime.InteropServices.Marshal]::WriteInt32($cursorInfo, 0x0, $structSize) - if ([W.User32]::GetCursorInfo($cursorInfo)) + if ([User32]::GetCursorInfo($cursorInfo)) { $hCursor = [System.Runtime.InteropServices.Marshal]::ReadInt64($cursorInfo, 0x8) @@ -1590,15 +1853,24 @@ class ClientIO { } } +class TcpListenerEx : System.Net.Sockets.TcpListener +{ + TcpListenerEx([string] $ListenAddress, [int] $ListenPort) : base($ListenAddress, $ListenPort) + { } + + [bool] Active() + { + return $this.Active + } +} + class ServerIO { - [System.Net.Sockets.TcpListener] $Server = $null + [TcpListenerEx] $Server = $null [System.IO.StreamWriter] $Writer = $null [System.IO.StreamReader] $Reader = $null ServerIO() - { - - } + { } [void] Listen( [string] $ListenAddress, @@ -1610,7 +1882,7 @@ class ServerIO { $this.Close() } - $this.Server = New-Object System.Net.Sockets.TcpListener( + $this.Server = New-Object TcpListenerEx( $ListenAddress, $ListenPort ) @@ -1692,9 +1964,10 @@ class ServerIO { { if ($this.Server) { - return $this.Server.Active + return $this.Server.Active() } - else { + else + { return $false } } @@ -1706,10 +1979,10 @@ class ServerIO { Stop listening and release TcpListener object. #> if ($this.Server) - { + { if ($this.Server.Active) - { - $this.Server.Stop() + { + $this.Server.Stop() } $this.Server = $null @@ -1724,6 +1997,9 @@ class ViewerConfiguration { [int] $ExpectDesktopWidth = 0 [int] $ExpectDesktopHeight = 0 [int] $ImageCompressionQuality = 100 + [PacketSize] $PacketSize = [PacketSize]::Size9216 + [BlockSize] $BlockSize = [BlockSize]::Size64 + [bool] $HighQualityResize = $false [bool] ResizeDesktop() { @@ -2139,6 +2415,21 @@ class SessionManager { $session.SafeHash.ViewerConfiguration.ImageCompressionQuality = $viewerExpectation.ImageCompressionQuality } + if ($viewerExpectation.PSobject.Properties.name -contains "PacketSize") + { + $session.SafeHash.ViewerConfiguration.PacketSize = [PacketSize]$viewerExpectation.PacketSize + } + + if ($viewerExpectation.PSobject.Properties.name -contains "BlockSize") + { + $session.SafeHash.ViewerConfiguration.BlockSize = [BlockSize]$viewerExpectation.BlockSize + } + + if ($viewerExpectation.PSobject.Properties.name -contains "HighQualityResize") + { + $session.SafeHash.ViewerConfiguration.HighQualityResize = $viewerExpectation.HighQualityResize + } + Write-Verbose "New session successfully created." $this.Sessions.Add($session) @@ -2205,10 +2496,10 @@ class SessionManager { } [void] ListenForWorkers() - { + { while ($true) - { - if (-not $this.Server -or $this.Server.Active()) + { + if (-not $this.Server -or -not $this.Server.Active()) { throw "A server must be active to listen for new workers." } @@ -2284,36 +2575,36 @@ class SessionManager { } } - [void] CloseServer() + [void] CloseSessions() { <# .SYNOPSIS - Close all existing sessions and dispose server. + Close all existing sessions #> - $this.CloseSessions() - - if ($this.Server) + foreach ($session in $this.Sessions) { - $this.Server.Close() - - $this.Server = $null + $session.Close() } + + $this.Sessions.Clear() } - [void] CloseSessions() + [void] CloseServer() { <# .SYNOPSIS - Close all existing sessions + Close all existing sessions and dispose server. #> - foreach ($session in $this.Sessions) - { - $session.Close() - } + $this.CloseSessions() - $this.Sessions.Clear() + if ($this.Server) + { + $this.Server.Close() + + $this.Server = $null + } } } @@ -2383,6 +2674,12 @@ function Invoke-RemoteDesktopServer .PARAMETER ViewOnly (Default: None) If this switch is present, viewer wont be able to take the control of mouse (moves, clicks, wheel) and keyboard. Useful for view session only. + + .PARAMETER PreventComputerToSleep + Type: Switch + Default: None + Description: + If present, this option will prevent computer to enter in sleep mode while server is active and waiting for new connections. #> param ( @@ -2401,7 +2698,8 @@ function Invoke-RemoteDesktopServer [switch] $UseTLSv1_3, [switch] $DisableVerbosity, [ClipboardMode] $Clipboard = [ClipboardMode]::Both, - [switch] $ViewOnly + [switch] $ViewOnly, + [switch] $PreventComputerToSleep ) $oldErrorActionPreference = $ErrorActionPreference @@ -2421,7 +2719,7 @@ function Invoke-RemoteDesktopServer Write-Banner - $null = [W.User32]::SetProcessDPIAware() + $null = [User32]::SetProcessDPIAware() if (-not (Test-Administrator) -and -not $CertificateFile -and -not $EncodedCertificate) { @@ -2477,6 +2775,14 @@ function Invoke-RemoteDesktopServer try { + $oldExecutionStateFlags = $null + if ($PreventComputerToSleep) + { + $oldExecutionStateFlags = Invoke-PreventSleepMode + + Write-OperationSuccessState -Message "Preventing computer to entering sleep mode." -Result ($oldExecutionStateFlags -gt 0) + } + Write-Host "Loading remote desktop server components..." $sessionManager = [SessionManager]::New( @@ -2505,6 +2811,11 @@ function Invoke-RemoteDesktopServer $sessionManager = $null } + if ($oldExecutionStateFlags) + { + Write-OperationSuccessState -Message "Stop preventing computer to enter sleep mode. Restore thread execution state." -Result (Update-ThreadExecutionState -Flags $oldExecutionStateFlags) + } + Write-Host "Remote desktop was closed." } } diff --git a/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psd1 b/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psd1 index bb43a2a..3d5402e 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 d67f647..0aac480 100644 --- a/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psm1 +++ b/PowerRemoteDesktop_Viewer/PowerRemoteDesktop_Viewer.psm1 @@ -19,16 +19,6 @@ Version 2.0, January 2004 http://www.apache.org/licenses/ - .Why - - Prove PowerShell is as "PowerFul" as compiled language. - - Improve my PowerShell skills. - - Because Remote Desktop Powershell Scripts doesn't exists so far. - - .Important - This PowerShell Application is not yet marked as Stable / Final. It is not recommended to use - it in a production environment at this time. - Wait for final 1.0 version. - .Disclaimer We are doing our best to prepare the content of this app. However, PHROZEN SASU and / or Jean-Pierre LESUEUR cannot warranty the expressions and suggestions of the contents, @@ -49,9 +39,19 @@ -------------------------------------------------------------------------------#> Add-Type -Assembly System.Windows.Forms -Add-Type -MemberDefinition '[DllImport("User32.dll")] public static extern bool SetProcessDPIAware();' -Name User32 -Namespace W; -$global:PowerRemoteDesktopVersion = "2.0.0" +Add-Type @" + using System; + using System.Runtime.InteropServices; + + public static class User32 + { + [DllImport("User32.dll")] + public static extern bool SetProcessDPIAware(); + } +"@ + +$global:PowerRemoteDesktopVersion = "3.0.0" $global:HostSyncHash = [HashTable]::Synchronized(@{ host = $host @@ -87,6 +87,25 @@ enum WorkerKind { Events = 2 } +enum BlockSize { + Size32 = 32 + Size64 = 64 + Size96 = 96 + Size128 = 128 + Size256 = 256 + Size512 = 512 +} + +enum PacketSize { + Size1024 = 1024 + Size2048 = 2048 + Size4096 = 4096 + Size8192 = 8192 + Size9216 = 9216 + Size12288 = 12288 + Size16384 = 16384 +} + function Write-Banner { <# @@ -718,6 +737,9 @@ class ViewerSession [bool] $UseTLSv1_3 = $false [int] $ImageCompressionQuality = 100 [int] $ResizeRatio = 0 + [PacketSize] $PacketSize = [PacketSize]::Size9216 + [BlockSize] $BlockSize = [BlockSize]::Size64 + [bool] $HighQualityResize = $false [ClientIO] $ClientDesktop = $null [ClientIO] $ClientEvents = $null @@ -725,9 +747,7 @@ class ViewerSession ViewerSession( [string] $ServerAddress, [int] $ServerPort, - [SecureString] $SecurePassword, - [bool] $UseTLSv1_3, - [int] $ImageCompressionQuality + [SecureString] $SecurePassword ) { # Or: System.Management.Automation.Runspaces.MaxPort (High(Word)) @@ -739,8 +759,6 @@ class ViewerSession $this.ServerAddress = $ServerAddress $this.ServerPort = $ServerPort $this.SecurePassword = $SecurePassword - $this.UseTLSv1_3 = $UseTLSv1_3 - $this.ImageCompressionQuality = $ImageCompressionQuality } [void] OpenSession() { @@ -918,7 +936,10 @@ class ViewerSession $viewerExpectation = New-Object PSCustomObject -Property @{ ScreenName = $selectedScreen.Name - ImageCompressionQuality = $this.ImageCompressionQuality + ImageCompressionQuality = $this.ImageCompressionQuality + PacketSize = $this.PacketSize + BlockSize = $this.BlockSize + HighQualityResize = $this.HighQualityResize } if ($this.ViewerConfiguration.RequireResize) @@ -1073,23 +1094,28 @@ $global:VirtualDesktopUpdaterScriptBlock = { try { $packetSize = 9216 # 9KiB - + + # SizeOf(DWORD) * 3 (SizeOf(Desktop) + SizeOf(Left) + SizeOf(Top)) + $struct = New-Object -TypeName byte[] -ArgumentList (([Runtime.InteropServices.Marshal]::SizeOf([System.Type][UInt32])) * 3) + + $stream = New-Object System.IO.MemoryStream + + $scene = $null + $sceneGraphics = $null + while ($true) - { - $stream = New-Object System.IO.MemoryStream + { try - { - $buffer = New-Object -TypeName byte[] -ArgumentList 4 # SizeOf(Int32) + { + $null = $Param.Client.SSLStream.Read($struct, 0, $struct.Length) - $Param.Client.SSLStream.Read($buffer, 0, $buffer.Length) - - [int32] $totalBufferSize = [BitConverter]::ToInt32($buffer, 0) + $totalBufferSize = [System.Runtime.InteropServices.Marshal]::ReadInt32($struct, 0x0) + $rectLeft = [System.Runtime.InteropServices.Marshal]::ReadInt32($struct, 0x4) + $rectTop = [System.Runtime.InteropServices.Marshal]::ReadInt32($struct, 0x8) $stream.SetLength($totalBufferSize) - $stream.position = 0 - - $buffer = New-Object -TypeName Byte[] -ArgumentList $packetSize + $stream.Position = 0 do { $bufferSize = $stream.Length - $stream.Position @@ -1102,22 +1128,62 @@ $global:VirtualDesktopUpdaterScriptBlock = { } until ($stream.Position -eq $stream.Length) $stream.Position = 0 - - $Param.VirtualDesktopSyncHash.VirtualDesktop.Picture.Image = [System.Drawing.Image]::FromStream($stream) + + if ($stream.Length -eq 0) + { + continue + } + + if (-not $scene) + { + # First Iteration + $scene = [System.Drawing.Image]::FromStream($stream) + $sceneGraphics = [System.Drawing.Graphics]::FromImage($scene) + + $Param.VirtualDesktopSyncHash.VirtualDesktop.Picture.Image = $scene + } + else + { + # Next Iterations + $sceneChunk = [System.Drawing.Image]::FromStream($stream) + + $sceneGraphics.DrawImage( + $sceneChunk, + [System.Drawing.Point]::New( + $rectLeft, + $rectTop + ) + ) + + $sceneChunk.Dispose() + + $Param.VirtualDesktopSyncHash.VirtualDesktop.Picture.Invalidate() + } } catch - { + { break - } - finally - { - $stream.Close() - } + } } } finally { + if ($scene) + { + $scene.Dispose() + } + + if ($sceneGraphics) + { + $sceneGraphics.Dispose() + } + + if ($stream) + { + $stream.Close() + } + $Param.VirtualDesktopSyncHash.VirtualDesktop.Form.Close() } } @@ -1520,6 +1586,29 @@ function Invoke-RemoteDesktopViewer .PARAMETER AlwaysOnTop If this switch is present, virtual desktop form will be above all other windows. + .PARAMETER BlockSize + Type: Enum + Values: Size32, Size64, Size96, Size128, Size256, Size512 + Default: Size64 + Description: + (Advanced) Define the screen grid block size. + Choose the block size accordingly to remote screen size / computer constrainsts (CPU / Network) + + .PARAMETER PacketSize + Type: Enum + Values: Size1024, Size2048, Size4096, Size8192, Size9216, Size12288, Size16384 + Default: Size9216 + Description: + (Advanced) Define the network packet size for streams. + Choose the packet size accordingly to your network constrainsts. + + .PARAMETER HighQualityResize + Type: Switch + Default: None + Description: + Control the quality of remote desktop resize (smoothing) if applicable. + If you lack of network speed, this option is not recommended. + .EXAMPLE Invoke-RemoteDesktopViewer -ServerAddress "192.168.0.10" -ServerPort "2801" -SecurePassword (ConvertTo-SecureString -String "s3cr3t!" -AsPlainText -Force) Invoke-RemoteDesktopViewer -ServerAddress "192.168.0.10" -ServerPort "2801" -Password "s3cr3t!" @@ -1548,7 +1637,10 @@ function Invoke-RemoteDesktopViewer [ValidateRange(30, 99)] [int] $ResizeRatio = 90, - [switch] $AlwaysOnTop + [switch] $AlwaysOnTop, + [PacketSize] $PacketSize = [PacketSize]::Size9216, + [BlockSize] $BlockSize = [BlockSize]::Size64, + [switch] $HighQualityResize = $false ) [System.Collections.Generic.List[PSCustomObject]]$runspaces = @() @@ -1570,7 +1662,7 @@ function Invoke-RemoteDesktopViewer Write-Banner - $null = [W.User32]::SetProcessDPIAware() + $null = [User32]::SetProcessDPIAware() Write-Verbose "Server address: ""${ServerAddress}:${ServerPort}""" @@ -1589,12 +1681,16 @@ function Invoke-RemoteDesktopViewer $session = [ViewerSession]::New( $ServerAddress, $ServerPort, - $SecurePassword, - $UseTLSv1_3, - $ImageCompressionQuality + $SecurePassword ) try { + $session.UseTLSv1_3 = $UseTLSv1_3 + $session.ImageCompressionQuality = $ImageCompressionQuality + $session.PacketSize = $PacketSize + $session.BlockSize = $BlockSize + $session.HighQualityResize = $HighQualityResize + if ($Resize) { $session.ResizeRatio = $ResizeRatio @@ -1880,8 +1976,7 @@ function Invoke-RemoteDesktopViewer } if ($result) - { - Write-Verbose $result + { Send-VirtualKeyboard -KeyChain $result } } diff --git a/README.md b/README.md index 8f75dd5..8a4d66c 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,20 @@ This project demonstrate why PowerShell contains the word *Power*. It is unfortu Tested on: -* **Windows 10** - PowerShell Version: 5.1.19041.1320 -* **Windows 11** - PowerShell Version: 5.1.22000.282 +* **Windows 10** +* **Windows 11** -Current version: **2.0 Stable** +Current version: **3.0 Stable** + +## Performance + +

+ +

+ +Enjoy better streaming performance and a better experience using **PowerShell 7** instead of **PowerShell 5**. + +You can install PowerShell 7 for Windows [here](https://docs.microsoft.com/fr-fr/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.2) --- @@ -35,6 +45,8 @@ 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. * Session concurrency. Multiple viewers can connect to a single server at the same time. +* Prevent computer to enter in sleep mode while server is waiting for viewers. +* Only pieces of desktop that was updated are sent to viewer to increase streaming speed. --- @@ -235,15 +247,41 @@ Create a new remote desktop session with a Power Remote Desktop Server. | Resize | Switch | False | If present, remote desktop will get resized accordingly with `ResizeRatio` option. | | ResizeRatio | Integer (30-99) | 90 | Used with `Resize` option, define the resize ratio in percentage. | | AlwaysOnTop | Switch | False | If present, virtual desktop form will be above all other window's | +| PacketSize | Enum | Size9216 | Define the network packet size for streams. Choose the packet size accordingly to your network constrainsts. | +| BlockSize | Enum | Size64 | Define the screen grid block size. Choose the block size accordingly to remote screen size / computer constrainsts (CPU / Network) | +| HighQualityResize | Switch | False | Control the quality of remote desktop resize (smoothing) if applicable. If you lack of network speed, this option is not recommended. | ##### Clipboard Mode Enum Properties +| Value | Description | +|-------------------|----------------------------------------------------| +| Disabled | Clipboard synchronization is disabled in both side | +| Receive | Only incomming clipboard is allowed | +| Send | Only outgoing clipboard is allowed | +| Both | Clipboard synchronization is allowed on both side | + +##### PacketSize Mode Enum Properties + +| Value | Description | +|-------------------|---------------------| +| Size1024 | 1024 Bytes (1KiB) | +| Size2048 | 2048 Bytes (2KiB) | +| Size4096 | 4096 Bytes (4KiB) | +| Size8192 | 8192 Bytes (8KiB) | +| Size9216 | 9216 Bytes (9KiB) | +| Size12288 | 12288 Bytes (12KiB) | +| Size16384 | 16384 Bytes (16KiB) | + +##### BlockSize Mode Enum Properties + | Value | Description | |-------------------|------------------| -| Disabled | Clipboard synchronization is disabled in both side | -| Receive | Only incomming clipboard is allowed | -| Send | Only outgoing clipboard is allowed | -| Both | Clipboard synchronization is allowed on both side | +| Size32 | 32x32 | +| Size64 | 64x64 | +| Size96 | 96x96 | +| Size128 | 128x128 | +| Size256 | 256x256 | +| Size512 | 512x512 | ##### ⚠️ Important Notices @@ -306,34 +344,35 @@ Invoke-RemoteDesktopServer ##### ⚙️ Supported Options: -| Parameter | Type | Default | Description | -|--------------------|------------------|------------|--------------| -| ServerAddress | String | 0.0.0.0 | IP Address that represents the local IP address | -| ServerPort | Integer | 2801 | The port on which to listen for incoming connection | -| SecurePassword | SecureString | None | SecureString object containing password used to authenticate remote viewer (Recommended) | -| Password | String | None | Plain-Text Password used to authenticate remote viewer (Not recommended, use SecurePassword instead) | -| DisableVerbosity | Switch | False | If present, program wont show verbosity messages | -| UseTLSv1_3 | Switch | False | If present, TLS v1.3 will be used instead of TLS v1.2 (Recommended if applicable to both systems) | -| Clipboard | Enum | Both | Define clipboard synchronization mode (`Both`, `Disabled`, `Send`, `Receive`) see bellow for more detail | -| CertificateFile | String | None | A file containing valid certificate information (x509), must include the **private key** | -| EncodedCertificate | String | None | A **base64** representation of the whole certificate file, must include the **private key** | -| ViewOnly | Switch | False | If present, remote viewer is only allowed to view the desktop (Mouse and Keyboard are not authorized) | +| Parameter | Type | Default | Description | +|------------------------|------------------|------------|--------------| +| ServerAddress | String | 0.0.0.0 | IP Address that represents the local IP address | +| ServerPort | Integer | 2801 | The port on which to listen for incoming connection | +| SecurePassword | SecureString | None | SecureString object containing password used to authenticate remote viewer (Recommended) | +| Password | String | None | Plain-Text Password used to authenticate remote viewer (Not recommended, use SecurePassword instead) | +| DisableVerbosity | Switch | False | If present, program wont show verbosity messages | +| UseTLSv1_3 | Switch | False | If present, TLS v1.3 will be used instead of TLS v1.2 (Recommended if applicable to both systems) | +| Clipboard | Enum | Both | Define clipboard synchronization mode (`Both`, `Disabled`, `Send`, `Receive`) see bellow for more detail | +| CertificateFile | String | None | A file containing valid certificate information (x509), must include the **private key** | +| EncodedCertificate | String | None | A **base64** representation of the whole certificate file, must include the **private key** | +| ViewOnly | Switch | False | If present, remote viewer is only allowed to view the desktop (Mouse and Keyboard are not authorized) | +| PreventComputerToSleep | Switch | None | If present, this option will prevent computer to enter in sleep mode while server is active and waiting for new connections. | ##### Server Address Examples -| Value | Description | -|-------------------|------------------| +| Value | Description | +|-------------------|--------------------------------------------------------------------------| | 127.0.0.1 | Only listen for localhost connection (most likely for debugging purpose) | -| 0.0.0.0 | Listen on all network interfaces (Local, LAN, WAN) | +| 0.0.0.0 | Listen on all network interfaces (Local, LAN, WAN) | ##### Clipboard Mode Enum Properties -| Value | Description | -|-------------------|------------------| +| Value | Description | +|-------------------|----------------------------------------------------| | Disabled | Clipboard synchronization is disabled in both side | -| Receive | Only incomming clipboard is allowed | -| Send | Only outgoing clipboard is allowed | -| Both | Clipboard synchronization is allowed on both side | +| Receive | Only incomming clipboard is allowed | +| Send | Only outgoing clipboard is allowed | +| Both | Clipboard synchronization is allowed on both side | ##### ⚠️ Important Notices @@ -451,12 +490,22 @@ You can then pass the output base64 certificate file to parameter `EncodedCertif https://user-images.githubusercontent.com/2520298/151220460-d620402b-da78-4d6d-8b5e-a96f44499013.mp4 +### 9 February 2022 (3.0.0) + +* Prevent computer to sleep in server side. +* Motion Update now supported in its very first version to increase desktop streaming speed. +* Mouse move works as expected in certain circumstances. +* Keyboard simulation improved. +* Various Optimization and fixes. + ### List of ideas and TODO * 🟢 Support Password Protected external Certificates. * 🟢 Mutual Authentication for SSL/TLS (Client Certificate). * 🟠 Listen for local/remote screen resolution update event. -* 🔴 Motion Update for Desktop Streaming (Only send and update changing parts of desktop). +* 🟠 Desktop Streaming Optimization. +* 🟠 Mouse Move / Events Optimization. +* 🔴 LogonUI Support. 🟢 = Easy 🟠 = Medium diff --git a/TestServer.ps1 b/TestServer.ps1 index c143316..bb2cfc3 100644 --- a/TestServer.ps1 +++ b/TestServer.ps1 @@ -2,4 +2,4 @@ Write-Output "This script is used during development phase. Never run this scrip Invoke-Expression -Command (Get-Content "PowerRemoteDesktop_Server\PowerRemoteDesktop_Server.psm1" -Raw) -Invoke-RemoteDesktopServer -SecurePassword (ConvertTo-SecureString -String "Jade@123@Pwd" -AsPlainText -Force) -EncodedCertificate "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" \ No newline at end of file +Invoke-RemoteDesktopServer -PreventComputerToSleep -SecurePassword (ConvertTo-SecureString -String "Jade@123@Pwd" -AsPlainText -Force) -EncodedCertificate "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" \ No newline at end of file diff --git a/TestViewer.ps1 b/TestViewer.ps1 index 9bf1c7a..42f43ea 100644 --- a/TestViewer.ps1 +++ b/TestViewer.ps1 @@ -2,5 +2,5 @@ Write-Output "This script is used during development phase. Never run this scrip Invoke-Expression -Command (Get-Content "PowerRemoteDesktop_Viewer\PowerRemoteDesktop_Viewer.psm1" -Raw) -Invoke-RemoteDesktopViewer -SecurePassword (ConvertTo-SecureString -String "Jade@123@Pwd" -AsPlainText -Force) -ServerAddress "127.0.0.1" +Invoke-RemoteDesktopViewer -SecurePassword (ConvertTo-SecureString -String "Jade@123@Pwd" -AsPlainText -Force) -ServerAddress "192.168.1.25" -BlockSize "Size128" -ImageCompressionQuality 100 -PacketSize "Size16384" -Resize -ResizeRatio 50 #Invoke-RemoteDesktopViewer -SecurePassword (ConvertTo-SecureString -String "Jade@123@Pwd" -AsPlainText -Force) -ServerAddress "172.31.115.183" \ No newline at end of file