main
Drew Owens 0.9.27 source 4c5f7a0 9d ago
# local-sign-in.ps1 — the "local desktop assistant" for a 3DEXPERIENCE ID login window.
#
# RUN THIS ON THE PC THAT SHOWS THE LOGIN (double-click, or:
#   powershell -ExecutionPolicy Bypass -File local-sign-in.ps1 [-Hwnd <hwnd>] [-VaultHost iam.3ds.com]).
#
# It uses Adom Bridge's bundled local CLI, a trusted LOCAL caller: ab shows its own consent card for the
# vault reveal, the password is set straight into the window's password box (UIA, by automation id),
# "Remember me" is ticked, "Log in" is clicked, and the window is checked to be gone.
# The password is never printed, logged or sent anywhere; the variable is cleared right after the set.
#
# Why a script and not the bridge: ab hands a saved password only to a verified local caller — never to a
# bridge (origin "bridge") and never over the relay — and a cloud agent is not allowed to start this helper
# for you either (both measured 2026-10-12). adom-bridge issue #237 asks ab for a fill verb so that no
# helper is needed.
param(
  [int]$Hwnd = 0,
  [string]$VaultHost = 'iam.3ds.com',
  [int]$OwnerPid = 0
)
$ErrorActionPreference = 'Continue'
$cli = Join-Path $env:LOCALAPPDATA 'Adom Bridge\adom-bridge-cli.exe'
if (-not (Test-Path $cli)) { "Adom Bridge CLI not found at $cli"; exit 1 }

function Ab($verb, $obj) {
  $json = ($obj | ConvertTo-Json -Compress -Depth 6)
  $out = & $cli --ai-thread 'local sign-in' $verb $json 2>&1 | Out-String
  try { return ($out | ConvertFrom-Json) } catch { return [pscustomobject]@{ status = 'unparsed'; error = $out.Substring(0, [Math]::Min(300, $out.Length)) } }
}

# 0. the login window: by content, through the bridge's own census, unless given
if ($Hwnd -eq 0) {
  $st = Ab 'swx_status' @{ reason = 'find the 3DEXPERIENCE login window' }
  $w = $null
  try { $w = $st.result.solidworks.signInWindow } catch {}
  if (-not $w) { try { $w = $st.solidworks.signInWindow } catch {} }
  if ($w -and $w.hwnd) { $Hwnd = [int]$w.hwnd; if ($w.ownerPid) { $OwnerPid = [int]$w.ownerPid } }
  if ($Hwnd -eq 0) { "no 3DEXPERIENCE login window is open (swx_status.solidworks.signInWindow is empty); pass -Hwnd"; exit 1 }
}
"login window: hwnd=$Hwnd owner pid=$OwnerPid"

# 1. the user name, if the page is still on page 1 (prefilled on page 2 — then this set is refused read-only, which is fine)
$cred = Ab 'pup_credentials' @{}
$user = $null
foreach ($c in @($cred.credentials)) { if ($c.host -eq $VaultHost) { $user = $c.username } }
if ($user) {
  $u = Ab 'desktop_find_control' @{ hwnd = $Hwnd; role = 'Edit'; name = 'Email or username'; reason = 'is the user name box here' }
  if ($u.best) {
    $null = Ab 'desktop_ui_set' @{ hwnd = $Hwnd; automationId = 'username'; text = ''; reason = 'clear the user name box' }
    $s0 = Ab 'desktop_ui_set' @{ hwnd = $Hwnd; automationId = 'username'; text = $user; reason = 'set the saved user name' }
    "user name: status=$($s0.status) $($s0.error)"
    $cont = Ab 'desktop_find_control' @{ hwnd = $Hwnd; name = 'Continue'; reason = 'page 1?' }
    if ($cont.best) { $null = Ab 'desktop_ui_click' @{ hwnd = $Hwnd; name = 'Continue'; reason = 'go to the password page' }; Start-Sleep -Seconds 3 }
  }
}

# 2. the secret, from ab's vault, behind ab's own consent card (this script is a local caller)
$r = Ab 'pup_credential_reveal' @{ host = $VaultHost; reason = 'Sign in to the 3DEXPERIENCE window on this PC; the password is set into the login window by this local helper and is never printed, logged or sent anywhere' }
$pw = $null
foreach ($k in 'password', 'secret', 'value') { if ($r.$k) { $pw = [string]$r.$k; break } }
if (-not $pw -and $r.credential) { foreach ($k in 'password', 'secret') { if ($r.credential.$k) { $pw = [string]$r.credential.$k; break } } }
if (-not $pw -and $r.result) { foreach ($k in 'password', 'secret') { if ($r.result.$k) { $pw = [string]$r.result.$k; break } } }
if (-not $pw) { "reveal: no secret (status=$($r.status) errorCode=$($r.errorCode) error=$($r.error) granted=$($r.granted) pending=$($r.pending))"; exit 2 }
"reveal: ok"

# 3. the password box, by automation id; clear, then set
$null = Ab 'desktop_ui_set' @{ hwnd = $Hwnd; automationId = 'password'; text = ''; reason = 'clear the password box' }
$s = Ab 'desktop_ui_set' @{ hwnd = $Hwnd; automationId = 'password'; text = $pw; sensitive = $true; reason = 'set the saved password into the 3DEXPERIENCE login (local helper)' }
$pw = $null; $r = $null; [GC]::Collect()
"set password: status=$($s.status) $($s.error)"
if ($s.status -ne 'ok') { exit 3 }

# 4. Remember me (best effort: a real checkbox on the updater host, an off-screen one on the Visualize host), then Log in
$c = Ab 'desktop_find_control' @{ hwnd = $Hwnd; role = 'CheckBox'; contains = 'Remember me'; reason = 'find the Remember me checkbox' }
if ($c.best -and $c.best.name) {
  $t = Ab 'desktop_ui_click' @{ hwnd = $Hwnd; name = $c.best.name; reason = 'tick Remember me so the platform session persists' }
  "remember me: status=$($t.status) invoked=$($t.invoked)"
} else {
  $lbl = Ab 'desktop_find_control' @{ hwnd = $Hwnd; role = 'Text'; contains = 'Remember me'; reason = 'find the Remember me label' }
  if ($lbl.best -and $lbl.best.rect) { $t = Ab 'desktop_click' @{ x = $lbl.best.rect.centerX; y = $lbl.best.rect.centerY; reason = 'tick Remember me by its label' }; "remember me (label click): status=$($t.status)" } else { "remember me: not found" }
}
$l = Ab 'desktop_ui_click' @{ hwnd = $Hwnd; name = 'Log in'; reason = 'submit the 3DEXPERIENCE sign-in' }
"log in: status=$($l.status) invoked=$($l.invoked) $($l.error)"

# 5. verify by effect: the password box must be gone from this window
Start-Sleep -Seconds 10
$f = Ab 'desktop_find_control' @{ hwnd = $Hwnd; automationId = 'password'; reason = 'verify the login window is gone' }
$alive = $false
if ($OwnerPid -ne 0) { $alive = (Get-Process -Id $OwnerPid -ErrorAction SilentlyContinue) -ne $null }
"after 10 s: password box still found=$([bool]($f.best)) owner process alive=$alive"
if ($f.best) { "the window is still up: the password may have been rejected — it is NOT retried (lockout risk); sign in by hand this once"; exit 4 }
"signed in"