vdi-cloud/tools/run-cursor-poc-host.py

74 lines
3.6 KiB
Python

"""Scoped VM100 interactive cursor exporter. No new network listener or system cursor theme."""
import argparse
import base64
import json
from pathlib import Path
import runpy
import sys
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=["start", "status", "export", "stop"])
parser.add_argument("--output", type=Path)
args = parser.parse_args()
root = Path(__file__).resolve().parent
t = runpy.run_path(str(root / "pve_term_exec.py"))
v = runpy.run_path(str(root / "pve_vm.py"))
ticket, csrf = t["login"]()
if v["api"]("/api2/json/nodes/starhe/qemu/100/status/current", ticket=ticket).get("status") != "running":
sys.exit("VM100 is not running")
command = r"$ErrorActionPreference='Stop'; $folder='C:\ProgramData\VDI\cursor-poc-20261103'; $task='VDICursorPOC'; "
if args.action == "start":
payload = base64.b64encode((root / "cursor-poc-host.ps1").read_bytes()).decode()
command += r"""
if(Get-ScheduledTask -TaskName $task -ErrorAction SilentlyContinue) { throw 'Probe already exists' }
$user=(Get-CimInstance Win32_ComputerSystem).UserName
if(-not $user) { throw 'No interactive user' }
New-Item -ItemType Directory -Force $folder | Out-Null
$acl=Get-Acl $folder
$acl.SetAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($user,'Modify','ContainerInherit,ObjectInherit','None','Allow')))
Set-Acl $folder $acl
Remove-Item "$folder\error.txt","$folder\state.json","$folder\shapes.json" -ErrorAction SilentlyContinue
[IO.File]::WriteAllBytes("$folder\probe.ps1",[Convert]::FromBase64String('__PAYLOAD__'))
$a=New-ScheduledTaskAction -Execute powershell.exe -Argument "-NoProfile -STA -ExecutionPolicy Bypass -WindowStyle Hidden -File $folder\probe.ps1"
$p=New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Limited
$s=New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 9)
Register-ScheduledTask -TaskName $task -Action $a -Principal $p -Settings $s | Out-Null
Start-ScheduledTask -TaskName $task
Start-Sleep -Seconds 3
if(Test-Path "$folder\error.txt") { throw (Get-Content "$folder\error.txt" -Raw) }
if(-not(Test-Path "$folder\shapes.json")) { throw 'No fresh cursor export' }
'Cursor probe started'
""".replace("__PAYLOAD__", payload)
elif args.action == "export":
command += 'Get-Content "$folder\\shapes.json" -Raw'
elif args.action == "status":
command += r"""
Get-Content "$folder\state.json" -Raw
Get-Content "$folder\clicks.csv"
Get-Content "$folder\states.csv"
Get-Content "$folder\error.txt" -ErrorAction SilentlyContinue
"""
else:
command += r"""
Stop-ScheduledTask -TaskName $task -ErrorAction SilentlyContinue
Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue
if(Test-Path "$folder\pid.txt") {
$probePid=[int](Get-Content "$folder\pid.txt")
$process=Get-CimInstance Win32_Process -Filter "ProcessId=$probePid"
if($process.CommandLine -like '*cursor-poc-20261103*') { Stop-Process -Id $probePid -ErrorAction SilentlyContinue }
}
'Cursor probe stopped'
"""
out, error = v["agent"](ticket, csrf, ["powershell.exe", "-NoProfile", "-Command", command])
if error or not out or out == "TIMEOUT":
sys.exit(error or out or "Empty guest-agent result")
if args.action == "export":
data = json.loads(out.lstrip("\ufeff"))
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(data, separators=(",", ":")), encoding="utf-8")
print(json.dumps([{k: s[k] for k in ("name", "width", "height", "hotX", "hotY", "monochrome", "invertedPixels")} for s in data["shapes"]]))
else:
print(out)