comber 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +732 -0
  3. package/action.yml +279 -0
  4. package/comber.config.example.json +64 -0
  5. package/dist/api/classify.js +99 -0
  6. package/dist/api/classify.js.map +1 -0
  7. package/dist/api/driver.js +235 -0
  8. package/dist/api/driver.js.map +1 -0
  9. package/dist/api/graphql-driver.js +150 -0
  10. package/dist/api/graphql-driver.js.map +1 -0
  11. package/dist/api/graphql.js +115 -0
  12. package/dist/api/graphql.js.map +1 -0
  13. package/dist/api/har-driver.js +167 -0
  14. package/dist/api/har-driver.js.map +1 -0
  15. package/dist/api/har.js +100 -0
  16. package/dist/api/har.js.map +1 -0
  17. package/dist/api/load.js +83 -0
  18. package/dist/api/load.js.map +1 -0
  19. package/dist/api/openapi.js +159 -0
  20. package/dist/api/openapi.js.map +1 -0
  21. package/dist/api/request.js +141 -0
  22. package/dist/api/request.js.map +1 -0
  23. package/dist/api/schema.js +126 -0
  24. package/dist/api/schema.js.map +1 -0
  25. package/dist/cli.js +352 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/comment.js +164 -0
  28. package/dist/comment.js.map +1 -0
  29. package/dist/config.js +664 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/connectors/builtin/discord.js +55 -0
  32. package/dist/connectors/builtin/github-checks.js +53 -0
  33. package/dist/connectors/builtin/junit.js +70 -0
  34. package/dist/connectors/builtin/linear.js +58 -0
  35. package/dist/connectors/builtin/slack.js +60 -0
  36. package/dist/connectors/builtin/webhook.js +39 -0
  37. package/dist/connectors/index.js +3 -0
  38. package/dist/connectors/loader.js +72 -0
  39. package/dist/connectors/registry.js +28 -0
  40. package/dist/connectors/types.js +23 -0
  41. package/dist/core/action-gate.js +48 -0
  42. package/dist/core/baseline.js +85 -0
  43. package/dist/core/baseline.js.map +1 -0
  44. package/dist/core/blind-spots.js +22 -0
  45. package/dist/core/blind-spots.js.map +1 -0
  46. package/dist/core/contract-extras.js +1 -0
  47. package/dist/core/contract-extras.js.map +1 -0
  48. package/dist/core/contract.js +1 -0
  49. package/dist/core/contract.js.map +1 -0
  50. package/dist/core/coverage.js +70 -0
  51. package/dist/core/coverage.js.map +1 -0
  52. package/dist/core/crawl.js +229 -0
  53. package/dist/core/crawl.js.map +1 -0
  54. package/dist/core/egress.js +128 -0
  55. package/dist/core/egress.js.map +1 -0
  56. package/dist/core/ledger.js +41 -0
  57. package/dist/core/ledger.js.map +1 -0
  58. package/dist/core/net.js +39 -0
  59. package/dist/core/pipeline.js +191 -0
  60. package/dist/core/pipeline.js.map +1 -0
  61. package/dist/core/plan.js +121 -0
  62. package/dist/core/plan.js.map +1 -0
  63. package/dist/core/redact.js +240 -0
  64. package/dist/core/redact.js.map +1 -0
  65. package/dist/core/replay.js +263 -0
  66. package/dist/core/replay.js.map +1 -0
  67. package/dist/core/report.js +253 -0
  68. package/dist/core/report.js.map +1 -0
  69. package/dist/core/safety-judge.js +116 -0
  70. package/dist/core/safety.js +92 -0
  71. package/dist/core/safety.js.map +1 -0
  72. package/dist/core/signal-normalize.js +190 -0
  73. package/dist/core/signal-normalize.js.map +1 -0
  74. package/dist/core/signals.js +5 -0
  75. package/dist/core/signals.js.map +1 -0
  76. package/dist/core/triage.js +104 -0
  77. package/dist/core/triage.js.map +1 -0
  78. package/dist/core/types.js +1 -0
  79. package/dist/core/types.js.map +1 -0
  80. package/dist/desktop/desktop-profile.js +6 -0
  81. package/dist/desktop/desktop-profile.js.map +1 -0
  82. package/dist/desktop/driver.js +433 -0
  83. package/dist/desktop/driver.js.map +1 -0
  84. package/dist/desktop/evidence.js +206 -0
  85. package/dist/desktop/evidence.js.map +1 -0
  86. package/dist/desktop/macos/accessibility-runner.js +118 -0
  87. package/dist/desktop/macos/accessibility-runner.js.map +1 -0
  88. package/dist/desktop/macos/types.js +1 -0
  89. package/dist/desktop/macos/types.js.map +1 -0
  90. package/dist/desktop/preflight.js +107 -0
  91. package/dist/desktop/preflight.js.map +1 -0
  92. package/dist/desktop/proxy-proof.js +185 -0
  93. package/dist/desktop/proxy-proof.js.map +1 -0
  94. package/dist/desktop/view.js +348 -0
  95. package/dist/desktop/view.js.map +1 -0
  96. package/dist/desktop/windows/types.js +1 -0
  97. package/dist/desktop/windows/types.js.map +1 -0
  98. package/dist/desktop/windows/uia-runner.js +127 -0
  99. package/dist/desktop/windows/uia-runner.js.map +1 -0
  100. package/dist/native/apple.js +14 -0
  101. package/dist/native/apple.js.map +1 -0
  102. package/dist/native/device-profile.js +6 -0
  103. package/dist/native/device-profile.js.map +1 -0
  104. package/dist/native/driver.js +416 -0
  105. package/dist/native/driver.js.map +1 -0
  106. package/dist/native/evidence.js +164 -0
  107. package/dist/native/evidence.js.map +1 -0
  108. package/dist/native/load.js +9 -0
  109. package/dist/native/load.js.map +1 -0
  110. package/dist/native/maestro.js +153 -0
  111. package/dist/native/maestro.js.map +1 -0
  112. package/dist/native/preflight.js +72 -0
  113. package/dist/native/preflight.js.map +1 -0
  114. package/dist/native/proxy-proof.js +182 -0
  115. package/dist/native/proxy-proof.js.map +1 -0
  116. package/dist/native/samsung.js +34 -0
  117. package/dist/native/samsung.js.map +1 -0
  118. package/dist/native/signals.js +50 -0
  119. package/dist/native/signals.js.map +1 -0
  120. package/dist/native/view.js +139 -0
  121. package/dist/native/view.js.map +1 -0
  122. package/dist/profile.js +237 -0
  123. package/dist/profile.js.map +1 -0
  124. package/dist/sink.js +134 -0
  125. package/dist/sink.js.map +1 -0
  126. package/dist/sinks/file.js +49 -0
  127. package/dist/sinks/file.js.map +1 -0
  128. package/dist/sinks/http.js +38 -0
  129. package/dist/sinks/http.js.map +1 -0
  130. package/dist/sinks/index.js +5 -0
  131. package/dist/sinks/index.js.map +1 -0
  132. package/dist/sinks/multi.js +40 -0
  133. package/dist/sinks/multi.js.map +1 -0
  134. package/dist/sinks/paths.js +32 -0
  135. package/dist/sinks/paths.js.map +1 -0
  136. package/dist/sinks/record.js +58 -0
  137. package/dist/sinks/record.js.map +1 -0
  138. package/dist/sinks/types.js +1 -0
  139. package/dist/sinks/types.js.map +1 -0
  140. package/dist/web/breakage.js +210 -0
  141. package/dist/web/breakage.js.map +1 -0
  142. package/dist/web/controls.js +102 -0
  143. package/dist/web/controls.js.map +1 -0
  144. package/dist/web/driver.js +803 -0
  145. package/dist/web/driver.js.map +1 -0
  146. package/dist/web/elements.js +151 -0
  147. package/dist/web/elements.js.map +1 -0
  148. package/dist/web/fingerprint.js +13 -0
  149. package/dist/web/fingerprint.js.map +1 -0
  150. package/dist/web/forms.js +74 -0
  151. package/dist/web/forms.js.map +1 -0
  152. package/dist/web/links.js +194 -0
  153. package/dist/web/links.js.map +1 -0
  154. package/dist/web/types.js +1 -0
  155. package/dist/web/types.js.map +1 -0
  156. package/package.json +80 -0
  157. package/runtime/macos-accessibility-runner.js +275 -0
  158. package/runtime/windows-uia-runner.ps1 +539 -0
