Install-ShopdbKiosk.ps1 lays the enforce client down once at bootstrap and never refreshes it. So a client change rode the code deploy to the server and then sat one directory away from where kiosks actually fetch, waiting for someone to re-stage the installer bundle by hand - which is how the new display-type reporting reached prod and changed nothing on any kiosk. The module now ships as a manifest entry like everything else in this scope: inline over HTTPS, Hash detection against the exact bytes shipped, written to the same path the installer uses so bootstrap and self-update cannot disagree. Ordered first, so a stale client refreshes before anything leans on it. The installer keeps its real job - a fresh kiosk still needs something that can talk to shopdb - it just stops being the update path. Self-modifying by design: this module is what stages payloads, but PowerShell loads it into memory at start, so rewriting the file mid-run is harmless and lands on the next cycle. Pilot a client change on ONE kiosk before the fleet: a broken module cannot fetch its own replacement, and on a share-less display that means a site visit.
910 lines
41 KiB
Python
910 lines
41 KiB
Python
"""Author the gea-shopfloor-display runtime scope programmatically.
|
|
|
|
Displays are Intune/Entra-joined, credential-less kiosk PCs that pull their
|
|
manifest over HTTPS (no SMB share). The kiosk engine and the kiosk browser are
|
|
BAKED INTO THE DISPLAY IMAGE, so this scope does not ship any EXE payloads; it
|
|
heals POLICY / CONFIG drift only, plus one dispatcher that points the kiosk at
|
|
the right target for the display subtype.
|
|
|
|
What this scope contains:
|
|
- Four Registry drift-heal entries that re-assert the Microsoft Edge kiosk
|
|
relaunch policies from the imaging script 09-Setup-Display.ps1 (so a display
|
|
that loses those policies self-heals on the next enforce cycle without a
|
|
keyboard or mouse on site).
|
|
- One inline PS1 dispatcher that reads C:\\Enrollment\\display-type.txt and
|
|
launches the kiosk target for the subtype. The display-type -> target map is
|
|
a data-driven table (DISPLAY_TYPE_TARGETS) so the targets are easy to edit.
|
|
|
|
Self-sufficient: the display scope carries EVERYTHING a display enforces and
|
|
does NOT inherit the fleet-wide 'common' scope. Displays run the enforcer with
|
|
common-merge off (the client default), so common's SMB-backed fleet entries
|
|
never reach a share-less display. This keeps the display path simple and needs
|
|
no common-payload repackaging.
|
|
|
|
Authoring path mirrors how every other scope is created: build a manifest dict
|
|
and hand it to service.replace_scope_draft (the same call the import-share CLI
|
|
uses), then attach the inline dispatcher payload and optionally publish. Re-
|
|
running replace_scope_draft is an idempotent draft rebuild.
|
|
"""
|
|
|
|
import hashlib
|
|
import os
|
|
|
|
from shopdb.api import db
|
|
|
|
from . import service
|
|
|
|
|
|
SCOPE_NAME = 'gea-shopfloor-display'
|
|
SCOPE_PHASE = 'runtime'
|
|
SCOPE_VERSION = '2.0'
|
|
|
|
# Edge kiosk relaunch policy key. Values below mirror 09-Setup-Display.ps1
|
|
# exactly so the imaging-time state and the enforced state never disagree.
|
|
EDGE_POLICY_PATH = 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge'
|
|
RELAUNCH_WINDOW_JSON = (
|
|
'{"entries":[{"start":{"hour":2,"minute":0},"duration_mins":120}]}')
|
|
|
|
# Data-driven display-type -> kiosk target map. The value of
|
|
# C:\Enrollment\display-type.txt selects the row; the target is a route the
|
|
# kiosk browser opens against the local kiosk base URL. Edit here to retarget a
|
|
# subtype. Keys are matched case-insensitively by the dispatcher.
|
|
#
|
|
# TODO-confirm: 3DPrintRoom points at the printedparts /parts-kiosk route as a
|
|
# PLACEHOLDER. Confirm the real 3D-print-room kiosk target with the floor team
|
|
# before this scope is published to production displays.
|
|
DISPLAY_TYPE_TARGETS = {
|
|
'Dashboard': '/shopfloor',
|
|
'Lobby': '/tv',
|
|
'3DPrintRoom': '/parts-kiosk',
|
|
}
|
|
|
|
DISPATCHER_FILENAME = 'Invoke-DisplayKioskDispatch.ps1'
|
|
|
|
# The enforce client module itself, delivered as an enforced entry so a client
|
|
# change no longer means hands on every kiosk. Path matches where
|
|
# Install-ShopdbKiosk.ps1 puts it, so the installer stays the BOOTSTRAP and this
|
|
# becomes the update path.
|
|
CLIENT_MODULE_FILENAME = 'ShopdbEnforceClient.psm1'
|
|
CLIENT_MODULE_DEST = r'C:\ProgramData\GE-Enforce\ShopdbEnforceClient.psm1'
|
|
|
|
# One-shot: apply a pending Edge update and bounce the kiosk browser. The marker
|
|
# path carries a DATE. That is the whole re-arm mechanism: change the date and
|
|
# every display runs it once more; leave it alone and each runs it exactly once.
|
|
FORCE_EDGE_UPDATE_FILENAME = 'Invoke-EdgeForceUpdate.ps1'
|
|
FORCE_EDGE_UPDATE_MARKER = (
|
|
r'C:\ProgramData\ShopDB\markers\edge-force-update-2026-08-12.done')
|
|
|
|
|
|
def _relaunch_window_targets_comment():
|
|
"""Human note that lists the data-driven targets, for the manifest comment."""
|
|
pairs = ', '.join(f'{name}={route}'
|
|
for name, route in DISPLAY_TYPE_TARGETS.items())
|
|
return pairs
|
|
|
|
|
|
def build_dispatcher_script():
|
|
"""Return the inline dispatcher PowerShell as text.
|
|
|
|
The display-type -> target map is emitted as a hashtable at the top of the
|
|
script (generated from DISPLAY_TYPE_TARGETS) so the on-PC script and the
|
|
manifest metadata agree and both stay easy to edit.
|
|
"""
|
|
table_lines = []
|
|
for display_type, route in DISPLAY_TYPE_TARGETS.items():
|
|
table_lines.append(f" '{display_type}' = '{route}'")
|
|
table_body = ';\n'.join(table_lines)
|
|
|
|
return f"""# Invoke-DisplayKioskDispatch.ps1 -- gea-shopfloor-display dispatcher.
|
|
#
|
|
# Reads C:\\Enrollment\\display-type.txt and ensures an all-users Startup
|
|
# shortcut that launches Edge in kiosk fullscreen at the route mapped for that
|
|
# subtype. The kiosk browser is baked into the display image; this only points
|
|
# the launch at the right target and heals the shortcut on a URL/type change.
|
|
#
|
|
# WHY a Startup shortcut, not Start-Process: this runs as SYSTEM under the
|
|
# enforce task. SYSTEM has no interactive desktop, so Start-Process msedge.exe
|
|
# opens INVISIBLY in session 0. A shortcut in the all-users Startup folder is
|
|
# instead launched by the auto-login user at logon, in their own visible
|
|
# session. Idempotent: prior ShopDB kiosk shortcuts are cleared first, then the
|
|
# one for the resolved target is written, so a display-type/URL change self-heals.
|
|
#
|
|
# The DisplayTypeTargets table below is the single source of truth for the
|
|
# subtype -> route map. Edit a row to retarget a subtype.
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
# --- Data-driven subtype -> kiosk route map ---
|
|
$DisplayTypeTargets = @{{
|
|
{table_body}
|
|
}}
|
|
|
|
# Base URL the kiosk browser opens; the route from the table is appended. Read
|
|
# from HKLM (the value the GE-Enforce client is configured with) so it stays
|
|
# site-agnostic; falls back to the West Jefferson shopdb host.
|
|
$KioskBaseUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb'
|
|
try {{
|
|
$shopdbConfig = Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\GE\\ShopDB' -Name BaseUrl -ErrorAction Stop
|
|
if ($shopdbConfig.BaseUrl) {{ $KioskBaseUrl = ([string]$shopdbConfig.BaseUrl).TrimEnd('/') }}
|
|
}} catch {{}}
|
|
|
|
# PREFERRED: resolve this display's role from the server by its FQDN, so a change
|
|
# in Settings > Dashboard Defaults takes effect with no reimage / local edit. The
|
|
# FQDN is F<BIOS serial>.<domain> (GE device naming); domain from HKLM
|
|
# DisplayFqdnDomain or the default. display-role is a PUBLIC endpoint (no token).
|
|
$path = $null
|
|
$serial = ''
|
|
try {{ $serial = ([string](Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop).SerialNumber).Trim() }} catch {{}}
|
|
$fqdnDomain = 'device.geaerospace.net'
|
|
try {{
|
|
$ddom = Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\GE\\ShopDB' -Name DisplayFqdnDomain -ErrorAction Stop
|
|
if ($ddom.DisplayFqdnDomain) {{ $fqdnDomain = ([string]$ddom.DisplayFqdnDomain).Trim().Trim('.') }}
|
|
}} catch {{}}
|
|
if ($serial) {{
|
|
$fqdn = ('F' + $serial + '.' + $fqdnDomain).ToLower()
|
|
try {{
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
$roleUri = "$KioskBaseUrl/api/dashboarddefaults/display-role?fqdn=$([uri]::EscapeDataString($fqdn))"
|
|
$resp = Invoke-RestMethod -Uri $roleUri -TimeoutSec 20 -ErrorAction Stop
|
|
if ($resp -and $resp.data -and $resp.data.path) {{
|
|
$path = [string]$resp.data.path
|
|
Write-Host "server role for $fqdn -> $path"
|
|
}}
|
|
}} catch {{ Write-Host "display-role lookup failed for $fqdn : $($_.Exception.Message)" }}
|
|
}}
|
|
|
|
# FALLBACK: the local display-type.txt map (offline, or no server mapping yet).
|
|
if (-not $path) {{
|
|
$displayTypeFile = 'C:\\Enrollment\\display-type.txt'
|
|
if (Test-Path -LiteralPath $displayTypeFile) {{
|
|
$displayType = (Get-Content -LiteralPath $displayTypeFile -Raw).Trim()
|
|
if (-not [string]::IsNullOrWhiteSpace($displayType)) {{
|
|
$matchedKey = $DisplayTypeTargets.Keys | Where-Object {{ $_ -ieq $displayType }} | Select-Object -First 1
|
|
if ($matchedKey) {{ $path = [string]$DisplayTypeTargets[$matchedKey]; Write-Host "local display-type '$displayType' -> $path" }}
|
|
}}
|
|
}}
|
|
}}
|
|
if (-not $path) {{
|
|
Write-Host 'No display role from server or display-type.txt; nothing to configure.'
|
|
return
|
|
}}
|
|
|
|
$kioskUrl = "$KioskBaseUrl$path"
|
|
|
|
# Resolve msedge.exe (baked into the display image).
|
|
$edge = $null
|
|
foreach ($p in @("${{env:ProgramFiles(x86)}}\\Microsoft\\Edge\\Application\\msedge.exe",
|
|
"$env:ProgramFiles\\Microsoft\\Edge\\Application\\msedge.exe")) {{
|
|
if (Test-Path -LiteralPath $p) {{ $edge = $p; break }}
|
|
}}
|
|
if (-not $edge) {{ Write-Host 'msedge.exe not found; cannot write kiosk startup shortcut.'; return }}
|
|
|
|
$startup = Join-Path $env:ProgramData 'Microsoft\\Windows\\Start Menu\\Programs\\Startup'
|
|
if (-not (Test-Path $startup)) {{ New-Item -ItemType Directory -Path $startup -Force | Out-Null }}
|
|
$shell = New-Object -ComObject WScript.Shell
|
|
|
|
# Clear leftover kiosk launchers from THIS or PRIOR installs so two kiosks do
|
|
# not fight: our own 'ShopDB Kiosk*.lnk', ANY .lnk that launches Edge in --kiosk
|
|
# mode, and any .url pointing at a shopdb kiosk page. Unrelated Startup items are
|
|
# left alone.
|
|
Get-ChildItem -LiteralPath $startup -Filter '*.lnk' -ErrorAction SilentlyContinue | ForEach-Object {{
|
|
$drop = $false
|
|
if ($_.Name -like 'ShopDB Kiosk*' -or
|
|
$_.Name -like 'GE Aerospace Dashboard*' -or
|
|
$_.Name -like 'GE Aerospace Lobby*') {{ $drop = $true }}
|
|
else {{
|
|
try {{
|
|
$existing = $shell.CreateShortcut($_.FullName)
|
|
$isBrowser = $existing.TargetPath -match '(?i)(msedge|chrome)\\.exe$'
|
|
# -kiosk matches both '-kiosk' and '--kiosk'. Also catch any browser
|
|
# shortcut pointing at a shopdb kiosk URL (incl. the dead
|
|
# shopfloor-dashboard route + --app variants).
|
|
$kioskArgs = ($existing.Arguments -match '(?i)-kiosk') -or `
|
|
($existing.Arguments -match '(?i)tsgwp00525|/shopdb/|shopfloor-dashboard')
|
|
if ($isBrowser -and $kioskArgs) {{ $drop = $true }}
|
|
}} catch {{}}
|
|
}}
|
|
if ($drop) {{ Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue }}
|
|
}}
|
|
Get-ChildItem -LiteralPath $startup -Filter '*.url' -ErrorAction SilentlyContinue | ForEach-Object {{
|
|
try {{
|
|
$body = Get-Content -LiteralPath $_.FullName -Raw -ErrorAction Stop
|
|
if ($body -match '/shopdb/(tv|shopfloor|parts-kiosk)' -or $body -match 'shopfloor-dashboard') {{
|
|
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue
|
|
}}
|
|
}} catch {{}}
|
|
}}
|
|
|
|
# Legacy autostarts the OLD Dashboard/Lobby Inno installers planted keep
|
|
# relaunching the dead old URL (-> 404 white screen) and BEAT our shortcut. They
|
|
# hide in several places: the 32-bit Inno installer's HKLM Run write is
|
|
# WOW64-redirected into Wow6432Node (invisible to 64-bit tooling), and copies
|
|
# can sit in per-user hives, RunOnce, or the policy Run key. Sweep ALL of them
|
|
# by legacy NAME and by VALUE (any Run entry whose data points at the old URLs),
|
|
# across the native + Wow6432Node views and every loaded user hive, plus every
|
|
# per-user + common Startup folder.
|
|
$legacyNames = @('GE Aerospace Dashboard','GE Aerospace Lobby Display')
|
|
$oldUrlPattern = 'tv-dashboard|shopfloor-dashboard'
|
|
|
|
$regRoots = @('HKLM:\\SOFTWARE', 'HKLM:\\SOFTWARE\\Wow6432Node')
|
|
if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {{
|
|
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -ErrorAction SilentlyContinue | Out-Null
|
|
}}
|
|
try {{
|
|
Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction Stop |
|
|
Where-Object {{ $_.PSChildName -notmatch '_Classes$' }} |
|
|
ForEach-Object {{
|
|
$regRoots += "HKU:\\$($_.PSChildName)\\SOFTWARE"
|
|
$regRoots += "HKU:\\$($_.PSChildName)\\SOFTWARE\\Wow6432Node"
|
|
}}
|
|
}} catch {{}}
|
|
|
|
$runSubkeys = @(
|
|
'Microsoft\\Windows\\CurrentVersion\\Run',
|
|
'Microsoft\\Windows\\CurrentVersion\\RunOnce',
|
|
'Microsoft\\Windows\\CurrentVersion\\Policies\\Explorer\\Run'
|
|
)
|
|
foreach ($root in $regRoots) {{
|
|
foreach ($sub in $runSubkeys) {{
|
|
$key = Join-Path $root $sub
|
|
if (-not (Test-Path -LiteralPath $key)) {{ continue }}
|
|
$props = Get-ItemProperty -LiteralPath $key -ErrorAction SilentlyContinue
|
|
if (-not $props) {{ continue }}
|
|
foreach ($prop in $props.PSObject.Properties) {{
|
|
if ($prop.Name -like 'PS*') {{ continue }}
|
|
if (($legacyNames -contains $prop.Name) -or ("$($prop.Value)" -match $oldUrlPattern)) {{
|
|
Remove-ItemProperty -LiteralPath $key -Name $prop.Name -Force -ErrorAction SilentlyContinue
|
|
Write-Host "removed legacy autostart $key -> $($prop.Name)"
|
|
}}
|
|
}}
|
|
}}
|
|
}}
|
|
|
|
$startupDirs = @($startup, (Join-Path $env:Public 'Desktop'))
|
|
Get-ChildItem 'C:\\Users' -Directory -ErrorAction SilentlyContinue | ForEach-Object {{
|
|
$startupDirs += (Join-Path $_.FullName 'AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup')
|
|
}}
|
|
foreach ($dir in ($startupDirs | Select-Object -Unique)) {{
|
|
if (-not (Test-Path -LiteralPath $dir)) {{ continue }}
|
|
Get-ChildItem -LiteralPath $dir -Filter '*.lnk' -ErrorAction SilentlyContinue | ForEach-Object {{
|
|
$drop = ($_.Name -like 'GE Aerospace *')
|
|
if (-not $drop) {{
|
|
try {{
|
|
$existing = $shell.CreateShortcut($_.FullName)
|
|
if ("$($existing.Arguments)" -match $oldUrlPattern) {{ $drop = $true }}
|
|
}} catch {{}}
|
|
}}
|
|
if ($drop) {{
|
|
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue
|
|
Write-Host "removed legacy startup shortcut $($_.FullName)"
|
|
}}
|
|
}}
|
|
}}
|
|
try {{
|
|
Get-CimInstance Win32_Process -Filter "Name = 'msedge.exe'" -ErrorAction SilentlyContinue |
|
|
Where-Object {{ $_.CommandLine -match 'tv-dashboard|shopfloor-dashboard' }} |
|
|
ForEach-Object {{ Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue; Write-Host "killed old-URL kiosk Edge pid $($_.ProcessId)" }}
|
|
}} catch {{}}
|
|
|
|
# remove a stale VBS launcher from an earlier build (kiosk now uses a direct
|
|
# Edge shortcut again). Harmless if absent.
|
|
$staleVbs = Join-Path (Join-Path $env:ProgramData 'ShopDB') 'Start-ShopdbKiosk.vbs'
|
|
if (Test-Path -LiteralPath $staleVbs) {{ Remove-Item -LiteralPath $staleVbs -Force -ErrorAction SilentlyContinue }}
|
|
|
|
$edgeArgs = "--kiosk `"$kioskUrl`" --edge-kiosk-type=fullscreen"
|
|
$lnkPath = Join-Path $startup 'ShopDB Kiosk.lnk'
|
|
$lnk = $shell.CreateShortcut($lnkPath)
|
|
$lnk.TargetPath = $edge
|
|
$lnk.Arguments = $edgeArgs
|
|
$lnk.WorkingDirectory = Split-Path -Parent $edge
|
|
$lnk.WindowStyle = 3
|
|
$lnk.Description = "ShopDB kiosk display: $kioskUrl"
|
|
$lnk.Save()
|
|
Write-Host "display-type '$displayType' -> kiosk Startup shortcut for $kioskUrl"
|
|
"""
|
|
|
|
|
|
WATCHDOG_FILENAME = 'Register-KioskWatchdog.ps1'
|
|
|
|
# Where the watchdog stages the script the scheduled task actually runs. The
|
|
# task must run in the INTERACTIVE session, so its action cannot be an inline
|
|
# payload executed by the SYSTEM enforce task.
|
|
WATCHDOG_TASKNAME = 'ShopDB Kiosk Watchdog'
|
|
WATCHDOG_SCRIPTPATH = r'C:\ProgramData\ShopDB\Watch-ShopdbKiosk.ps1'
|
|
WATCHDOG_INTERVAL_MIN = 2
|
|
|
|
|
|
def build_watchdog_script():
|
|
"""Return the inline watchdog-installer PowerShell as text.
|
|
|
|
WHY THIS EXISTS: the kiosk is launched by an all-users Startup shortcut,
|
|
which runs ONCE at logon. Nothing supervises the browser afterwards, so when
|
|
Edge exits - an update, a crash, someone closing the window - the display
|
|
stays dead until the next logon or reboot.
|
|
|
|
The Edge RelaunchNotification=2 policy was intended to cover the update
|
|
case, but it does not: that policy drives Edge's own update-restart flow,
|
|
which depends on session restore to come back to where it was. Kiosk mode
|
|
(--kiosk) restores no session and shows no notification UI, so Edge honours
|
|
the close and never the relaunch.
|
|
|
|
This entry runs as SYSTEM under the enforce task and cannot launch a visible
|
|
browser itself (session 0 has no desktop - the same reason the dispatcher
|
|
writes a Startup shortcut). So it REGISTERS a scheduled task that runs in
|
|
the interactive user's session and relaunches the kiosk when it is gone.
|
|
|
|
Idempotent: the staged script and the task are rewritten only when they
|
|
differ from what is wanted, so a steady-state cycle costs one file read.
|
|
"""
|
|
# The script the task runs. Kept as a separate literal so the outer f-string
|
|
# does not have to escape its braces.
|
|
watched = r'''# Watch-ShopdbKiosk.ps1 -- relaunch the kiosk browser if it has gone.
|
|
#
|
|
# Runs in the INTERACTIVE user session (see the watchdog task), every couple of
|
|
# minutes. Reads the same Startup shortcut the dispatcher writes, so the target
|
|
# URL has exactly one source of truth: retarget a display by editing the
|
|
# dispatcher, and this follows without change.
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
$logDir = 'C:\Logs\ShopDB'
|
|
$logFile = Join-Path $logDir 'kiosk-watchdog.log'
|
|
$stamp = Join-Path $env:TEMP 'shopdb-kiosk-lastlaunch.txt'
|
|
|
|
if (-not (Test-Path $logDir)) {
|
|
New-Item -ItemType Directory -Path $logDir -Force -EA SilentlyContinue | Out-Null
|
|
}
|
|
|
|
function Log {
|
|
param([string]$Message)
|
|
# Bounded: this runs every couple of minutes forever on a PC nobody logs
|
|
# into, so an unbounded log would be the only thing that ever fills the disk.
|
|
try {
|
|
if ((Test-Path $logFile) -and ((Get-Item $logFile).Length -gt 512KB)) {
|
|
$keep = Get-Content $logFile -Tail 200 -EA SilentlyContinue
|
|
Set-Content -Path $logFile -Value $keep -EA SilentlyContinue
|
|
}
|
|
} catch { }
|
|
Add-Content -Path $logFile -EA SilentlyContinue `
|
|
-Value ('[{0}] {1}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Message)
|
|
}
|
|
|
|
# REFUSE to run as SYSTEM. MainWindowHandle is session-scoped: a SYSTEM caller
|
|
# reads 0 for a perfectly healthy kiosk in the user's session, so this script
|
|
# would judge it dead, kill it and relaunch - every single cycle. The scheduled
|
|
# task uses an interactive Users principal precisely so this cannot happen, and
|
|
# this guard makes a mis-registered task fail loudly instead of thrashing the
|
|
# display.
|
|
if ([Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) {
|
|
Log 'refusing to run as SYSTEM: window state is not readable across sessions.'
|
|
return
|
|
}
|
|
|
|
# The kiosk shortcut is the source of truth for target + arguments.
|
|
$startup = Join-Path $env:ProgramData 'Microsoft\Windows\Start Menu\Programs\StartUp'
|
|
$lnkPath = Join-Path $startup 'ShopDB Kiosk.lnk'
|
|
if (-not (Test-Path -LiteralPath $lnkPath)) {
|
|
# Not a kiosk display, or the dispatcher has not run yet. Do NOT invent a
|
|
# browser launch here - that would put Edge on a PC that never asked for it.
|
|
return
|
|
}
|
|
|
|
try {
|
|
$shell = New-Object -ComObject WScript.Shell
|
|
$lnk = $shell.CreateShortcut($lnkPath)
|
|
$target = $lnk.TargetPath
|
|
$arguments = $lnk.Arguments
|
|
} catch {
|
|
Log "could not read $lnkPath : $_"
|
|
return
|
|
}
|
|
if (-not $target -or -not (Test-Path -LiteralPath $target)) {
|
|
Log "shortcut target missing: $target"
|
|
return
|
|
}
|
|
|
|
# A kiosk counts as UP only when a --kiosk process still owns a VISIBLE WINDOW.
|
|
#
|
|
# Two traps, and the obvious checks fall into one or the other. Testing "is
|
|
# msedge running" lets a stray renderer or crashpad child mask a dead kiosk.
|
|
# Testing the command line alone is what shipped first, and it is not enough
|
|
# either: after an Edge update the window can be gone while a --kiosk process
|
|
# lingers, so the watchdog saw "kiosk is up" and never relaunched - observed on
|
|
# a real display, Edge closed on the desktop but still listed in Task Manager.
|
|
#
|
|
# MainWindowHandle is the discriminator. A process with no window cannot be
|
|
# showing anything to the floor, whatever its command line says.
|
|
$kioskProcs = @()
|
|
try {
|
|
$kioskProcs = @(Get-CimInstance Win32_Process -Filter "Name = 'msedge.exe'" -EA SilentlyContinue |
|
|
Where-Object { $_.CommandLine -match '--kiosk' })
|
|
} catch {
|
|
Log "process query failed, assuming kiosk is up: $_"
|
|
return
|
|
}
|
|
|
|
$visible = @()
|
|
foreach ($p in $kioskProcs) {
|
|
$proc = Get-Process -Id $p.ProcessId -EA SilentlyContinue
|
|
if ($proc -and $proc.MainWindowHandle -ne 0) { $visible += $p }
|
|
}
|
|
if ($visible.Count -gt 0) { return }
|
|
|
|
# Windowless --kiosk processes are orphans. They must be killed BEFORE
|
|
# relaunching: leaving them would keep the next cycle's check satisfied again,
|
|
# and a second browser would fight the first for the display.
|
|
if ($kioskProcs.Count -gt 0) {
|
|
Log ("found {0} windowless --kiosk process(es) - killing before relaunch" -f $kioskProcs.Count)
|
|
foreach ($p in $kioskProcs) {
|
|
try {
|
|
Stop-Process -Id $p.ProcessId -Force -EA Stop
|
|
Log (" killed pid {0}" -f $p.ProcessId)
|
|
} catch {
|
|
Log (" could not kill pid {0}: {1}" -f $p.ProcessId, $_)
|
|
}
|
|
}
|
|
}
|
|
|
|
# Debounce. Edge takes a few seconds to present a --kiosk process, and a display
|
|
# that fails to start would otherwise get a new browser every couple of minutes.
|
|
try {
|
|
if (Test-Path $stamp) {
|
|
$last = Get-Content $stamp -First 1 -EA SilentlyContinue
|
|
if ($last) {
|
|
$age = ((Get-Date) - [datetime]$last).TotalSeconds
|
|
if ($age -lt 120) {
|
|
Log ('kiosk still absent {0:N0}s after a launch - not relaunching yet' -f $age)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
} catch { }
|
|
|
|
Log "kiosk browser not running - relaunching: $target $arguments"
|
|
try {
|
|
Set-Content -Path $stamp -Value (Get-Date -Format 'o') -EA SilentlyContinue
|
|
Start-Process -FilePath $target -ArgumentList $arguments -EA Stop
|
|
Log 'relaunched.'
|
|
} catch {
|
|
Log "relaunch FAILED: $_"
|
|
}
|
|
'''
|
|
|
|
return f"""# Register-KioskWatchdog.ps1 -- gea-shopfloor-display kiosk watchdog.
|
|
#
|
|
# Installs a scheduled task that relaunches the kiosk browser when it is gone.
|
|
# See the manifest comment for why the Startup shortcut alone is not enough.
|
|
#
|
|
# Runs as SYSTEM under the enforce task, so it registers the task rather than
|
|
# launching anything: SYSTEM has no interactive desktop.
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
$taskName = '{WATCHDOG_TASKNAME}'
|
|
$scriptPath = '{WATCHDOG_SCRIPTPATH}'
|
|
$intervalMin = {WATCHDOG_INTERVAL_MIN}
|
|
|
|
$wanted = @'
|
|
{watched}
|
|
'@
|
|
|
|
# --- stage the script the task runs --------------------------------------
|
|
$dir = Split-Path -Parent $scriptPath
|
|
if (-not (Test-Path $dir)) {{ New-Item -ItemType Directory -Path $dir -Force | Out-Null }}
|
|
|
|
$current = ''
|
|
if (Test-Path -LiteralPath $scriptPath) {{
|
|
$current = Get-Content -LiteralPath $scriptPath -Raw -EA SilentlyContinue
|
|
}}
|
|
# Compare trimmed: Set-Content appends a trailing newline that the here-string
|
|
# does not carry, so a raw comparison never matches and the file is rewritten
|
|
# every enforce cycle forever.
|
|
if ($current.TrimEnd() -ne $wanted.TrimEnd()) {{
|
|
Set-Content -LiteralPath $scriptPath -Value $wanted -Encoding UTF8 -Force
|
|
Write-Host "staged $scriptPath"
|
|
}} else {{
|
|
Write-Host "watchdog script already current"
|
|
}}
|
|
|
|
# --- register the task ----------------------------------------------------
|
|
# Principal is the INTERACTIVE users group, not SYSTEM: the relaunched browser
|
|
# has to appear on the auto-login user's visible desktop. A SYSTEM task would
|
|
# start Edge in session 0 where nobody can see it.
|
|
$existing = Get-ScheduledTask -TaskName $taskName -EA SilentlyContinue
|
|
if (-not $existing) {{
|
|
try {{
|
|
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
|
-Argument ('-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "{{0}}"' -f $scriptPath)
|
|
$atLogon = New-ScheduledTaskTrigger -AtLogOn
|
|
# -Once + -RepetitionInterval repeats indefinitely. Do NOT pass
|
|
# -RepetitionDuration [TimeSpan]::MaxValue: it serialises to an
|
|
# out-of-range Duration that Register-ScheduledTask rejects.
|
|
$repeat = New-ScheduledTaskTrigger -Once -At ((Get-Date).AddMinutes(1)) `
|
|
-RepetitionInterval (New-TimeSpan -Minutes $intervalMin)
|
|
$principal = New-ScheduledTaskPrincipal -GroupId 'S-1-5-32-545' -RunLevel Limited
|
|
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable `
|
|
-MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
|
-DontStopIfGoingOnBatteries -AllowStartIfOnBatteries
|
|
Register-ScheduledTask -TaskName $taskName -Action $action `
|
|
-Trigger @($atLogon, $repeat) -Principal $principal -Settings $settings `
|
|
-Force | Out-Null
|
|
Write-Host "registered '$taskName' (every $intervalMin min, interactive)"
|
|
}} catch {{
|
|
Write-Host "FAILED to register '$taskName': $_"
|
|
}}
|
|
}} else {{
|
|
Write-Host "'$taskName' already registered"
|
|
}}
|
|
"""
|
|
|
|
|
|
ALWAYSON_FILENAME = 'Set-DisplayAlwaysOn.ps1'
|
|
|
|
|
|
def build_alwayson_script():
|
|
"""Return the inline always-on PowerShell as text.
|
|
|
|
Keeps a display awake 24/7 (kiosk page always visible): powercfg never-off on
|
|
AC+DC, screensaver + auto-lock disabled. Plain raw string - no table
|
|
interpolation, so no brace escaping.
|
|
"""
|
|
return r'''# Set-DisplayAlwaysOn.ps1 -- gea-shopfloor-display always-on.
|
|
# Keeps the display awake 24/7 so the kiosk page stays visible: powercfg
|
|
# never-off (AC+DC) + screensaver/auto-lock disabled. SYSTEM, every cycle.
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
foreach ($c in @(
|
|
'monitor-timeout-ac 0','monitor-timeout-dc 0',
|
|
'standby-timeout-ac 0','standby-timeout-dc 0',
|
|
'hibernate-timeout-ac 0','hibernate-timeout-dc 0',
|
|
'disk-timeout-ac 0','disk-timeout-dc 0')) {
|
|
try { Start-Process -FilePath powercfg -ArgumentList "/change $c" -NoNewWindow -Wait -ErrorAction Stop } catch {}
|
|
}
|
|
|
|
# disable screensaver + secure lock for future profiles (.DEFAULT) + loaded hives.
|
|
if (-not (Get-PSDrive -Name HKU -ErrorAction SilentlyContinue)) {
|
|
New-PSDrive -Name HKU -PSProvider Registry -Root HKEY_USERS -ErrorAction SilentlyContinue | Out-Null
|
|
}
|
|
$hives = @('HKU:\.DEFAULT')
|
|
try {
|
|
Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction Stop |
|
|
Where-Object { $_.PSChildName -match '^S-1-5-21' -and $_.PSChildName -notmatch '_Classes$' } |
|
|
ForEach-Object { $hives += "HKU:\$($_.PSChildName)" }
|
|
} catch {}
|
|
foreach ($h in $hives) {
|
|
$cp = "$h\Control Panel\Desktop"
|
|
try {
|
|
if (-not (Test-Path $cp)) { New-Item -Path $cp -Force -ErrorAction Stop | Out-Null }
|
|
Set-ItemProperty -Path $cp -Name ScreenSaveActive -Value '0' -ErrorAction Stop
|
|
Set-ItemProperty -Path $cp -Name ScreenSaverIsSecure -Value '0' -ErrorAction SilentlyContinue
|
|
Set-ItemProperty -Path $cp -Name ScreenSaveTimeOut -Value '0' -ErrorAction SilentlyContinue
|
|
} catch {}
|
|
}
|
|
|
|
# never auto-lock the kiosk console.
|
|
try {
|
|
$pol = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Personalization'
|
|
if (-not (Test-Path $pol)) { New-Item -Path $pol -Force | Out-Null }
|
|
Set-ItemProperty -Path $pol -Name NoLockScreen -Value 1 -Type DWord -ErrorAction SilentlyContinue
|
|
} catch {}
|
|
|
|
Write-Host 'kiosk always-on enforced (power never-off + screensaver/lock disabled).'
|
|
'''
|
|
|
|
|
|
def read_client_module():
|
|
"""The enforce client module's bytes, read from this repo.
|
|
|
|
Shipping it as a manifest entry closes a real gap: the module is installed
|
|
once by Install-ShopdbKiosk.ps1 and never refreshed, so a client change
|
|
reached the server with the code deploy and then sat one directory away from
|
|
where kiosks fetch from, waiting for someone to re-stage the installer
|
|
bundle by hand. Displays are share-less by design; hands on every kiosk is
|
|
the wrong cost for a client change.
|
|
"""
|
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
'client', CLIENT_MODULE_FILENAME)
|
|
with open(path, 'rb') as handle:
|
|
return handle.read()
|
|
|
|
|
|
def build_forceedgeupdate_script():
|
|
"""Return the inline one-shot Edge force-update PowerShell as text.
|
|
|
|
WHY THIS EXISTS: a pending Edge update parks a restart prompt on every
|
|
display and RelaunchWindow defers the forced restart to 02:00-04:00, so the
|
|
prompt can sit on screen all day with nobody on site to dismiss it.
|
|
|
|
This does not relaunch the browser itself. It cannot: the enforce task runs
|
|
as SYSTEM in session 0, where a launched browser is invisible (the same
|
|
constraint the dispatcher and the watchdog are built around). It stops Edge
|
|
and lets the EXISTING kiosk watchdog relaunch it from the Startup shortcut
|
|
within its interval, which is also what applies the staged update.
|
|
|
|
One-shot via DetectionMethod=MarkerFile: the engine writes the marker after
|
|
a 0 exit, so this runs once per display and is skipped on every later cycle.
|
|
A FAILED run writes no marker and is retried next cycle. To force another
|
|
round later, bump the date in FORCE_EDGE_UPDATE_MARKER.
|
|
"""
|
|
return r'''# Invoke-EdgeForceUpdate.ps1 -- one-shot: apply a pending Edge update now.
|
|
#
|
|
# Runs as SYSTEM under the enforce task. Stops Edge; the kiosk watchdog
|
|
# relaunches it (that relaunch is what completes a staged update).
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
$updater = Join-Path ${env:ProgramFiles(x86)} 'Microsoft\EdgeUpdate\MicrosoftEdgeUpdate.exe'
|
|
if (Test-Path $updater) {
|
|
# /ua = update-all check; runs the same path Edge's own scheduled task uses,
|
|
# so a staged update is downloaded rather than waited for.
|
|
try {
|
|
Start-Process -FilePath $updater -ArgumentList '/ua','/installsource','scheduler' `
|
|
-NoNewWindow -Wait -ErrorAction Stop
|
|
Write-Host 'edge updater ran'
|
|
} catch {
|
|
Write-Host "edge updater failed: $_"
|
|
}
|
|
} else {
|
|
Write-Host "edge updater not found at $updater"
|
|
}
|
|
|
|
# Stop every Edge process, kiosk or not. The watchdog notices no --kiosk
|
|
# process and relaunches from the Startup shortcut, which is the ONE source of
|
|
# truth for the target URL.
|
|
$edge = Get-Process -Name msedge -ErrorAction SilentlyContinue
|
|
if ($edge) {
|
|
try {
|
|
$edge | Stop-Process -Force -ErrorAction Stop
|
|
Write-Host "stopped $($edge.Count) edge process(es); watchdog will relaunch the kiosk"
|
|
} catch {
|
|
# Do NOT exit non-zero here. A process that vanished between the Get and
|
|
# the Stop is a success, not a failure - failing would withhold the
|
|
# marker and re-kill Edge on every cycle from now on.
|
|
Write-Host "stop-process reported: $_"
|
|
}
|
|
} else {
|
|
Write-Host 'no edge process running; watchdog will relaunch the kiosk'
|
|
}
|
|
|
|
exit 0
|
|
'''
|
|
|
|
|
|
def _registry_drift_heal_entry(name, regname, regvalue, regtype, comment):
|
|
"""One Type=Registry entry that writes a value and detects drift via
|
|
ValueMatches against that same path/name.
|
|
|
|
DetectionValue is stored as a string; the engine string-coerces for
|
|
ValueMatches, so a DWord value of 2 detects against '2'.
|
|
"""
|
|
return {
|
|
'_comment': comment,
|
|
'Name': name,
|
|
'Type': 'Registry',
|
|
'RegPath': EDGE_POLICY_PATH,
|
|
'RegName': regname,
|
|
'RegValue': regvalue,
|
|
'RegType': regtype,
|
|
'DetectionMethod': 'ValueMatches',
|
|
'DetectionPath': EDGE_POLICY_PATH,
|
|
'DetectionName': regname,
|
|
'DetectionValue': str(regvalue),
|
|
}
|
|
|
|
|
|
def build_display_manifest():
|
|
"""Return the gea-shopfloor-display manifest dict (Applications in order).
|
|
|
|
Four Edge kiosk relaunch-policy drift-heal entries, then the one dispatcher
|
|
entry. The dispatcher is declared PayloadSource=inline with no hash yet; the
|
|
seed attaches the real payload bytes (and its sha256) after the draft rows
|
|
exist. Kept payload-free otherwise: the kiosk engine and browser are baked
|
|
into the display image, not shipped over HTTPS.
|
|
"""
|
|
clientbytes = read_client_module()
|
|
applications = [
|
|
{
|
|
'_comment': (
|
|
'The GE-Enforce client module itself. Install-ShopdbKiosk.ps1 '
|
|
'lays this down once at bootstrap and never refreshes it, so a '
|
|
'client change reached the server with the code deploy and then '
|
|
'sat one directory away from where kiosks fetch, waiting for '
|
|
'someone to re-stage the installer bundle by hand. Displays are '
|
|
'share-less by design; hands on every kiosk is the wrong cost '
|
|
'for a client change. Hash detection against the shipped bytes, '
|
|
'so a matching module is left alone and only a changed one is '
|
|
'rewritten. SELF-MODIFYING BY DESIGN: this module is what stages '
|
|
'payloads, but PowerShell loads it into memory at start, so '
|
|
'rewriting the file mid-run is harmless and takes effect on the '
|
|
'NEXT cycle. Pilot a client change on ONE kiosk before the fleet '
|
|
'- a broken module cannot fetch its own replacement, and on a '
|
|
'share-less display that means a site visit.'),
|
|
'Name': 'GE-Enforce client module (self-update)',
|
|
'Type': 'File',
|
|
'Source': CLIENT_MODULE_FILENAME,
|
|
'Destination': CLIENT_MODULE_DEST,
|
|
'PayloadSource': 'inline',
|
|
'PayloadRef': CLIENT_MODULE_FILENAME,
|
|
'DetectionMethod': 'Hash',
|
|
'DetectionPath': CLIENT_MODULE_DEST,
|
|
'DetectionValue': hashlib.sha256(clientbytes).hexdigest(),
|
|
},
|
|
_registry_drift_heal_entry(
|
|
'Edge kiosk RelaunchNotification (Required auto-restart)',
|
|
'RelaunchNotification', 2, 'DWord',
|
|
'RelaunchNotification=2 (Required): Edge auto-restarts after the '
|
|
'notification period. Displays have no operator to dismiss the '
|
|
'update dialog, so this is the only mode that recovers unattended. '
|
|
'Heals drift of the policy set at imaging by 09-Setup-Display.ps1.'),
|
|
_registry_drift_heal_entry(
|
|
'Edge kiosk RelaunchNotificationPeriod (1 hour)',
|
|
'RelaunchNotificationPeriod', 3600000, 'DWord',
|
|
'Milliseconds before the forced auto-restart. 3600000 ms = 1 hour.'),
|
|
_registry_drift_heal_entry(
|
|
'Edge kiosk RelaunchHeadsUpPeriod (1 minute)',
|
|
'RelaunchHeadsUpPeriod', 60000, 'DWord',
|
|
'Milliseconds of final warning before auto-restart. 60000 ms = 1 '
|
|
'minute.'),
|
|
_registry_drift_heal_entry(
|
|
'Edge kiosk RelaunchWindow (02:00-04:00)',
|
|
'RelaunchWindow', RELAUNCH_WINDOW_JSON, 'String',
|
|
'Overnight forced-restart window (02:00 start, 120 minute '
|
|
'duration) so business-hour updates wait until off-hours and the '
|
|
'dialog stays invisible during the day.'),
|
|
{
|
|
'_comment': (
|
|
'Kiosk dispatcher. Reads C:\\Enrollment\\display-type.txt and '
|
|
'writes an all-users Startup shortcut that launches Edge kiosk '
|
|
'fullscreen at the subtype target. Data-driven map: '
|
|
+ _relaunch_window_targets_comment()
|
|
+ '. Base URL from HKLM BaseUrl, else the WJ host. Runs as SYSTEM '
|
|
'(no interactive desktop), so it does NOT Start-Process Edge '
|
|
'(that opens invisibly in session 0) - the auto-login user runs '
|
|
'the shortcut at logon in a visible session. Delivered inline over '
|
|
'HTTPS (share-less displays); Always re-asserts, idempotent '
|
|
'(rewrites only the resolved-target shortcut, self-heals a '
|
|
'type/URL change).'),
|
|
'Name': 'Display kiosk dispatcher (display-type.txt)',
|
|
'Type': 'PS1',
|
|
'Script': DISPATCHER_FILENAME,
|
|
'PayloadSource': 'inline',
|
|
'PayloadRef': DISPATCHER_FILENAME,
|
|
'DetectionMethod': 'Always',
|
|
},
|
|
{
|
|
'_comment': (
|
|
'Relaunch the kiosk browser when it goes away. The Startup '
|
|
'shortcut the dispatcher writes runs ONCE at logon, so an Edge '
|
|
'update, a crash or a closed window leaves the display dead '
|
|
'until the next logon. RelaunchNotification=2 does not cover '
|
|
'it: that policy drives Edge\'s update-restart, which relies on '
|
|
'session restore, and --kiosk restores no session and shows no '
|
|
'notification UI. Registers an INTERACTIVE-session scheduled '
|
|
'task (SYSTEM cannot launch a visible browser) that relaunches '
|
|
'from the same shortcut every '
|
|
f'{WATCHDOG_INTERVAL_MIN} minutes when no --kiosk process is '
|
|
'running.'),
|
|
'Name': 'Display kiosk watchdog (relaunch Edge)',
|
|
'Type': 'PS1',
|
|
'Script': WATCHDOG_FILENAME,
|
|
'PayloadSource': 'inline',
|
|
'PayloadRef': WATCHDOG_FILENAME,
|
|
'DetectionMethod': 'Always',
|
|
},
|
|
{
|
|
'_comment': (
|
|
'ONE-SHOT. Applies a pending Edge update now instead of waiting '
|
|
'for the 02:00-04:00 RelaunchWindow, which can leave the update '
|
|
'prompt on screen all day with nobody on site to dismiss it. '
|
|
'Ordered AFTER the watchdog entry on purpose: this stops Edge '
|
|
'and relies on the watchdog to bring the kiosk back, so the '
|
|
'watchdog must be registered first on a display seeing both for '
|
|
'the first time. DetectionMethod=MarkerFile makes it run once '
|
|
'per display - the engine writes the marker only after a 0 exit, '
|
|
'so a failed run retries next cycle. Re-arm for a future update '
|
|
'by bumping the date in FORCE_EDGE_UPDATE_MARKER.'),
|
|
'Name': 'Force pending Edge update and bounce the kiosk (one-shot)',
|
|
'Type': 'PS1',
|
|
'Script': FORCE_EDGE_UPDATE_FILENAME,
|
|
'PayloadSource': 'inline',
|
|
'PayloadRef': FORCE_EDGE_UPDATE_FILENAME,
|
|
'DetectionMethod': 'MarkerFile',
|
|
'DetectionPath': FORCE_EDGE_UPDATE_MARKER,
|
|
},
|
|
{
|
|
'_comment': (
|
|
'Keep the display awake 24/7 so the kiosk page stays visible: '
|
|
'powercfg never-off (AC+DC) + screensaver/auto-lock disabled. '
|
|
'Delivered inline over HTTPS; Always re-asserts + self-heals a '
|
|
'power-plan drift each cycle.'),
|
|
'Name': 'Display always-on (no sleep/blank/lock)',
|
|
'Type': 'PS1',
|
|
'Script': ALWAYSON_FILENAME,
|
|
'PayloadSource': 'inline',
|
|
'PayloadRef': ALWAYSON_FILENAME,
|
|
'DetectionMethod': 'Always',
|
|
},
|
|
]
|
|
return {
|
|
'Version': SCOPE_VERSION,
|
|
'_comment': (
|
|
'gea-shopfloor-display runtime scope. Heals Edge kiosk relaunch '
|
|
'policy drift and dispatches the kiosk to the subtype target. No '
|
|
'EXE payloads: kiosk engine and browser are baked into the display '
|
|
'image. Self-sufficient: displays do NOT inherit the common '
|
|
'scope.'),
|
|
'Applications': applications,
|
|
}
|
|
|
|
|
|
def seed_display_scope(publish=False, notes='seed gea-shopfloor-display'):
|
|
"""Create/refresh the gea-shopfloor-display draft scope and its entries.
|
|
|
|
Idempotent for the draft: replace_scope_draft rebuilds the draft rows, and
|
|
the inline dispatcher payload is content-addressed (a re-run stores the same
|
|
bytes to the same sha256). Set publish=True to also freeze a published
|
|
snapshot (that step is NOT idempotent: it always creates a new version).
|
|
|
|
Commits the session. Returns a summary dict:
|
|
{scopeid, entrycount, entrytypes, dispatchersha256, publishedversion}.
|
|
"""
|
|
manifest = build_display_manifest()
|
|
scope = service.replace_scope_draft(SCOPE_NAME, SCOPE_PHASE, manifest)
|
|
# Flush so the new entries get entryids before the inline payload attaches.
|
|
db.session.flush()
|
|
|
|
dispatcher = next(entry for entry in scope.entries
|
|
if entry.name == 'Display kiosk dispatcher (display-type.txt)')
|
|
scriptbytes = build_dispatcher_script().encode('utf-8')
|
|
payload = service.store_inline_payload(
|
|
dispatcher, DISPATCHER_FILENAME,
|
|
'text/plain; charset=utf-8', scriptbytes)
|
|
|
|
alwayson = next(entry for entry in scope.entries
|
|
if entry.name == 'Display always-on (no sleep/blank/lock)')
|
|
alwaysonbytes = build_alwayson_script().encode('utf-8')
|
|
alwaysonpayload = service.store_inline_payload(
|
|
alwayson, ALWAYSON_FILENAME,
|
|
'text/plain; charset=utf-8', alwaysonbytes)
|
|
|
|
watchdog = next(entry for entry in scope.entries
|
|
if entry.name == 'Display kiosk watchdog (relaunch Edge)')
|
|
watchdogbytes = build_watchdog_script().encode('utf-8')
|
|
watchdogpayload = service.store_inline_payload(
|
|
watchdog, WATCHDOG_FILENAME,
|
|
'text/plain; charset=utf-8', watchdogbytes)
|
|
|
|
clientmodule = next(entry for entry in scope.entries
|
|
if entry.name.startswith('GE-Enforce client module'))
|
|
clientbytes = read_client_module()
|
|
clientpayload = service.store_inline_payload(
|
|
clientmodule, CLIENT_MODULE_FILENAME,
|
|
'text/plain; charset=utf-8', clientbytes)
|
|
|
|
forceupdate = next(entry for entry in scope.entries
|
|
if entry.name.startswith('Force pending Edge update'))
|
|
forceupdatebytes = build_forceedgeupdate_script().encode('utf-8')
|
|
forceupdatepayload = service.store_inline_payload(
|
|
forceupdate, FORCE_EDGE_UPDATE_FILENAME,
|
|
'text/plain; charset=utf-8', forceupdatebytes)
|
|
|
|
publishedversion = None
|
|
if publish:
|
|
publishedversion = service.publish_scope(
|
|
SCOPE_NAME, SCOPE_PHASE, notes=notes)
|
|
|
|
db.session.commit()
|
|
|
|
return {
|
|
'scopeid': scope.scopeid,
|
|
'entrycount': len(scope.entries),
|
|
'entrytypes': [entry.entrytype for entry in scope.entries],
|
|
'dispatchersha256': payload.payloadsha256,
|
|
'alwaysonsha256': alwaysonpayload.payloadsha256,
|
|
'watchdogsha256': watchdogpayload.payloadsha256,
|
|
'forceupdatesha256': forceupdatepayload.payloadsha256,
|
|
'clientmodulesha256': clientpayload.payloadsha256,
|
|
'publishedversion': publishedversion,
|
|
}
|