displays: clear a pending Edge update without waiting for 02:00
Every kiosk was sitting on Edge's restart-to-update prompt. The scope already sets RelaunchNotification=2 so Edge restarts unattended, but RelaunchWindow defers that restart to 02:00-04:00, so during the day the update waits and there is nobody on site to dismiss the prompt. A one-shot entry runs the Edge updater and stops the browser. It does not relaunch it - the enforce task is SYSTEM in session 0, where a launched browser is invisible - so it leans on the watchdog that already relaunches the kiosk from the Startup shortcut. That relaunch is what applies the staged update. One-shot is DetectionMethod=MarkerFile. The engine writes the marker only after a 0 exit, so a failed run retries next cycle instead of being recorded as done, and the script exits 0 when no Edge was running - that is a success, and failing it would withhold the marker and re-kill Edge on every cycle from then on. The marker path carries a date, which is the re-arm mechanism for a future update. Ordered after the watchdog entry: a display seeing both for the first time must have its relauncher registered before anything stops the browser.
This commit is contained in:
@@ -58,6 +58,14 @@ DISPLAY_TYPE_TARGETS = {
|
||||
|
||||
DISPATCHER_FILENAME = 'Invoke-DisplayKioskDispatch.ps1'
|
||||
|
||||
# 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 display runs
|
||||
# it exactly once, ever.
|
||||
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."""
|
||||
@@ -576,6 +584,68 @@ Write-Host 'kiosk always-on enforced (power never-off + screensaver/lock disable
|
||||
'''
|
||||
|
||||
|
||||
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.
|
||||
@@ -670,6 +740,26 @@ def build_display_manifest():
|
||||
'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: '
|
||||
@@ -733,6 +823,13 @@ def seed_display_scope(publish=False, notes='seed gea-shopfloor-display'):
|
||||
watchdog, WATCHDOG_FILENAME,
|
||||
'text/plain; charset=utf-8', watchdogbytes)
|
||||
|
||||
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(
|
||||
@@ -747,5 +844,6 @@ def seed_display_scope(publish=False, notes='seed gea-shopfloor-display'):
|
||||
'dispatchersha256': payload.payloadsha256,
|
||||
'alwaysonsha256': alwaysonpayload.payloadsha256,
|
||||
'watchdogsha256': watchdogpayload.payloadsha256,
|
||||
'forceupdatesha256': forceupdatepayload.payloadsha256,
|
||||
'publishedversion': publishedversion,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user