# drive.ps1 - focus the Code Count window, optionally send keys, optionally capture a screenshot. # Usage: powershell -File drive.ps1 -Keys "alt right right down enter" -Out shot.png -SettleMs 800 param( [string]$Keys = "", [string]$Out = "", [string]$RClick = "", # "x,y" window-relative right click [string]$LClick = "", # "x,y" window-relative left click [string]$Move = "", # "x,y" window-relative hover (no click) [string]$Wheel = "", # "x,y,ticks" wheel at point (positive ticks = up) [int]$SettleMs = 700, [int]$KeyGapMs = 150, [long]$Hwnd = 0 ) $ErrorActionPreference = 'Stop' Add-Type -ReferencedAssemblies System.Drawing @" using System; using System.Runtime.InteropServices; public class Drv { [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr dc, uint flags); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r); [DllImport("user32.dll")] public static extern void keybd_event(byte vk, byte sc, uint fl, UIntPtr ex); [DllImport("user32.dll")] public static extern bool SetCursorPos(int x, int y); [DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT p); [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; } [DllImport("user32.dll")] public static extern void mouse_event(uint fl, int dx, int dy, uint data, UIntPtr ex); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern IntPtr FindWindow(string cls, string title); [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd); [DllImport("user32.dll")] public static extern IntPtr WindowFromPoint(POINT p); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid); [DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetClassName(IntPtr h, System.Text.StringBuilder sb, int n); [DllImport("user32.dll")] public static extern bool SetProcessDPIAware(); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L; public int T; public int R; public int B; } } "@ [Drv]::SetProcessDPIAware() | Out-Null if ($Hwnd -ne 0) { $h = [IntPtr]$Hwnd } else { # Window title is non-ASCII now; resolve via process main window handle to keep this file ASCII-safe. $proc = Get-Process code-count | Where-Object { $_.MainWindowHandle -ne 0 } | Sort-Object StartTime -Descending | Select-Object -First 1 if (-not $proc) { throw "app window not found" } $h = $proc.MainWindowHandle } if ([Drv]::IsIconic($h)) { [Drv]::ShowWindow($h, 9) | Out-Null # SW_RESTORE Start-Sleep -Milliseconds 600 Write-Output "restored from minimized" } [Drv]::SetForegroundWindow($h) | Out-Null Start-Sleep -Milliseconds 300 if ([Drv]::GetForegroundWindow() -ne $h) { # press Alt to satisfy the foreground-lock heuristic, then retry [Drv]::keybd_event(0x12, 0, 0, [UIntPtr]::Zero) [Drv]::SetForegroundWindow($h) | Out-Null [Drv]::keybd_event(0x12, 0, 2, [UIntPtr]::Zero) Start-Sleep -Milliseconds 300 } Write-Output ("foreground=" + ([Drv]::GetForegroundWindow() -eq $h)) Start-Sleep -Milliseconds 200 $vk = @{ alt=0x12; ctrl=0x11; shift=0x10; enter=0x0D; esc=0x1B; tab=0x09; space=0x20; f10=0x79; left=0x25; up=0x26; right=0x27; down=0x28; apps=0x5D } $ext = @(0x25,0x26,0x27,0x28,0x5D) function Press([int[]]$codes) { foreach ($c in $codes) { $f = if ($ext -contains $c) { 1 } else { 0 } [Drv]::keybd_event([byte]$c, 0, [uint32]$f, [UIntPtr]::Zero) Start-Sleep -Milliseconds 25 } [array]::Reverse($codes) foreach ($c in $codes) { $f = if ($ext -contains $c) { 3 } else { 2 } [Drv]::keybd_event([byte]$c, 0, [uint32]$f, [UIntPtr]::Zero) Start-Sleep -Milliseconds 25 } } if ($Keys) { foreach ($tok in $Keys.Split(' ')) { if (-not $tok) { continue } if ($tok.StartsWith('type:')) { foreach ($ch in $tok.Substring(5).ToCharArray()) { Press @([int][char]::ToUpper($ch)) } continue } $codes = @() foreach ($part in $tok.Split('+')) { if ($vk.ContainsKey($part)) { $codes += [int]$vk[$part] } elseif ($part.Length -eq 1) { $codes += [int][char]::ToUpper($part[0]) } else { throw "unknown key: $part" } } Press $codes Start-Sleep -Milliseconds $KeyGapMs } } if ($Move) { $xy = $Move.Split(',') $rm = New-Object Drv+RECT [Drv]::GetWindowRect($h, [ref]$rm) | Out-Null [Drv]::SetCursorPos($rm.L + [int]$xy[0], $rm.T + [int]$xy[1]) | Out-Null # nudge one pixel so the webview registers a mousemove event Start-Sleep -Milliseconds 80 [Drv]::mouse_event(0x0001, 1, 0, 0, [UIntPtr]::Zero) Start-Sleep -Milliseconds 200 } if ($RClick) { $xy = $RClick.Split(',') $r0 = New-Object Drv+RECT [Drv]::GetWindowRect($h, [ref]$r0) | Out-Null $px = $r0.L + [int]$xy[0]; $py = $r0.T + [int]$xy[1] [Drv]::SetCursorPos($px, $py) | Out-Null Start-Sleep -Milliseconds 150 [Drv]::mouse_event(0x0008, 0, 0, 0, [UIntPtr]::Zero) # right down Start-Sleep -Milliseconds 60 [Drv]::mouse_event(0x0010, 0, 0, 0, [UIntPtr]::Zero) # right up Start-Sleep -Milliseconds 300 } if ($LClick) { $xy = $LClick.Split(',') $r1 = New-Object Drv+RECT [Drv]::GetWindowRect($h, [ref]$r1) | Out-Null $tx = $r1.L + [int]$xy[0]; $ty = $r1.T + [int]$xy[1] [Drv]::SetCursorPos($tx - 1, $ty) | Out-Null Start-Sleep -Milliseconds 80 [Drv]::mouse_event(0x0001, 1, 0, 0, [UIntPtr]::Zero) # real WM_MOUSEMOVE for the webview Start-Sleep -Milliseconds 120 $cp = New-Object Drv+POINT [Drv]::GetCursorPos([ref]$cp) | Out-Null $wfp = [Drv]::WindowFromPoint($cp) $wpid = 0 [Drv]::GetWindowThreadProcessId($wfp, [ref]$wpid) | Out-Null $wcls = New-Object System.Text.StringBuilder 128 [Drv]::GetClassName($wfp, $wcls, 128) | Out-Null Write-Output "rect=($($r1.L),$($r1.T))-($($r1.R),$($r1.B)) target=($tx,$ty) cursor=($($cp.X),$($cp.Y)) under=$wfp cls=$($wcls.ToString()) pid=$wpid" [Drv]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero) # left down Start-Sleep -Milliseconds 60 [Drv]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero) # left up Start-Sleep -Milliseconds 300 } if ($Wheel) { $wp = $Wheel.Split(',') $r2 = New-Object Drv+RECT [Drv]::GetWindowRect($h, [ref]$r2) | Out-Null [Drv]::SetCursorPos($r2.L + [int]$wp[0], $r2.T + [int]$wp[1]) | Out-Null Start-Sleep -Milliseconds 120 $ticks = [int]$wp[2] $dir = if ($ticks -gt 0) { 120 } else { -120 } for ($i = 0; $i -lt [Math]::Abs($ticks); $i++) { [Drv]::mouse_event(0x0800, 0, 0, [uint32]($dir -band 0xFFFFFFFF), [UIntPtr]::Zero) Start-Sleep -Milliseconds 60 } } if ($Out) { Start-Sleep -Milliseconds $SettleMs $r = New-Object Drv+RECT [Drv]::GetWindowRect($h, [ref]$r) | Out-Null $w = $r.R - $r.L; $ht = $r.B - $r.T $bmp = New-Object System.Drawing.Bitmap($w, $ht) $g = [System.Drawing.Graphics]::FromImage($bmp) $dc = $g.GetHdc() [Drv]::PrintWindow($h, $dc, 2) | Out-Null $g.ReleaseHdc($dc); $g.Dispose() $bmp.Save($Out, [System.Drawing.Imaging.ImageFormat]::Png) $bmp.Dispose() Write-Output "saved $Out ($w x $ht)" }