From c464f45f4fa69e8b461f4aae0a440207f124d52e Mon Sep 17 00:00:00 2001 From: cproudlock Date: Thu, 9 Apr 2026 13:30:12 -0400 Subject: [PATCH] Shopfloor sync_intune + Set-MachineNumber hardening Long debugging round on the shopfloor test PC with several overlapping bugs. This commit folds all the fixes together. sync_intune.bat - Slim down to an elevation thunk that launches a NEW elevated PS window via Start-Process -Verb RunAs (with -NoExit so the window doesn't vanish on error). All UI now lives in the PS monitor, not mixed into the cmd launcher. - Goto-based control flow. Earlier version had nested if (...) blocks with literal parens inside echo lines (e.g. "wrappers (Install-eDNC, ...etc)."); cmd parses if-blocks by counting parens character-by- character, so the ")" in "etc)." closed the outer block early and the leftover "." threw ". was unexpected at this time.", crashing the elevated cmd /c window before pause ran. - Multi-location Monitor-IntuneProgress.ps1 lookup so the user's quick-test workflow (drop both files on the desktop) works without manually editing the hardcoded path. Lookup order: 1. %~dp0lib\Monitor-IntuneProgress.ps1 2. %~dp0Monitor-IntuneProgress.ps1 3. C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1 4. C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1 - Prints "Launching: " as its first line so you can see which copy it actually loaded. This caught a bug where a stale desktop copy was shadowing the canonical file via fallback #2. Set-MachineNumber.bat - Same multi-location lookup pattern. Old version used %~dp0Set-MachineNumber.ps1 and bombed when the bat was copied to the desktop without its .ps1 sibling. - Goto-based dispatch, no nested parens, for the same parser reason. Monitor-IntuneProgress.ps1 - Start-Transcript at the top, writing to C:\Logs\SFLD\ (falls back to %TEMP% if C:\Logs\SFLD isn't writable yet) with a startup banner including a timestamp. Every run leaves a captured trace. - Main polling loop wrapped in try/catch/finally. Unhandled exceptions print a red report with type, message, position, and stack trace, then block on Wait-ForAnyKey so the window can't auto-close on a silent crash. - Console window resize at startup via $Host.UI.RawUI.WindowSize / BufferSize, wrapped in try/catch (Windows Terminal ignores it, but classic conhost honors it). - Clear-KeyBuffer / Read-SingleKey / Wait-ForAnyKey helpers. Drain any buffered keystrokes from the polling loop before each prompt so an accidental keypress can't satisfy a pause prematurely. - Invoke-SetupComplete / Invoke-RebootPrompt final-state handlers. The REBOOT REQUIRED branch now shows a yellow 3-line header, a four-line explanation, and a cyan "Press Y to reboot now, or N to cancel:" prompt via Read-SingleKey @('Y','N'). Y triggers Restart-Computer -Force (with shutdown.exe fallback), N falls through to Wait-ForAnyKey. - Display order: status table FIRST, QR LAST. The cursor ends below the QR so the viewport always follows it - keeps the QR on screen regardless of window height. Works on both classic conhost and Windows Terminal (neither reliably honors programmatic resize). - Half-block QR renderer: walks QRCoder's ModuleMatrix directly and emits U+2580 / U+2584 / U+2588 / space, one output line per two matrix rows. Halves the rendered height vs AsciiQRCode full-block. Quiet zone added manually via $pad=4 since QRCoder's ModuleMatrix doesn't include one. Trade-off: may not be perfectly square on all fonts, but the user accepted that for the smaller footprint after multiple iterations comparing full-block vs half-block vs PNG popup. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../Shopfloor/lib/Monitor-IntuneProgress.ps1 | 300 +++++++++++++++--- .../shopfloor-setup/Shopfloor/sync_intune.bat | 119 +++---- .../Standard/Set-MachineNumber.bat | 38 ++- 3 files changed, 336 insertions(+), 121 deletions(-) diff --git a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 index a24cf73..9e8de0c 100644 --- a/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 +++ b/playbook/shopfloor-setup/Shopfloor/lib/Monitor-IntuneProgress.ps1 @@ -54,6 +54,52 @@ param( [int]$RetriggerMinutes = 3 ) +# ============================================================================ +# Transcript logging - writes EVERYTHING the script sees and writes to a log +# file so we can diagnose auto-close / crash issues after the fact. Stored +# under C:\Logs\SFLD\ alongside the DSC logs. If the dir doesn't exist yet +# we fall back to %TEMP% so logging never itself fails the run. +# ============================================================================ +$logDir = 'C:\Logs\SFLD' +if (-not (Test-Path $logDir)) { + try { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } catch { $logDir = $env:TEMP } +} +$transcriptPath = Join-Path $logDir 'sync_intune_transcript.txt' +try { Start-Transcript -Path $transcriptPath -Append -Force | Out-Null } catch {} + +Write-Host "" +Write-Host "=== Monitor-IntuneProgress.ps1 starting $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ===" +Write-Host "Transcript: $transcriptPath" +Write-Host "" + +# ============================================================================ +# Console resize - the QR code is ~30 lines and the 5-phase status table is +# ~25 lines. Default cmd window is 25 rows, so the QR scrolls off the top. +# Bump the window (and buffer) to ~58 rows so the whole frame fits at once. +# Wrapped in try/catch because some hosts (Windows Terminal, ISE) don't honor +# WindowSize/BufferSize changes - in that case we just live with the default. +# ============================================================================ +try { + $rui = $Host.UI.RawUI + $maxH = $rui.MaxPhysicalWindowSize.Height + $targetWindow = [Math]::Min(58, [int]$maxH) + $targetBuffer = [Math]::Max($targetWindow, 200) + + # Buffer must be set FIRST and must be >= window. Width must not shrink + # below current window width or SetBufferSize throws. + $bs = $rui.BufferSize + if ($bs.Height -lt $targetBuffer) { + $bs.Height = $targetBuffer + $rui.BufferSize = $bs + } + + $ws = $rui.WindowSize + if ($ws.Height -lt $targetWindow) { + $ws.Height = $targetWindow + $rui.WindowSize = $ws + } +} catch {} + # ============================================================================ # Helpers # ============================================================================ @@ -306,6 +352,16 @@ function Invoke-IntuneSync { # ============================================================================ # QR code (cached as text - generate once, re-print on every redraw) +# +# Half-block renderer: each output character represents 1 QR module wide and +# 2 QR modules tall, using Unicode half-block characters: +# U+2588 ([block]) = both top and bottom modules set +# U+2580 ([upper]) = only top module set +# U+2584 ([lower]) = only bottom module set +# space = neither set +# Cuts QR height roughly in half vs AsciiQRCode full-block rendering. May +# render slightly non-square on fonts where char cell aspect isn't exactly +# 1:2, but it's half the real estate and the user accepted that trade-off. # ============================================================================ function Build-QRCodeText { $lines = @() @@ -324,19 +380,50 @@ function Build-QRCodeText { $lines += "" $dllPath = 'C:\Enrollment\shopfloor-setup\Shopfloor\QRCoder.dll' - if (Test-Path $dllPath) { - try { - Add-Type -Path $dllPath - $gen = New-Object QRCoder.QRCodeGenerator - $data = $gen.CreateQrCode($deviceId, [QRCoder.QRCodeGenerator+ECCLevel]::L) - $ascii = New-Object QRCoder.AsciiQRCode($data) - $qr = $ascii.GetGraphic(1, [char]0x2588 + [char]0x2588, ' ') - $lines += $qr -split "`r?`n" - } catch { - $lines += "(QR code generation failed: $($_.Exception.Message))" - } - } else { + if (-not (Test-Path $dllPath)) { $lines += "(QRCoder.dll not found at $dllPath - skipping QR code)" + return ($lines -join "`n") + } + + try { + Add-Type -Path $dllPath + $gen = New-Object QRCoder.QRCodeGenerator + $data = $gen.CreateQrCode($deviceId, [QRCoder.QRCodeGenerator+ECCLevel]::L) + + # Walk the module matrix. QRCoder's ModuleMatrix does NOT include a + # quiet zone by default (confirmed empirically - for a UUID payload + # at ECC L, ModuleMatrix is 29x29 for QR3 data only). We add a + # 4-module quiet zone manually via $pad below. + $matrix = $data.ModuleMatrix + $size = $matrix.Count + $pad = 4 + $total = $size + 2 * $pad + + $upper = [char]0x2580 + $lower = [char]0x2584 + $full = [char]0x2588 + $left = ' ' # 8-space indent matches the old layout + + for ($y = 0; $y -lt $total; $y += 2) { + $sb = New-Object System.Text.StringBuilder + [void]$sb.Append($left) + for ($x = 0; $x -lt $total; $x++) { + $mx = $x - $pad + $my1 = $y - $pad + $my2 = $y + 1 - $pad + + $top = ($my1 -ge 0 -and $my1 -lt $size -and $mx -ge 0 -and $mx -lt $size -and $matrix[$my1].Get($mx)) + $bot = ($my2 -ge 0 -and $my2 -lt $size -and $mx -ge 0 -and $mx -lt $size -and $matrix[$my2].Get($mx)) + + if ($top -and $bot) { [void]$sb.Append($full) } + elseif ($top) { [void]$sb.Append($upper) } + elseif ($bot) { [void]$sb.Append($lower) } + else { [void]$sb.Append(' ') } + } + $lines += $sb.ToString() + } + } catch { + $lines += "(QR code generation failed: $($_.Exception.Message))" } return ($lines -join "`n") } @@ -408,46 +495,165 @@ function Format-Snapshot { } # ============================================================================ -# Main loop +# Key buffer drain + single-keypress reader - used before final prompts so +# any keystrokes the user tapped during the polling loop don't satisfy the +# prompt prematurely. Wrapped in try/catch because [Console]::KeyAvailable +# throws in non-interactive hosts (ISE, remoting). # ============================================================================ -$qrText = Build-QRCodeText -Invoke-IntuneSync -$lastSync = Get-Date -$nextRetrigger = $lastSync.AddMinutes($RetriggerMinutes) +function Clear-KeyBuffer { + try { + while ([Console]::KeyAvailable) { [void][Console]::ReadKey($true) } + } catch {} +} -while ($true) { - $snap = Get-Snapshot - - Clear-Host - Write-Host $qrText - Write-Host "" - foreach ($l in (Format-Snapshot -Snap $snap -LastSync $lastSync -NextRetrigger $nextRetrigger)) { - Write-Host $l +function Read-SingleKey { + param([string[]]$ValidKeys) + Clear-KeyBuffer + while ($true) { + try { + $k = [Console]::ReadKey($true) + $char = ([string]$k.KeyChar).ToUpper() + if ($ValidKeys -contains $char) { return $char } + } catch { + # Fallback for hosts that don't expose ReadKey - use Read-Host. + $ans = (Read-Host ("Enter one of: " + ($ValidKeys -join '/'))).Trim().ToUpper() + if ($ValidKeys -contains $ans) { return $ans } + } } +} - # Final state: post-reboot install complete -> exit clean - if ($snap.DscInstallComplete) { +function Wait-ForAnyKey { + param([string]$Prompt = 'Press any key to close this window...') + Write-Host "" + Write-Host $Prompt + Clear-KeyBuffer + try { + [void][Console]::ReadKey($true) + } catch { + [void](Read-Host) + } +} + +# ============================================================================ +# Final-state handlers - called from the main loop when either the +# post-reboot install has completed, or the pre-reboot deployment is done +# and a reboot is required. Each handler draws its own final frame, prompts +# the user, then exits. +# ============================================================================ +function Invoke-SetupComplete { + Write-Host "" + Write-Host "========================================" -ForegroundColor Green + Write-Host " Setup complete - no reboot needed" -ForegroundColor Green + Write-Host "========================================" -ForegroundColor Green + Write-Host "" + Write-Host "The post-reboot DSC install phase is finished. The device is ready." + Wait-ForAnyKey + exit 0 +} + +function Invoke-RebootPrompt { + Write-Host "" + Write-Host "========================================" -ForegroundColor Yellow + Write-Host " REBOOT REQUIRED" -ForegroundColor Yellow + Write-Host "========================================" -ForegroundColor Yellow + Write-Host "" + Write-Host "The pre-reboot deployment phase is complete. You must reboot now" + Write-Host "to start the post-reboot DSC install phase, which downloads" + Write-Host "device-config.yaml and runs the per-app wrappers: Install-eDNC," + Write-Host "Install-UDC, Install-VCRedists, Install-OpenText, and so on." + Write-Host "" + Write-Host "Press Y to reboot now, or N to cancel: " -NoNewline -ForegroundColor Cyan + $ans = Read-SingleKey -ValidKeys @('Y', 'N') + Write-Host $ans + if ($ans -eq 'Y') { Write-Host "" - Write-Host "All milestones reached. Setup complete." -ForegroundColor Green + Write-Host "Rebooting in 5 seconds..." -ForegroundColor Cyan + Start-Sleep -Seconds 5 + try { + Restart-Computer -Force -ErrorAction Stop + } catch { + Write-Host "Restart-Computer failed: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Falling back to shutdown.exe /r /t 5..." + & shutdown.exe /r /t 5 + } exit 0 } - - # Reboot check (boot-loop-safe) - $rebootState = Test-RebootState - if ($rebootState -eq 'needed') { - Write-Host "" - Write-Host "Pre-reboot deployment phase complete - REBOOT REQUIRED" -ForegroundColor Yellow - exit 2 - } - - # Re-trigger sync periodically - if ((Get-Date) -ge $nextRetrigger) { - Write-Host "" - Write-Host "Re-triggering Intune sync..." -ForegroundColor Cyan - Invoke-IntuneSync - $lastSync = Get-Date - $nextRetrigger = $lastSync.AddMinutes($RetriggerMinutes) - } - - Start-Sleep -Seconds $PollSecs + Write-Host "" + Write-Host "Cancelled - reboot manually when ready." -ForegroundColor Yellow + Wait-ForAnyKey + exit 0 +} + +# ============================================================================ +# Main loop +# +# Display order is intentional: status table FIRST, QR LAST. The cursor +# ends below the QR, so the viewport follows it and keeps the QR visible +# regardless of window size. This works in classic conhost and Windows +# Terminal - neither reliably honors programmatic window resize, so we +# solve it by controlling cursor position instead. +# ============================================================================ +try { + $qrText = Build-QRCodeText + Invoke-IntuneSync + $lastSync = Get-Date + $nextRetrigger = $lastSync.AddMinutes($RetriggerMinutes) + + while ($true) { + $snap = Get-Snapshot + + Clear-Host + Write-Host "=== Monitor running - transcript: $transcriptPath ===" -ForegroundColor DarkGray + foreach ($l in (Format-Snapshot -Snap $snap -LastSync $lastSync -NextRetrigger $nextRetrigger)) { + Write-Host $l + } + Write-Host "" + Write-Host $qrText + + # Final state: post-reboot install complete + if ($snap.DscInstallComplete) { + Invoke-SetupComplete + } + + # Reboot check (boot-loop-safe) + $rebootState = Test-RebootState + if ($rebootState -eq 'needed') { + Invoke-RebootPrompt + } + + # Re-trigger sync periodically + if ((Get-Date) -ge $nextRetrigger) { + Write-Host "" + Write-Host "Re-triggering Intune sync..." -ForegroundColor Cyan + Invoke-IntuneSync + $lastSync = Get-Date + $nextRetrigger = $lastSync.AddMinutes($RetriggerMinutes) + } + + Start-Sleep -Seconds $PollSecs + } +} +catch { + # Any unhandled exception in the main loop lands here. Write the error + # into the transcript and then block on a keypress so the PS window + # doesn't auto-close before the user can read it. + Write-Host "" + Write-Host "=====================================================" -ForegroundColor Red + Write-Host " UNHANDLED ERROR in Monitor-IntuneProgress.ps1" -ForegroundColor Red + Write-Host "=====================================================" -ForegroundColor Red + Write-Host "" + Write-Host "Message : $($_.Exception.Message)" -ForegroundColor Red + Write-Host "Type : $($_.Exception.GetType().FullName)" -ForegroundColor Red + Write-Host "At : $($_.InvocationInfo.PositionMessage)" -ForegroundColor Red + Write-Host "" + Write-Host "Stack trace:" -ForegroundColor Red + Write-Host $_.ScriptStackTrace -ForegroundColor Red + Write-Host "" + Write-Host "Full transcript: $transcriptPath" + Wait-ForAnyKey + try { Stop-Transcript | Out-Null } catch {} + exit 1 +} +finally { + try { Stop-Transcript | Out-Null } catch {} } diff --git a/playbook/shopfloor-setup/Shopfloor/sync_intune.bat b/playbook/shopfloor-setup/Shopfloor/sync_intune.bat index 869a54b..781c83f 100644 --- a/playbook/shopfloor-setup/Shopfloor/sync_intune.bat +++ b/playbook/shopfloor-setup/Shopfloor/sync_intune.bat @@ -1,81 +1,58 @@ @echo off -REM sync_intune.bat - Thin launcher for Monitor-IntuneProgress.ps1 +REM sync_intune.bat - Launches Monitor-IntuneProgress.ps1 in an elevated +REM PowerShell window. REM -REM All polling, status display, sync triggering, and reboot detection lives in -REM the PowerShell monitor. This .bat just handles: -REM 1. Self-elevate to admin -REM 2. Invoke the monitor (which renders QR + 5-phase status table) -REM 3. Branch on the monitor's exit code: -REM 0 = post-reboot install complete, "done" message and exit -REM 2 = pre-reboot deployment done, prompt for reboot -REM else = error, pause so the user can read it +REM This .bat exists only because Windows doesn't give .ps1 files a +REM "double-click to run as admin" verb. It finds the monitor script, +REM then calls Start-Process -Verb RunAs to open a NEW elevated +REM PowerShell console hosting it. The cmd window that ran this .bat +REM exits immediately afterwards - all UI (QR code, status table, reboot +REM prompt) lives in the separate PS window. REM -REM The monitor lives at C:\Enrollment\shopfloor-setup\Shopfloor\Monitor-IntuneProgress.ps1. -REM This .bat gets copied to the user's desktop by Run-ShopfloorSetup.ps1, so -REM %~dp0 doesn't necessarily point at the shopfloor-setup tree - we use the -REM absolute path to find the monitor instead. +REM Monitor lookup order: +REM 1. %~dp0lib\Monitor-IntuneProgress.ps1 +REM - repo layout / canonical on-disk layout (bat in Shopfloor/, .ps1 in Shopfloor/lib/) +REM 2. %~dp0Monitor-IntuneProgress.ps1 +REM - both files dropped in same dir (quick-test on desktop) +REM 3. C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1 +REM - dispatcher-dropped, if this .bat is being invoked from elsewhere +REM 4. C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1 +REM - canonical enrollment staging copy +REM +REM -NoExit on the inner PowerShell keeps the new window open after the +REM script finishes so the user can read any final output or error before +REM the window closes. +REM +REM Goto-based dispatch - no nested if blocks, no literal parens in echo +REM lines. CMD parses "if (...)" blocks by counting parens and will silently +REM eat any "(" or ")" inside an echo, so keeping the flow flat avoids that +REM class of syntax bomb entirely. setlocal -title Intune Policy Sync +title Intune Policy Sync Launcher + +set "MONITOR=%~dp0lib\Monitor-IntuneProgress.ps1" +if exist "%MONITOR%" goto :launch + +set "MONITOR=%~dp0Monitor-IntuneProgress.ps1" +if exist "%MONITOR%" goto :launch + +set "MONITOR=C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1" +if exist "%MONITOR%" goto :launch -REM Monitor lives under lib\ to keep it OUT of the dispatcher's baseline scan. -REM Run-ShopfloorSetup.ps1 does Get-ChildItem -Filter "*.ps1" on the Shopfloor\ -REM dir (non-recursive) and runs every script it finds - if Monitor-IntuneProgress -REM lived there, the dispatcher would invoke it as a baseline script and hang the -REM whole shopfloor setup forever (it's an infinite poll loop, never returns). set "MONITOR=C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1" +if exist "%MONITOR%" goto :launch -REM Self-elevate to administrator -net session >nul 2>&1 -if errorlevel 1 ( - powershell -Command "Start-Process '%~f0' -Verb RunAs" - exit /b -) - -if not exist "%MONITOR%" ( - echo ERROR: Monitor not found at: - echo %MONITOR% - echo. - echo Was the shopfloor-setup tree staged correctly? - pause - exit /b 1 -) - -powershell -NoProfile -ExecutionPolicy Bypass -File "%MONITOR%" -set "MONITOR_EXIT=%errorlevel%" - +echo ERROR: Monitor-IntuneProgress.ps1 not found in any of: +echo %~dp0lib\Monitor-IntuneProgress.ps1 +echo %~dp0Monitor-IntuneProgress.ps1 +echo C:\Users\SupportUser\Desktop\Monitor-IntuneProgress.ps1 +echo C:\Enrollment\shopfloor-setup\Shopfloor\lib\Monitor-IntuneProgress.ps1 echo. -if "%MONITOR_EXIT%"=="0" ( - echo ======================================== - echo Setup complete - no reboot needed - echo ======================================== - echo. - echo The post-reboot DSC install phase is finished. The device is ready. - echo. - pause - exit /b 0 -) - -if "%MONITOR_EXIT%"=="2" ( - echo ======================================== - echo REBOOT REQUIRED - echo ======================================== - echo. - echo The pre-reboot deployment phase is complete. You must reboot now to - echo start the post-reboot DSC install phase, which downloads device-config.yaml - echo and runs the per-app wrappers (Install-eDNC, Install-UDC, Install-VCRedists, - echo Install-OpenText, etc). - echo. - choice /c YN /m "Reboot now" - if errorlevel 2 ( - echo Cancelled - reboot manually when ready. - pause - exit /b 0 - ) - shutdown /r /t 5 - exit /b 0 -) - -echo ERROR: Monitor exited with code %MONITOR_EXIT% pause -exit /b %MONITOR_EXIT% +exit /b 1 + +:launch +echo Launching: %MONITOR% +powershell -NoProfile -Command "Start-Process powershell.exe -Verb RunAs -ArgumentList '-NoProfile','-NoExit','-ExecutionPolicy','Bypass','-File','%MONITOR%'" +exit /b diff --git a/playbook/shopfloor-setup/Standard/Set-MachineNumber.bat b/playbook/shopfloor-setup/Standard/Set-MachineNumber.bat index f5aabf3..c6531a4 100644 --- a/playbook/shopfloor-setup/Standard/Set-MachineNumber.bat +++ b/playbook/shopfloor-setup/Standard/Set-MachineNumber.bat @@ -1,6 +1,38 @@ @echo off REM Set-MachineNumber.bat - Wrapper for Set-MachineNumber.ps1 -REM Runs the PowerShell helper with bypass execution policy so a double-click -REM from the desktop just works. +REM +REM Looks for the .ps1 in three places, in order: +REM 1. %~dp0Set-MachineNumber.ps1 +REM - .bat and .ps1 side-by-side (normal desktop-copied case, repo layout) +REM 2. C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1 +REM - dispatcher-copied location, if this .bat lives somewhere else +REM 3. C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1 +REM - canonical enrollment staging copy +REM +REM Goto-based dispatch - no nested if blocks, no literal parens in echo lines. +REM CMD parses "if (...)" blocks by counting parens and will silently eat any +REM "(" or ")" inside an echo, so keeping the flow flat avoids that class of +REM syntax bomb entirely. -powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0Set-MachineNumber.ps1" +setlocal +set "PS1=%~dp0Set-MachineNumber.ps1" +if exist "%PS1%" goto :run + +set "PS1=C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1" +if exist "%PS1%" goto :run + +set "PS1=C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1" +if exist "%PS1%" goto :run + +echo ERROR: Set-MachineNumber.ps1 not found in any of: +echo %~dp0Set-MachineNumber.ps1 +echo C:\Users\SupportUser\Desktop\Set-MachineNumber.ps1 +echo C:\Enrollment\shopfloor-setup\Standard\Set-MachineNumber.ps1 +echo. +pause +exit /b 1 + +:run +echo Launching: %PS1% +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%PS1%" +exit /b %errorlevel%