@@ -0,0 +1,539 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [string]$Command
4
+ )
5
+
6
+ $ErrorActionPreference = "Stop"
7
+ $WarningPreference = "SilentlyContinue"
8
+
9
+ function Write-UiaJson {
10
+ param([hashtable]$Value)
11
+ $Value | ConvertTo-Json -Depth 80 -Compress
12
+ }
13
+
14
+ function Write-UiaError {
15
+ param([string]$Message)
16
+ Write-UiaJson @{ ok = $false; error = $Message.Substring(0, [Math]::Min($Message.Length, 500)) }
17
+ }
18
+
19
+ function Read-UiaPayload {
20
+ $raw = [Console]::In.ReadToEnd()
21
+ if ([string]::IsNullOrWhiteSpace($raw)) {
22
+ return @{}
23
+ }
24
+ return $raw | ConvertFrom-Json
25
+ }
26
+
27
+ function Import-UiaAssemblies {
28
+ Add-Type -AssemblyName UIAutomationClient
29
+ Add-Type -AssemblyName UIAutomationTypes
30
+ }
31
+
32
+ function Get-Target {
33
+ param($Payload)
34
+ if ($null -ne $Payload.target) {
35
+ return $Payload.target
36
+ }
37
+ return $Payload
38
+ }
39
+
40
+ function Get-ProcessNameFromTarget {
41
+ param($Target)
42
+ if ($Target.processName) {
43
+ return [System.IO.Path]::GetFileNameWithoutExtension([string]$Target.processName)
44
+ }
45
+ if ($Target.appPath) {
46
+ return [System.IO.Path]::GetFileNameWithoutExtension([string]$Target.appPath)
47
+ }
48
+ return $null
49
+ }
50
+
51
+ function Get-ProcessIdFromTarget {
52
+ param($Target)
53
+ if ($Target.processId) {
54
+ return [int]$Target.processId
55
+ }
56
+ return $null
57
+ }
58
+
59
+ function Get-WindowTitleFromTarget {
60
+ param($Target)
61
+ if ($Target.windowTitle) {
62
+ return [string]$Target.windowTitle
63
+ }
64
+ return $null
65
+ }
66
+
67
+ function Has-DeclaredTarget {
68
+ param($Target)
69
+ return $Target.appPath -or $Target.appId -or $Target.processName -or $Target.processId -or $Target.windowTitle
70
+ }
71
+
72
+ function Start-UiaTarget {
73
+ param($Target)
74
+ if ($Target.appPath) {
75
+ $process = Start-Process -FilePath ([string]$Target.appPath) -PassThru
76
+ Start-Sleep -Milliseconds 800
77
+ return [int]$process.Id
78
+ }
79
+ if ($Target.processId) {
80
+ return [int]$Target.processId
81
+ }
82
+ return $null
83
+ }
84
+
85
+ function Get-UiaRootWindow {
86
+ param($Target)
87
+ Import-UiaAssemblies
88
+ $processId = Get-ProcessIdFromTarget $Target
89
+ $processName = Get-ProcessNameFromTarget $Target
90
+ $windowTitle = Get-WindowTitleFromTarget $Target
91
+ $windows = [System.Windows.Automation.AutomationElement]::RootElement.FindAll(
92
+ [System.Windows.Automation.TreeScope]::Children,
93
+ [System.Windows.Automation.Condition]::TrueCondition
94
+ )
95
+
96
+ $matches = @()
97
+ foreach ($window in $windows) {
98
+ if ($null -eq $window) {
99
+ continue
100
+ }
101
+ try {
102
+ if ($processId -and [int]$window.Current.ProcessId -ne $processId) {
103
+ continue
104
+ }
105
+ if ($windowTitle -and [string]$window.Current.Name -ne $windowTitle) {
106
+ continue
107
+ }
108
+ if ($processName) {
109
+ $process = Get-Process -Id $window.Current.ProcessId -ErrorAction SilentlyContinue
110
+ if (-not $process -or $process.ProcessName -ine $processName) {
111
+ continue
112
+ }
113
+ }
114
+ $matches += $window
115
+ } catch {
116
+ }
117
+ }
118
+
119
+ if ($matches.Count -eq 1) {
120
+ return $matches[0]
121
+ }
122
+
123
+ if ($matches.Count -gt 1) {
124
+ throw "Windows UI Automation target matched multiple root windows; add --process-id or --window-title"
125
+ }
126
+
127
+ if (Has-DeclaredTarget $Target) {
128
+ throw "No UI Automation root window was found for declared target"
129
+ }
130
+
131
+ foreach ($window in $windows) {
132
+ if ($null -ne $window) {
133
+ return $window
134
+ }
135
+ }
136
+ throw "No UI Automation root window was found for target"
137
+ }
138
+
139
+ function Get-ControlTypeName {
140
+ param($Element)
141
+ $programmaticName = [string]$Element.Current.ControlType.ProgrammaticName
142
+ return $programmaticName -replace "^ControlType\.", ""
143
+ }
144
+
145
+ function Get-PatternNames {
146
+ param($Element)
147
+ $names = @()
148
+ foreach ($pattern in $Element.GetSupportedPatterns()) {
149
+ $name = [string]$pattern.ProgrammaticName
150
+ $name = $name -replace "^PatternInterface\.", ""
151
+ $name = $name -replace "PatternIdentifiers\.Pattern$", ""
152
+ $names += $name
153
+ }
154
+ return $names
155
+ }
156
+
157
+ function Get-RuntimeIdText {
158
+ param($Element)
159
+ try {
160
+ return ([string]::Join(".", $Element.GetRuntimeId()))
161
+ } catch {
162
+ return ""
163
+ }
164
+ }
165
+
166
+ function Get-UiaBounds {
167
+ param($Element)
168
+ $rect = $Element.Current.BoundingRectangle
169
+ $values = @([double]$rect.X, [double]$rect.Y, [double]$rect.Width, [double]$rect.Height)
170
+ foreach ($value in $values) {
171
+ if ([double]::IsNaN($value) -or [double]::IsInfinity($value)) {
172
+ return $null
173
+ }
174
+ }
175
+ return @{
176
+ x = [double]$rect.X
177
+ y = [double]$rect.Y
178
+ width = [double]$rect.Width
179
+ height = [double]$rect.Height
180
+ }
181
+ }
182
+
183
+ function Convert-UiaNode {
184
+ param(
185
+ $Element,
186
+ [int]$Depth,
187
+ [int]$MaxDepth,
188
+ [ref]$Count,
189
+ [int]$MaxNodes
190
+ )
191
+
192
+ $Count.Value += 1
193
+ if ($Depth -ge $MaxDepth -or $Count.Value -gt $MaxNodes) {
194
+ return @{
195
+ name = [string]$Element.Current.Name
196
+ automationId = [string]$Element.Current.AutomationId
197
+ controlType = Get-ControlTypeName $Element
198
+ className = [string]$Element.Current.ClassName
199
+ frameworkId = [string]$Element.Current.FrameworkId
200
+ runtimeId = Get-RuntimeIdText $Element
201
+ isEnabled = [bool]$Element.Current.IsEnabled
202
+ isOffscreen = [bool]$Element.Current.IsOffscreen
203
+ patterns = @(Get-PatternNames $Element)
204
+ children = @()
205
+ isOpaque = $true
206
+ }
207
+ }
208
+
209
+ $children = @()
210
+ $childElements = $Element.FindAll(
211
+ [System.Windows.Automation.TreeScope]::Children,
212
+ [System.Windows.Automation.Condition]::TrueCondition
213
+ )
214
+ foreach ($child in $childElements) {
215
+ if ($Count.Value -gt $MaxNodes) {
216
+ break
217
+ }
218
+ $children += Convert-UiaNode $child ($Depth + 1) $MaxDepth $Count $MaxNodes
219
+ }
220
+
221
+ $node = @{
222
+ name = [string]$Element.Current.Name
223
+ automationId = [string]$Element.Current.AutomationId
224
+ controlType = Get-ControlTypeName $Element
225
+ className = [string]$Element.Current.ClassName
226
+ frameworkId = [string]$Element.Current.FrameworkId
227
+ runtimeId = Get-RuntimeIdText $Element
228
+ isEnabled = [bool]$Element.Current.IsEnabled
229
+ isOffscreen = [bool]$Element.Current.IsOffscreen
230
+ patterns = @(Get-PatternNames $Element)
231
+ children = $children
232
+ }
233
+ $bounds = Get-UiaBounds $Element
234
+ if ($null -ne $bounds) {
235
+ $node.bounds = $bounds
236
+ }
237
+ return $node
238
+ }
239
+
240
+ function Get-UiaTree {
241
+ param($Target)
242
+ $window = Get-UiaRootWindow $Target
243
+ $count = [ref]0
244
+ $root = Convert-UiaNode $window 0 25 $count 2000
245
+ $processName = ""
246
+ $processId = [int]$window.Current.ProcessId
247
+ try {
248
+ $processName = (Get-Process -Id $processId -ErrorAction SilentlyContinue).ProcessName
249
+ } catch {
250
+ }
251
+ return @{
252
+ appPath = [string]$Target.appPath
253
+ processName = $processName
254
+ processId = $processId
255
+ windowTitle = [string]$window.Current.Name
256
+ root = $root
257
+ }
258
+ }
259
+
260
+ function Get-ForegroundProcess {
261
+ if (-not ("ComberForegroundWindow" -as [type])) {
262
+ Add-Type -TypeDefinition @"
263
+ using System;
264
+ using System.Runtime.InteropServices;
265
+ public static class ComberForegroundWindow {
266
+ [DllImport("user32.dll")]
267
+ public static extern IntPtr GetForegroundWindow();
268
+ [DllImport("user32.dll")]
269
+ public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
270
+ }
271
+ "@
272
+ }
273
+
274
+ $handle = [ComberForegroundWindow]::GetForegroundWindow()
275
+ if ($handle -eq [IntPtr]::Zero) {
276
+ return @{ processName = ""; processId = 0 }
277
+ }
278
+ $processId = [uint32]0
279
+ [ComberForegroundWindow]::GetWindowThreadProcessId($handle, [ref]$processId) | Out-Null
280
+ $processName = ""
281
+ try {
282
+ $processName = (Get-Process -Id ([int]$processId) -ErrorAction SilentlyContinue).ProcessName
283
+ } catch {
284
+ }
285
+ return @{ processName = $processName; processId = [int]$processId }
286
+ }
287
+
288
+ function Import-WindowInterop {
289
+ if (-not ("ComberWindowInterop" -as [type])) {
290
+ Add-Type -TypeDefinition @"
291
+ using System;
292
+ using System.Runtime.InteropServices;
293
+ public static class ComberWindowInterop {
294
+ [DllImport("user32.dll")]
295
+ public static extern bool SetForegroundWindow(IntPtr hWnd);
296
+ [DllImport("user32.dll")]
297
+ public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
298
+ }
299
+ "@
300
+ }
301
+ }
302
+
303
+ function Copy-TargetWithProcessId {
304
+ param($Target, $ProcessId)
305
+ if (-not $ProcessId) {
306
+ return $Target
307
+ }
308
+ $copy = [ordered]@{}
309
+ foreach ($property in $Target.PSObject.Properties) {
310
+ $copy[$property.Name] = $property.Value
311
+ }
312
+ $copy["processId"] = [int]$ProcessId
313
+ return [pscustomobject]$copy
314
+ }
315
+
316
+ function Set-UiaRootWindowForeground {
317
+ param($Target)
318
+ Import-WindowInterop
319
+ $window = Get-UiaRootWindow $Target
320
+ $handle = [IntPtr]$window.Current.NativeWindowHandle
321
+ if ($handle -eq [IntPtr]::Zero) {
322
+ return
323
+ }
324
+ [ComberWindowInterop]::ShowWindow($handle, 5) | Out-Null
325
+ [ComberWindowInterop]::SetForegroundWindow($handle) | Out-Null
326
+ }
327
+
328
+ function Find-UiaByPath {
329
+ param($Root, [string]$Path)
330
+ $current = $Root
331
+ $parts = $Path -split "/"
332
+ for ($i = 1; $i -lt $parts.Length; $i++) {
333
+ $children = $current.FindAll(
334
+ [System.Windows.Automation.TreeScope]::Children,
335
+ [System.Windows.Automation.Condition]::TrueCondition
336
+ )
337
+ $index = [int]$parts[$i]
338
+ if ($index -lt 0 -or $index -ge $children.Count) {
339
+ throw "UIA path segment out of range"
340
+ }
341
+ $current = $children.Item($index)
342
+ }
343
+ return $current
344
+ }
345
+
346
+ function Find-UiaElement {
347
+ param($Target, $Selector)
348
+ $root = Get-UiaRootWindow $Target
349
+ if ($Selector.automationId) {
350
+ $condition = New-Object System.Windows.Automation.PropertyCondition(
351
+ [System.Windows.Automation.AutomationElement]::AutomationIdProperty,
352
+ [string]$Selector.automationId
353
+ )
354
+ $element = $root.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $condition)
355
+ if ($null -ne $element) {
356
+ return $element
357
+ }
358
+ }
359
+ if ($Selector.path) {
360
+ return Find-UiaByPath $root ([string]$Selector.path)
361
+ }
362
+ throw "UIA element selector did not match an element"
363
+ }
364
+
365
+ function Import-MouseInterop {
366
+ if (-not ("ComberMouseInput" -as [type])) {
367
+ Add-Type -TypeDefinition @"
368
+ using System;
369
+ using System.Runtime.InteropServices;
370
+ public static class ComberMouseInput {
371
+ [DllImport("user32.dll")]
372
+ public static extern bool SetCursorPos(int x, int y);
373
+ [DllImport("user32.dll")]
374
+ public static extern void mouse_event(uint flags, uint dx, uint dy, uint data, UIntPtr extraInfo);
375
+ }
376
+ "@
377
+ }
378
+ }
379
+
380
+ function Invoke-UiaMouseClick {
381
+ param($Element)
382
+ Import-MouseInterop
383
+ $rect = $Element.Current.BoundingRectangle
384
+ if ($rect.Width -le 0 -or $rect.Height -le 0) {
385
+ throw "UIA element has no clickable bounds"
386
+ }
387
+ try {
388
+ $Element.SetFocus()
389
+ } catch {
390
+ }
391
+ $x = [int]($rect.X + ($rect.Width / 2))
392
+ $y = [int]($rect.Y + ($rect.Height / 2))
393
+ [ComberMouseInput]::SetCursorPos($x, $y) | Out-Null
394
+ [ComberMouseInput]::mouse_event(0x0002, 0, 0, 0, [UIntPtr]::Zero)
395
+ [ComberMouseInput]::mouse_event(0x0004, 0, 0, 0, [UIntPtr]::Zero)
396
+ Start-Sleep -Milliseconds 120
397
+ }
398
+
399
+ function Invoke-UiaDefaultAction {
400
+ param($Element)
401
+ try {
402
+ $pattern = $Element.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern)
403
+ $pattern.Invoke()
404
+ return
405
+ } catch {
406
+ }
407
+ try {
408
+ $pattern = $Element.GetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern)
409
+ $pattern.DoDefaultAction()
410
+ return
411
+ } catch {
412
+ }
413
+ Invoke-UiaMouseClick $Element
414
+ }
415
+
416
+ function Set-UiaValue {
417
+ param($Element, [string]$Value)
418
+ try {
419
+ $pattern = $Element.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern)
420
+ $pattern.SetValue($Value)
421
+ return
422
+ } catch {
423
+ }
424
+ try {
425
+ $pattern = $Element.GetCurrentPattern([System.Windows.Automation.LegacyIAccessiblePattern]::Pattern)
426
+ $pattern.SetValue($Value)
427
+ return
428
+ } catch {
429
+ }
430
+ Add-Type -AssemblyName System.Windows.Forms
431
+ try {
432
+ $Element.SetFocus()
433
+ } catch {
434
+ }
435
+ [System.Windows.Forms.SendKeys]::SendWait("^a")
436
+ [System.Windows.Forms.SendKeys]::SendWait($Value)
437
+ }
438
+
439
+ function Invoke-UiaAction {
440
+ param($Target, $Action)
441
+ $element = Find-UiaElement $Target $Action.selector
442
+ switch ([string]$Action.kind) {
443
+ "invoke" {
444
+ Invoke-UiaDefaultAction $element
445
+ }
446
+ "toggle" {
447
+ $pattern = $element.GetCurrentPattern([System.Windows.Automation.TogglePattern]::Pattern)
448
+ $pattern.Toggle()
449
+ }
450
+ "set-value" {
451
+ Set-UiaValue $element ([string]$Action.value)
452
+ }
453
+ "select" {
454
+ $pattern = $element.GetCurrentPattern([System.Windows.Automation.SelectionItemPattern]::Pattern)
455
+ $pattern.Select()
456
+ }
457
+ "expand-collapse" {
458
+ $pattern = $element.GetCurrentPattern([System.Windows.Automation.ExpandCollapsePattern]::Pattern)
459
+ if ($pattern.Current.ExpandCollapseState -eq [System.Windows.Automation.ExpandCollapseState]::Expanded) {
460
+ $pattern.Collapse()
461
+ } else {
462
+ $pattern.Expand()
463
+ }
464
+ }
465
+ "range" {
466
+ $pattern = $element.GetCurrentPattern([System.Windows.Automation.RangeValuePattern]::Pattern)
467
+ $delta = 1
468
+ if ($Action.delta) {
469
+ $delta = [double]$Action.delta
470
+ }
471
+ $next = [Math]::Min($pattern.Current.Maximum, [Math]::Max($pattern.Current.Minimum, $pattern.Current.Value + $delta))
472
+ $pattern.SetValue($next)
473
+ }
474
+ default {
475
+ throw "Unsupported UIA action kind"
476
+ }
477
+ }
478
+ }
479
+
480
+ try {
481
+ $payload = Read-UiaPayload
482
+ $target = Get-Target $payload
483
+ switch ($Command) {
484
+ "launch" {
485
+ $processId = Start-UiaTarget $target
486
+ try {
487
+ Set-UiaRootWindowForeground (Copy-TargetWithProcessId $target $processId)
488
+ } catch {
489
+ }
490
+ if ($processId) {
491
+ Write-UiaJson @{ ok = $true; processId = $processId }
492
+ } else {
493
+ Write-UiaJson @{ ok = $true }
494
+ }
495
+ }
496
+ "snapshot" {
497
+ Write-UiaJson @{ ok = $true; tree = Get-UiaTree $target }
498
+ }
499
+ "foreground" {
500
+ $foreground = Get-ForegroundProcess
501
+ Write-UiaJson @{ ok = $true; processName = $foreground.processName; processId = $foreground.processId }
502
+ }
503
+ "close" {
504
+ if ($payload.launchedByComber -and $target.processId) {
505
+ Stop-Process -Id ([int]$target.processId) -Force -ErrorAction SilentlyContinue
506
+ }
507
+ Write-UiaJson @{ ok = $true }
508
+ }
509
+ "invoke" {
510
+ Invoke-UiaAction $target $payload.action
511
+ Write-UiaJson @{ ok = $true }
512
+ }
513
+ "toggle" {
514
+ Invoke-UiaAction $target $payload.action
515
+ Write-UiaJson @{ ok = $true }
516
+ }
517
+ "set-value" {
518
+ Invoke-UiaAction $target $payload.action
519
+ Write-UiaJson @{ ok = $true }
520
+ }
521
+ "select" {
522
+ Invoke-UiaAction $target $payload.action
523
+ Write-UiaJson @{ ok = $true }
524
+ }
525
+ "expand-collapse" {
526
+ Invoke-UiaAction $target $payload.action
527
+ Write-UiaJson @{ ok = $true }
528
+ }
529
+ "range" {
530
+ Invoke-UiaAction $target $payload.action
531
+ Write-UiaJson @{ ok = $true }
532
+ }
533
+ default {
534
+ Write-UiaError "Unsupported UIA command"
535
+ }
536
+ }
537
+ } catch {
538
+ Write-UiaError $_.Exception.Message
539
+ }