Everything West Jefferson specific moved out to a separate repo (pxe-site-wj): NTLARS/UDC/heat-treat backups keyed by WJ machine numbers, CMM and Wax/Trace bay lists, WJ OpenText profiles, site-config.json, the WJ preinstall list, and the WJ shopfloor PC types (Collections, NoCollections, Common, Keyence, CMM, Genspect, HeatTreat, Wax/Trace, Display, PartMarker) with GE-Enforce, machine numbers and the desktop layout. The part-marker bench tools moved to their own repo. Dead scripts removed: build-proxmox-iso, deploy-bios, sync_hardware_models, mirror-from-gold, pull-bios, startnet-template.cmd. Shopfloor is one generic type, gea-shopfloor-standard, with today's timing: - PPKG at first logon (unattend Order 10, unchanged). - Install-AppList.ps1 runs C:\PreInstall\preinstall.json during setup. - A new SYSTEM task, GE Shopfloor Post-PPKG, runs postppkg.json once after the post-PPKG reboot. It also clears AutoLogonCount after the ShopFloor handover (the Users-level monitor can be denied that HKLM write) and removes the Intune Sync task when done. - Both lists ship empty. A site adds entries and installers. - WinPE choice 3 goes straight to the PPKG menu. Staging, fetch and heal carry only the generic payload. The webapp's shopfloor sub-menu editor is gone, and the imaging tiles name the new stages. - The playbook deploys the empty lists only if missing. It now deploys the PowerShell 7 MSI the shopfloor unattend installs, which it never did before. Blancco site settings move into the webapp (Updates > Blancco releases): business location, licensing Wi-Fi SSID and password. They are rendered into the served preferences.xml and applied to every staged release, and are seeded from site-vars (site_name, blancco_wifi_ssid, blancco_wifi_password) only while none exist. The repo XML carries no site values; the plaintext Wi-Fi password is gone from it. New site_name and site_timezone settings: report and evidence-pack site name, server clock and webapp time zone. No more West Jefferson defaults in webapp/config.py or the dashboard title. Sync-BPRT.ps1 gets the server address filled in at download instead of WJ's GE address. USB on Windows: - scripts/Build-PxeUsb.ps1 runs the same checks as build-usb.sh. It writes one FAT32 CIDATA partition (ISO files plus patched grub.cfg plus payload) with diskpart and reads every file back by SHA-256. - Tested with Windows PowerShell 5.1 in the win11 VM, and the layout installs and first-boots in the pxetest VM. - scripts/stage-usb-share.sh stages the build folder on a share. - site-content/ is optional for both builders. KB docs/kb/How_To_Deploy_PXE_Server_New_Site.html: new-site deployment, Windows only, with a site-vars.yml guide. Checks: webapp tests pass (5 suites); the unattend lint and playbook syntax checks pass; every .ps1 parses; there are no WJ hosts, IPs, names or PC types in playbook/, webapp/, scripts/ or autoinstall/.
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. The standard staging path (C:\Enrollment\shopfloor-setup\Shopfloor)
|
|
# ----------------------------------------------------------------------------
|
|
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
|