Xcelerate.psm1
|
#Requires -Version 7.0 <# .SYNOPSIS PowerShell bindings for the xcelerate CDP browser-automation engine. .DESCRIPTION PowerShell has no UniFFI generator, so this module drives the generated .NET SDK (`uniffi.xcelerate`) directly. The managed assembly and the native cdylib are staged side by side by `scripts/generate_powershell_bindings.py`. Every engine method is `async` and returns a .NET `Task`. The wrapper functions resolve tasks synchronously so they compose naturally in a script; `Receive-XcelerateTask` unwraps any `Task` you obtain from the raw .NET objects, e.g. `Receive-XcelerateTask $page.Title()`. #> Set-StrictMode -Version Latest $script:XcelerateModuleRoot = $PSScriptRoot $script:XcelerateLoaded = $false $script:XcelerateLoadError = $null function Import-XcelerateAssembly { <# .SYNOPSIS Loads the xcelerate .NET assembly and makes its native library resolvable. .DESCRIPTION Picks the managed assembly whose target framework matches the runtime hosting PowerShell (net8.0 / net9.0 / net10.0) and preloads the native cdylib next to it, so the `[LibraryImport("xcelerate")]` calls resolve. Idempotent; safe to call before any other function. #> [CmdletBinding()] param() if ($script:XcelerateLoaded) { return } if ($script:XcelerateLoadError) { throw $script:XcelerateLoadError } $nativeName = if ($IsWindows) { 'xcelerate.dll' } elseif ($IsMacOS) { 'libxcelerate.dylib' } else { 'libxcelerate.so' } $runtimeMajor = [System.Environment]::Version.Major for ($major = $runtimeMajor; $major -ge 8; $major--) { $tfm = "net$major.0" $dir = Join-Path $script:XcelerateModuleRoot "lib/$tfm" $managed = Join-Path $dir 'Xcelerate.Net.dll' if (-not (Test-Path -LiteralPath $managed)) { continue } $native = Join-Path $dir $nativeName if (Test-Path -LiteralPath $native) { # Preloading pins the exact file so later name-based P/Invoke lookups # reuse it; PATH covers hosts that search the environment instead. [System.Runtime.InteropServices.NativeLibrary]::Load($native) | Out-Null } $env:PATH = "$dir$([IO.Path]::PathSeparator)$env:PATH" [System.Reflection.Assembly]::LoadFrom($managed) | Out-Null $script:XcelerateLoaded = $true Write-Verbose "Loaded xcelerate .NET binding from $managed" return } $message = "xcelerate binding not found under '$script:XcelerateModuleRoot/lib'. " + "Run 'python scripts/generate_powershell_bindings.py' to stage it." $script:XcelerateLoadError = $message throw $message } function Receive-XcelerateTask { <# .SYNOPSIS Blocks on a .NET Task and returns its result. .DESCRIPTION Unwraps the async methods exposed by the raw .NET objects, e.g. `Receive-XcelerateTask $page.Title()`. Non-Task input is passed through. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [object] $Task ) process { if ($Task -is [System.Threading.Tasks.Task]) { return $Task.GetAwaiter().GetResult() } return $Task } } function New-XcelerateConfig { <# .SYNOPSIS Builds a BrowserConfig with xcelerate's defaults (headless, detached). .EXAMPLE New-XcelerateConfig -Plugins stealth, human -NoHeadless #> [CmdletBinding()] [OutputType([object])] param( [switch] $NoHeadless, [switch] $NoDetached, [string] $ExecutablePath, [string[]] $Plugins ) Import-XcelerateAssembly return [uniffi.xcelerate.BrowserConfig]::new( [bool](-not $NoHeadless), [bool](-not $NoDetached), $ExecutablePath, $Plugins ) } function Start-XcelerateBrowser { <# .SYNOPSIS Launches a browser and returns the Browser object. .EXAMPLE $browser = Start-XcelerateBrowser -Plugins stealth -NoHeadless #> [CmdletBinding()] [OutputType([object])] param( [object] $Config, [switch] $NoHeadless, [switch] $NoDetached, [string] $ExecutablePath, [string[]] $Plugins ) Import-XcelerateAssembly if ($null -eq $Config) { $Config = New-XcelerateConfig -NoHeadless:$NoHeadless -NoDetached:$NoDetached ` -ExecutablePath $ExecutablePath -Plugins $Plugins } return [uniffi.xcelerate.Browser]::Launch($Config).GetAwaiter().GetResult() } function Stop-XcelerateBrowser { <# .SYNOPSIS Closes a browser launched by Start-XcelerateBrowser. #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [object] $Browser ) process { Import-XcelerateAssembly $null = $Browser.Close().GetAwaiter().GetResult() } } function New-XceleratePage { <# .SYNOPSIS Opens a new page in the browser and returns the Page object. .EXAMPLE $page = New-XceleratePage -Browser $browser -Url 'https://example.com' #> [CmdletBinding()] [OutputType([object])] param( [Parameter(Mandatory)] [object] $Browser, [Parameter(Mandatory, Position = 0)] [string] $Url ) Import-XcelerateAssembly return $Browser.NewPage($Url).GetAwaiter().GetResult() } function Get-XceleratePlugin { <# .SYNOPSIS Returns a handle to an enabled plugin. .EXAMPLE Receive-XcelerateTask (Get-XceleratePlugin -Browser $browser -Name human).Invoke('move', '{"x":320,"y":240}') #> [CmdletBinding()] [OutputType([object])] param( [Parameter(Mandatory)] [object] $Browser, [Parameter(Mandatory, Position = 0)] [string] $Name ) Import-XcelerateAssembly return $Browser.Plugin($Name) } # Best-effort: make the .NET types available as soon as the module is imported, # so callers can use `[uniffi.xcelerate.*]` directly without an explicit load. try { Import-XcelerateAssembly } catch { Write-Warning $_.Exception.Message } Export-ModuleMember -Function @( 'Import-XcelerateAssembly' 'Receive-XcelerateTask' 'New-XcelerateConfig' 'Start-XcelerateBrowser' 'Stop-XcelerateBrowser' 'New-XceleratePage' 'Get-XceleratePlugin' ) |