Bundles drift left uncommitted from prior sessions and the UDC matrix
verify entry added today.
Drift items (all per session-progress.md, completed in earlier sessions
but never staged):
- playbook/check-bios.cmd (deleted, moved to BIOS/check-bios.cmd)
- playbook/migrate-to-wifi.ps1 (made no-op 2026-04-24 after the dnsmasq
no-gateway fix removed the wired-NIC race that motivated it)
- playbook/preinstall/oracle/Install-Oracle11r2.cmd (post-OUI .ora copy
added 2026-04-24)
- playbook/preinstall/oracle/tnsnames.ora (live tnsnames, 469 KB,
deployed alongside the wrapper 2026-04-24)
- playbook/pxe_server_setup.yml (dnsmasq dhcp-option=3,6 commented,
Oracle .ora deploy task added 2026-04-24)
- playbook/shopfloor-setup/BIOS/{check-bios.cmd, models.txt} (BIOS
detection refinements)
- playbook/shopfloor-setup/Shopfloor/Force-Lockdown.bat
- playbook/shopfloor-setup/Shopfloor/Monitor-IntuneProgress.ps1
- playbook/shopfloor-setup/Shopfloor/SetShopfloorAutoLogon.bat (new)
- playbook/shopfloor-setup/Shopfloor/09-Install-PrinterInstallerMap.ps1
(new, places PrinterInstallerMap.exe + Public Desktop shortcut at
imaging time; manifest entry self-heals on tamper)
- playbook/shopfloor-setup/Shopfloor/lib/Show-IntuneDeviceQR.ps1 (new,
standalone QR rendering for site that wanted just that piece)
- playbook/shopfloor-setup/gea-shopfloor-collections/{Install-eMxInfo.cmd.template,
Restore-UDCData.ps1} (these were uncommitted in pre-rename Standard/;
git mv didn't catch them because they were untracked at the time)
- docs/shopfloor-machine-imaging-guide.md (operator-facing how-to)
Matrix:
- common.test/matrix.json: add UDC verify entry to gea-shopfloor-collections
row. Surfaces UDC silent-install issue (item H pending) instead of
letting it pass silently.
.gitignore:
- PrinterInstallerMap.exe (142 MB) excluded. Track via LFS or stage on
PXE server only - too big for regular git history. Untouched on disk
so existing local copy still works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
208 lines
8.1 KiB
PowerShell
208 lines
8.1 KiB
PowerShell
# Show-IntuneDeviceQR.ps1 - Standalone version of the QR-code panel from
|
|
# Monitor-IntuneProgress.ps1, extracted for use at sites that only need the
|
|
# device-ID display (no DSC monitoring, no Intune sync triggers, no reboot
|
|
# orchestration).
|
|
#
|
|
# Polls dsregcmd /status every 15s until the device reports an Azure AD
|
|
# DeviceId, then renders that ID as a half-block QR code in the console
|
|
# along with the literal text. Stays open until the user presses a key.
|
|
#
|
|
# REQUIREMENTS
|
|
# - Windows PowerShell 5.1 (or PS 7) with .NET Framework available
|
|
# - QRCoder.dll alongside this script (or at one of the fallback paths
|
|
# below). Single ~75 KB native dependency, no internet needed.
|
|
#
|
|
# USAGE
|
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -File Show-IntuneDeviceQR.ps1
|
|
#
|
|
# Or double-click via a one-line .bat:
|
|
# powershell.exe -NoProfile -ExecutionPolicy Bypass -NoExit -File "%~dp0Show-IntuneDeviceQR.ps1"
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[int]$PollSeconds = 15,
|
|
[string]$QRCoderDllPath = ''
|
|
)
|
|
|
|
$ErrorActionPreference = 'Continue'
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Locate QRCoder.dll. Search order:
|
|
# 1. -QRCoderDllPath if explicitly passed
|
|
# 2. Next to this script ($PSScriptRoot\QRCoder.dll)
|
|
# 3. Current working directory
|
|
# 4. WJ canonical staging path (kept for cross-site portability)
|
|
# ----------------------------------------------------------------------------
|
|
function Resolve-QRCoderDll {
|
|
param([string]$Override)
|
|
|
|
$candidates = @()
|
|
if ($Override) { $candidates += $Override }
|
|
if ($PSScriptRoot) { $candidates += (Join-Path $PSScriptRoot 'QRCoder.dll') }
|
|
$candidates += (Join-Path (Get-Location) 'QRCoder.dll')
|
|
$candidates += 'C:\Enrollment\shopfloor-setup\Shopfloor\QRCoder.dll'
|
|
|
|
foreach ($c in $candidates) {
|
|
if ($c -and (Test-Path -LiteralPath $c)) { return (Resolve-Path -LiteralPath $c).Path }
|
|
}
|
|
return $null
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Get the AAD DeviceId from dsregcmd. Returns $null until the device is
|
|
# Azure AD joined AND dsregcmd has populated the field.
|
|
# ----------------------------------------------------------------------------
|
|
function Get-AadDeviceId {
|
|
try {
|
|
$dsreg = dsregcmd /status 2>&1
|
|
} catch {
|
|
return $null
|
|
}
|
|
$line = $dsreg | Select-String 'DeviceId' | Select-Object -First 1
|
|
if (-not $line) { return $null }
|
|
$parts = $line.ToString().Split(':')
|
|
if ($parts.Count -lt 2) { return $null }
|
|
$id = $parts[1].Trim()
|
|
if ([string]::IsNullOrWhiteSpace($id)) { return $null }
|
|
return $id
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Half-block QR renderer. Each output character is 1 module wide, 2 modules
|
|
# tall, using:
|
|
# U+2588 = both top and bottom modules set
|
|
# U+2580 = top only
|
|
# U+2584 = bottom only
|
|
# space = neither
|
|
# Cuts QR height in half vs full-block rendering. Adds a 4-module quiet
|
|
# zone manually since QRCoder's ModuleMatrix excludes it by default.
|
|
# ----------------------------------------------------------------------------
|
|
function Format-QrHalfBlocks {
|
|
param([string]$Payload, [string]$DllPath, [int]$IndentSpaces = 8)
|
|
|
|
Add-Type -Path $DllPath
|
|
$gen = New-Object QRCoder.QRCodeGenerator
|
|
$data = $gen.CreateQrCode($Payload, [QRCoder.QRCodeGenerator+ECCLevel]::L)
|
|
|
|
$matrix = $data.ModuleMatrix
|
|
$size = $matrix.Count
|
|
$pad = 4
|
|
$total = $size + 2 * $pad
|
|
|
|
$upper = [char]0x2580
|
|
$lower = [char]0x2584
|
|
$full = [char]0x2588
|
|
$left = ' ' * $IndentSpaces
|
|
|
|
$lines = New-Object System.Collections.Generic.List[string]
|
|
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.Add($sb.ToString())
|
|
}
|
|
return $lines
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Bigger console so the QR + text frame fits in one viewport.
|
|
# ----------------------------------------------------------------------------
|
|
try {
|
|
$rui = $Host.UI.RawUI
|
|
$maxH = $rui.MaxPhysicalWindowSize.Height
|
|
$targetWindow = [Math]::Min(58, [int]$maxH)
|
|
$targetBuffer = [Math]::Max($targetWindow, 200)
|
|
|
|
$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 {}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Wait for QRCoder.dll
|
|
# ----------------------------------------------------------------------------
|
|
$dllPath = Resolve-QRCoderDll -Override $QRCoderDllPath
|
|
|
|
# Mark-of-the-Web: when files arrive via SMB / zip download / email,
|
|
# Windows attaches a Zone.Identifier alternate data stream that flags the
|
|
# file as "from another computer". Add-Type then refuses to load the DLL
|
|
# with "Operation is not supported" / "Could not load file or assembly".
|
|
# Unblock-File silently strips the ADS. Best-effort: errors are non-fatal
|
|
# (file may already be unblocked, or the user may not have write access).
|
|
if ($dllPath) {
|
|
try { Unblock-File -LiteralPath $dllPath -ErrorAction SilentlyContinue } catch {}
|
|
}
|
|
if ($PSCommandPath) {
|
|
try { Unblock-File -LiteralPath $PSCommandPath -ErrorAction SilentlyContinue } catch {}
|
|
}
|
|
|
|
if (-not $dllPath) {
|
|
Write-Host ""
|
|
Write-Host "ERROR: QRCoder.dll not found." -ForegroundColor Red
|
|
Write-Host "Searched (in order):"
|
|
if ($QRCoderDllPath) { Write-Host " - $QRCoderDllPath (-QRCoderDllPath)" }
|
|
if ($PSScriptRoot) { Write-Host " - $(Join-Path $PSScriptRoot 'QRCoder.dll')" }
|
|
Write-Host " - $(Join-Path (Get-Location) 'QRCoder.dll')"
|
|
Write-Host " - C:\Enrollment\shopfloor-setup\Shopfloor\QRCoder.dll"
|
|
Write-Host ""
|
|
Write-Host "Drop QRCoder.dll alongside this script and re-run." -ForegroundColor Yellow
|
|
exit 2
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Poll loop - retry every $PollSeconds until DeviceId appears
|
|
# ----------------------------------------------------------------------------
|
|
Write-Host ""
|
|
Write-Host " Waiting for Azure AD device registration..." -ForegroundColor Cyan
|
|
Write-Host " Polling dsregcmd every $PollSeconds s. Press Ctrl+C to abort." -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
|
|
$attempt = 0
|
|
while ($true) {
|
|
$attempt++
|
|
$deviceId = Get-AadDeviceId
|
|
if ($deviceId) { break }
|
|
|
|
Write-Host (" [{0}] attempt {1,3} no DeviceId yet, retrying in {2}s..." -f (Get-Date -Format 'HH:mm:ss'), $attempt, $PollSeconds) -ForegroundColor DarkGray
|
|
Start-Sleep -Seconds $PollSeconds
|
|
}
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# Render
|
|
# ----------------------------------------------------------------------------
|
|
Clear-Host
|
|
Write-Host ""
|
|
Write-Host " GE Aerospace -- Intune Device ID" -ForegroundColor Cyan
|
|
Write-Host " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host " Device ID: $deviceId" -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
try {
|
|
$qrLines = Format-QrHalfBlocks -Payload $deviceId -DllPath $dllPath
|
|
foreach ($l in $qrLines) { Write-Host $l }
|
|
} catch {
|
|
Write-Host " (QR generation failed: $($_.Exception.Message))" -ForegroundColor Red
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " Scan with phone camera or Intune admin app." -ForegroundColor DarkGray
|
|
Write-Host ""
|
|
Write-Host " Press any key to close..." -ForegroundColor Yellow
|
|
try { [void][Console]::ReadKey($true) } catch { [void](Read-Host) }
|
|
exit 0
|