From 1e91c41b9714795fc3114ec05375fde24127b564 Mon Sep 17 00:00:00 2001 From: SaltSpectre <200460916+SaltSpectre@users.noreply.github.com> Date: Sat, 2 May 2026 10:19:04 -0400 Subject: [PATCH 1/2] feat(config): add ability to enable/disable local and/or RDP activity with config options for runtime restore and toggles in the system tray menu --- README.md | 3 ++ config.json | 8 +-- skhost.ps1 | 138 ++++++++++++++++++++++++++++++++++------------------ version.txt | 2 +- 4 files changed, 99 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 4037358..a4a9523 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ For a list of all possible keystrokes, refer to the [SendKeys Class documentatio ### Changing the execution interval By default the script will invoke the keep-alive logic every 240 seconds (4 minutes). This can be configured in `config.json` via the `loopIntervalSeconds` property. The value must be in seconds and must be a positive integer value. The script will validate the value and revert back to 240 seconds if the configured value is invalid. +### Enabling or disabling activity types +The system tray menu includes toggles for local activity and RDP/RemoteApp activity. These settings are saved in `config.json` via the `localEnabled` and `rdpEnabled` properties. Local activity controls the skSink keystroke sent to the host, while RDP activity controls background mouse movement for RDP and RemoteApp windows. + ### Required Files and Configuration skHost requires the following files to operate properly: diff --git a/config.json b/config.json index 9723ba8..0a3ce4b 100644 --- a/config.json +++ b/config.json @@ -1,4 +1,6 @@ -{ - "skHostKeystroke": "{F15}", - "loopIntervalSeconds": 240 +{ + "skHostKeystroke": "{F15}", + "loopIntervalSeconds": 240, + "localEnabled": false, + "rdpEnabled": true } diff --git a/skhost.ps1 b/skhost.ps1 index ee84ae0..8acba4f 100644 --- a/skhost.ps1 +++ b/skhost.ps1 @@ -46,6 +46,11 @@ if ($Uninstall) { Break } +$ConfigPath = Join-Path $PSScriptRoot "config.json" +$Config = Get-Content $ConfigPath | ConvertFrom-Json +$script:localEnabled = if ($null -ne $Config.localEnabled) { [bool]$Config.localEnabled } else { $true } +$script:rdpEnabled = if ($null -ne $Config.rdpEnabled) { [bool]$Config.rdpEnabled } else { $true } + # Create system tray icon $iconFile = Find-IconFile -BasePath $PSScriptRoot -IconName "skHost" $SysTrayIconImage = New-IconFromFile -IconPath $iconFile @@ -63,6 +68,20 @@ $menuItem_OpenLog.Text = "Log Viewer" $menuItem_OpenLog.add_click({ Toggle-LogViewer }) +$menuItem_Local = New-Object System.Windows.Forms.MenuItem +$menuItem_Local.add_click({ + $script:localEnabled = -not $script:localEnabled + Save-skConfig + Update-skMenuLabels + Write-skSessionLog -Message "Local activity $($(if ($script:localEnabled) { 'enabled' } else { 'disabled' }))" -Type "INFO" -Color Cyan + }) +$menuItem_Rdp = New-Object System.Windows.Forms.MenuItem +$menuItem_Rdp.add_click({ + $script:rdpEnabled = -not $script:rdpEnabled + Save-skConfig + Update-skMenuLabels + Write-skSessionLog -Message "RDP activity $($(if ($script:rdpEnabled) { 'enabled' } else { 'disabled' }))" -Type "INFO" -Color Cyan + }) $menuItem_Exit = New-Object System.Windows.Forms.MenuItem $menuItem_Exit.Text = "Exit" $menuItem_Exit.add_click({ @@ -74,7 +93,7 @@ $menuItem_Exit.add_click({ Start-Sleep 1 [System.Windows.Forms.Application]::Exit() }) -$notifyIcon.ContextMenu.MenuItems.AddRange(@($menuItem_OpenLog, $menuItem_Exit)) +$notifyIcon.ContextMenu.MenuItems.AddRange(@($menuItem_OpenLog, $menuItem_Local, $menuItem_Rdp, $menuItem_Exit)) $notifyIcon.Visible = $true #region CoreLogic and Helper Functions @@ -82,9 +101,6 @@ Add-Type -AssemblyName System.Windows.Forms Add-Type -TypeDefinition $(Get-Content "$PSScriptRoot\user32.cs" -Raw) Add-Type -TypeDefinition $(Get-Content "$PSScriptRoot\mouse.cs" -Raw) -# Load configuration file -$Config = Get-Content "$PSScriptRoot\config.json" | ConvertFrom-Json - # Read keystroke from config file or default to Ctrl+Shift+F15 if ($Config.skHostKeystroke -and $Config.skHostKeystroke.Trim() -ne "") { $skHostKeystroke = $Config.skHostKeystroke.Trim() @@ -109,7 +125,25 @@ if ($Config.loopIntervalSeconds -and $Config.loopIntervalSeconds -is [int] -and } # Update the tooltip to show the configured keystroke and interval -$notifyIcon.Text = "skHost ($(Get-Content "$PSScriptRoot\version.txt" -TotalCount 1))`nKeystroke: $skHostKeystroke`nInterval: $($skHostInterval / 1000) seconds" +Function Save-skConfig { + $configOutput = [ordered]@{ + skHostKeystroke = if ($Config.skHostKeystroke) { $Config.skHostKeystroke } else { $skHostKeystroke } + loopIntervalSeconds = if ($Config.loopIntervalSeconds) { $Config.loopIntervalSeconds } else { [int]($skHostInterval / 1000) } + localEnabled = $script:localEnabled + rdpEnabled = $script:rdpEnabled + } + + $configOutput | ConvertTo-Json | Set-Content -Path $ConfigPath -Encoding utf8 + $script:Config = Get-Content $ConfigPath | ConvertFrom-Json +} + +Function Update-skMenuLabels { + $menuItem_Local.Text = if ($script:localEnabled) { "Disable Local" } else { "Enable Local" } + $menuItem_Rdp.Text = if ($script:rdpEnabled) { "Disable RDP" } else { "Enable RDP" } + $notifyIcon.Text = "skHost ($(Get-Content "$PSScriptRoot\version.txt" -TotalCount 1))`nKeystroke: $skHostKeystroke`nInterval: $($skHostInterval / 1000) seconds" +} + +Update-skMenuLabels Function Move-MouseCursor { [Mouse]::MoveMouse() @@ -244,57 +278,65 @@ $RdpWindowClasses = @( Function Invoke-skLogic { Write-skSessionLog -Message "========== Main Loop Started ==========" -Type "INFO" -Color Cyan - # Create temporary window ("skSink"), activate it, send keystroke via SendKeys, then destroy it - Write-skSessionLog -Message "🪄 Creating temporary skSink window for keystroke activity" -Type "DEBUG" -Color Yellow - $hInstance = [User32]::GetModuleHandle($null) - $skSink = [User32]::CreateWindowEx(0, "Static", "", 0x10000000, -10000, -10000, 1, 1, [IntPtr]::Zero, [IntPtr]::Zero, $hInstance, [IntPtr]::Zero) - - if ($skSink -ne [IntPtr]::Zero) { - Write-skSessionLog -Message "✔️ Temporary skSink window created successfully [Handle: $skSink]" -Type "SUCCESS" -Color Green - - # Activate the temporary window - [User32]::SetForegroundWindow($skSink) | Out-Null - Write-skSessionLog -Message "✔️ Temporary skSink window activated" -Type "SUCCESS" -Color Green - - # Send keystroke using SendKeys (goes to active window) - [System.Windows.Forms.SendKeys]::SendWait("$skHostKeystroke") - Write-skSessionLog -Message "✔️ Keystroke sent to skSink: $skHostKeystroke" -Type "SUCCESS" -Color Green + if ($script:localEnabled) { + # Create temporary window ("skSink"), activate it, send keystroke via SendKeys, then destroy it + Write-skSessionLog -Message "🪄 Creating temporary skSink window for keystroke activity" -Type "DEBUG" -Color Yellow + $hInstance = [User32]::GetModuleHandle($null) + $skSink = [User32]::CreateWindowEx(0, "Static", "", 0x10000000, -10000, -10000, 1, 1, [IntPtr]::Zero, [IntPtr]::Zero, $hInstance, [IntPtr]::Zero) - # Destroy the temporary window - [User32]::DestroyWindow($skSink) | Out-Null - Write-skSessionLog -Message "✔️ Temporary skSink window destroyed" -Type "SUCCESS" -Color Green + if ($skSink -ne [IntPtr]::Zero) { + Write-skSessionLog -Message "✔️ Temporary skSink window created successfully [Handle: $skSink]" -Type "SUCCESS" -Color Green + + # Activate the temporary window + [User32]::SetForegroundWindow($skSink) | Out-Null + Write-skSessionLog -Message "✔️ Temporary skSink window activated" -Type "SUCCESS" -Color Green + + # Send keystroke using SendKeys (goes to active window) + [System.Windows.Forms.SendKeys]::SendWait("$skHostKeystroke") + Write-skSessionLog -Message "✔️ Keystroke sent to skSink: $skHostKeystroke" -Type "SUCCESS" -Color Green + + # Destroy the temporary window + [User32]::DestroyWindow($skSink) | Out-Null + Write-skSessionLog -Message "✔️ Temporary skSink window destroyed" -Type "SUCCESS" -Color Green + } else { + Write-skSessionLog -Message "❌ Failed to create temporary skSink window" -Type "ERROR" -Color Red + } } else { - Write-skSessionLog -Message "❌ Failed to create temporary skSink window" -Type "ERROR" -Color Red + Write-skSessionLog -Message "⏭️ Local activity disabled--skipping skSink keystroke." -Type "INFO" -Color Cyan } - Write-skSessionLog -Message "🔍 Searching for RDP and RemoteApp windows across all desktops." -Type "DEBUG" -Color Yellow - $ActiveRdpWindows = @(Get-WindowsByClass -ClassName $RdpWindowClasses) - if ($ActiveRdpWindows -and $ActiveRdpWindows.Count -gt 0) { - Write-skSessionLog -Message "✔️ Found $($ActiveRdpWindows.Count) active session(s). Checking foreground window..." -Type "INFO" -Color Magenta - $ForegroundWindowHandle = [User32]::GetForegroundWindow() - $ForegroundWindow = Get-WindowInformation -Handle $ForegroundWindowHandle - Write-skSessionLog -Message "🎯 Current foreground: [$ForegroundWindowHandle] $($ForegroundWindow.Class) - '$($ForegroundWindow.Title)'" -Type "DEBUG" -Color Magenta - - foreach ($RdpWindow in $ActiveRdpWindows) { - $WindowHandle = $RdpWindow.Handle - Write-skSessionLog -Message "⌛ Processing session: [$WindowHandle] $($RdpWindow.Class) - '$($RdpWindow.Title)'" -Type "DEBUG" -Color Yellow - - if ([User32]::IsIconic($WindowHandle)) { - Write-skSessionLog -Message "⏭️ Skipping minimized session: [$WindowHandle] '$($RdpWindow.Title)'" -Type "DEBUG" -Color DarkGray - } - elseif (Test-WindowIsForegroundRoot -WindowHandle $WindowHandle -ForegroundWindowHandle $ForegroundWindowHandle) { - Move-MouseCursor - Write-skSessionLog -Message "✔️ Sent foreground mouse input to active session: [$WindowHandle] '$($RdpWindow.Title)'" -Type "SUCCESS" -Color Green - } - else { - $backgroundInputSent = Invoke-BackgroundSessionMouseInput -Window $RdpWindow - if (-not $backgroundInputSent) { - Write-skSessionLog -Message "⏭️ Skipping: no background input target found for [$WindowHandle] '$($RdpWindow.Title)'" -Type "WARNING" -Color Yellow + if ($script:rdpEnabled) { + Write-skSessionLog -Message "🔍 Searching for RDP and RemoteApp windows across all desktops." -Type "DEBUG" -Color Yellow + $ActiveRdpWindows = @(Get-WindowsByClass -ClassName $RdpWindowClasses) + if ($ActiveRdpWindows -and $ActiveRdpWindows.Count -gt 0) { + Write-skSessionLog -Message "✔️ Found $($ActiveRdpWindows.Count) active session(s). Checking foreground window..." -Type "INFO" -Color Magenta + $ForegroundWindowHandle = [User32]::GetForegroundWindow() + $ForegroundWindow = Get-WindowInformation -Handle $ForegroundWindowHandle + Write-skSessionLog -Message "🎯 Current foreground: [$ForegroundWindowHandle] $($ForegroundWindow.Class) - '$($ForegroundWindow.Title)'" -Type "DEBUG" -Color Magenta + + foreach ($RdpWindow in $ActiveRdpWindows) { + $WindowHandle = $RdpWindow.Handle + Write-skSessionLog -Message "⌛ Processing session: [$WindowHandle] $($RdpWindow.Class) - '$($RdpWindow.Title)'" -Type "DEBUG" -Color Yellow + + if ([User32]::IsIconic($WindowHandle)) { + Write-skSessionLog -Message "⏭️ Skipping minimized session: [$WindowHandle] '$($RdpWindow.Title)'" -Type "DEBUG" -Color DarkGray + } + elseif (Test-WindowIsForegroundRoot -WindowHandle $WindowHandle -ForegroundWindowHandle $ForegroundWindowHandle) { + Move-MouseCursor + Write-skSessionLog -Message "✔️ Sent foreground mouse input to active session: [$WindowHandle] '$($RdpWindow.Title)'" -Type "SUCCESS" -Color Green + } + else { + $backgroundInputSent = Invoke-BackgroundSessionMouseInput -Window $RdpWindow + if (-not $backgroundInputSent) { + Write-skSessionLog -Message "⏭️ Skipping: no background input target found for [$WindowHandle] '$($RdpWindow.Title)'" -Type "WARNING" -Color Yellow + } } } + } else { + Write-skSessionLog -Message "✔️ No matching windows found--nothing to do." -Type "DEBUG" -Color Yellow } } else { - Write-skSessionLog -Message "✔️ No matching windows found--nothing to do." -Type "DEBUG" -Color Yellow + Write-skSessionLog -Message "⏭️ RDP activity disabled--skipping RDP and RemoteApp windows." -Type "INFO" -Color Cyan } Write-skSessionLog -Message "========== Main Loop Completed ==========" -Type "INFO" -Color Cyan } diff --git a/version.txt b/version.txt index d4528b7..b848428 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -dev-vNext \ No newline at end of file +dev-localtoggle \ No newline at end of file From 46cde75c50d12cfb851b81fac6234712fdab6c8d Mon Sep 17 00:00:00 2001 From: SaltSpectre <200460916+SaltSpectre@users.noreply.github.com> Date: Sat, 2 May 2026 10:49:54 -0400 Subject: [PATCH 2/2] feat(ui): enhance system tray menu with improved logging messages and reorganize menu items --- skhost.ps1 | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/skhost.ps1 b/skhost.ps1 index 8acba4f..2c2313e 100644 --- a/skhost.ps1 +++ b/skhost.ps1 @@ -63,24 +63,28 @@ $notifyIcon.ContextMenu = New-Object System.Windows.Forms.ContextMenu $notifyIcon.add_DoubleClick({ Toggle-LogViewer }) -$menuItem_OpenLog = New-Object System.Windows.Forms.MenuItem -$menuItem_OpenLog.Text = "Log Viewer" -$menuItem_OpenLog.add_click({ - Toggle-LogViewer - }) $menuItem_Local = New-Object System.Windows.Forms.MenuItem $menuItem_Local.add_click({ $script:localEnabled = -not $script:localEnabled Save-skConfig Update-skMenuLabels - Write-skSessionLog -Message "Local activity $($(if ($script:localEnabled) { 'enabled' } else { 'disabled' }))" -Type "INFO" -Color Cyan + Write-skSessionLog -Message "✔️ Local activity $($(if ($script:localEnabled) { 'enabled 🟢' } else { 'disabled 🔴' }))" -Type "SUCCESS" -Color Green + Write-skSessionLog -Message "Executing one-time main logic to reflect local activity change across all sessions..." -Type "INFO" -Color Cyan + Invoke-skLogic }) $menuItem_Rdp = New-Object System.Windows.Forms.MenuItem $menuItem_Rdp.add_click({ $script:rdpEnabled = -not $script:rdpEnabled Save-skConfig Update-skMenuLabels - Write-skSessionLog -Message "RDP activity $($(if ($script:rdpEnabled) { 'enabled' } else { 'disabled' }))" -Type "INFO" -Color Cyan + Write-skSessionLog -Message "✔️ RDP activity $($(if ($script:rdpEnabled) { 'enabled 🟢' } else { 'disabled 🔴' }))" -Type "SUCCESS" -Color Green + Write-skSessionLog -Message "Executing one-time main logic to reflect RDP activity change across all sessions..." -Type "INFO" -Color Cyan + Invoke-skLogic + }) +$menuItem_OpenLog = New-Object System.Windows.Forms.MenuItem +$menuItem_OpenLog.Text = "Log Viewer" +$menuItem_OpenLog.add_click({ + Toggle-LogViewer }) $menuItem_Exit = New-Object System.Windows.Forms.MenuItem $menuItem_Exit.Text = "Exit" @@ -93,7 +97,12 @@ $menuItem_Exit.add_click({ Start-Sleep 1 [System.Windows.Forms.Application]::Exit() }) -$notifyIcon.ContextMenu.MenuItems.AddRange(@($menuItem_OpenLog, $menuItem_Local, $menuItem_Rdp, $menuItem_Exit)) +$notifyIcon.ContextMenu.MenuItems.AddRange(@( + $menuItem_Local, + $menuItem_Rdp, + $menuItem_OpenLog, + $menuItem_Exit + )) $notifyIcon.Visible = $true #region CoreLogic and Helper Functions