Fix Display sync loop, PPKG deployment, dnsmasq cron, dpkg configure

- Monitor-IntuneProgress: Display PCs skip DSC phases entirely (no SAS
  token, no DSCInstall.log), complete after Phase 1 identity. Renderer
  hides Phase 2-5 for Display type.
- Playbook: deploy PPKG files and run-enrollment.ps1 from USB to
  enrollment share. Bump dnsmasq restart cron from 15s to 30s.
- build-usb.sh: copy enrollment/ directory (PPKGs) onto USB if present.
- user-data: add dpkg --configure -a after offline .deb install to fix
  packages left in unconfigured state (cron, systemd-timesyncd).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-04-13 14:27:21 -04:00
parent 743bc91996
commit 855d501fc2
4 changed files with 91 additions and 36 deletions

View File

@@ -55,6 +55,7 @@ autoinstall:
cp /mnt/cidata/packages/*.deb /tmp/ cp /mnt/cidata/packages/*.deb /tmp/
dpkg -i /tmp/*.deb 2>/dev/null || true dpkg -i /tmp/*.deb 2>/dev/null || true
dpkg -i /tmp/*.deb 2>/dev/null || true dpkg -i /tmp/*.deb 2>/dev/null || true
dpkg --configure -a 2>/dev/null || true
if command -v nmcli >/dev/null; then if command -v nmcli >/dev/null; then
systemctl enable NetworkManager systemctl enable NetworkManager
fi fi
@@ -78,6 +79,7 @@ autoinstall:
if compgen -G "/mnt/usb/packages/*.deb" > /dev/null; then if compgen -G "/mnt/usb/packages/*.deb" > /dev/null; then
dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true
dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true
dpkg --configure -a 2>/dev/null || true
fi fi
if [ -f /mnt/usb/playbook/pxe_server_setup.yml ]; then if [ -f /mnt/usb/playbook/pxe_server_setup.yml ]; then
cd /mnt/usb/playbook cd /mnt/usb/playbook

View File

@@ -270,6 +270,18 @@ else
echo " No boot-tools/ found (run prepare-boot-tools.sh first)" echo " No boot-tools/ found (run prepare-boot-tools.sh first)"
fi fi
# Copy enrollment directory (PPKGs, run-enrollment.ps1) if present
ENROLLMENT_DIR="$SCRIPT_DIR/enrollment"
if [ -d "$ENROLLMENT_DIR" ]; then
mkdir -p "$MOUNT_POINT/enrollment"
cp -r "$ENROLLMENT_DIR"/* "$MOUNT_POINT/enrollment/" 2>/dev/null || true
PPKG_COUNT=$(find "$MOUNT_POINT/enrollment" -name '*.ppkg' 2>/dev/null | wc -l)
ENROLL_SIZE=$(du -sh "$MOUNT_POINT/enrollment" | cut -f1)
echo " Copied enrollment/ ($ENROLL_SIZE, $PPKG_COUNT PPKGs)"
else
echo " No enrollment/ directory found (PPKGs can be uploaded via webapp later)"
fi
# Optionally copy WinPE deployment images # Optionally copy WinPE deployment images
if [ -n "$WINPE_SOURCE" ] && [ -d "$WINPE_SOURCE" ]; then if [ -n "$WINPE_SOURCE" ] && [ -d "$WINPE_SOURCE" ]; then
echo " Copying WinPE deployment content from $WINPE_SOURCE..." echo " Copying WinPE deployment content from $WINPE_SOURCE..."

View File

@@ -306,6 +306,19 @@
state: directory state: directory
mode: '0777' mode: '0777'
- name: "Deploy PPKG enrollment packages to enrollment share"
shell: cp -f {{ usb_mount }}/enrollment/*.ppkg /srv/samba/enrollment/ 2>/dev/null || true
args:
warn: false
ignore_errors: yes
- name: "Deploy run-enrollment.ps1 to enrollment share"
copy:
src: "{{ usb_mount }}/shopfloor-setup/run-enrollment.ps1"
dest: /srv/samba/enrollment/run-enrollment.ps1
mode: '0644'
ignore_errors: yes
- name: "Deploy shopfloor setup scripts to enrollment share" - name: "Deploy shopfloor setup scripts to enrollment share"
copy: copy:
src: "{{ usb_mount }}/shopfloor-setup/" src: "{{ usb_mount }}/shopfloor-setup/"
@@ -674,7 +687,7 @@
dest: /etc/cron.d/dnsmasq-restart dest: /etc/cron.d/dnsmasq-restart
mode: '0644' mode: '0644'
content: | content: |
@reboot root /bin/sleep 15 && /usr/bin/systemctl restart dnsmasq.service @reboot root /bin/sleep 30 && /usr/bin/systemctl restart dnsmasq.service
# --- Web Management App (Flask) --- # --- Web Management App (Flask) ---
- name: "Create webapp directory" - name: "Create webapp directory"

View File

@@ -480,6 +480,8 @@ function Format-Snapshot {
$lines += " $(Mk $Snap.Phase1.IntuneEnrolled) Intune enrolled" $lines += " $(Mk $Snap.Phase1.IntuneEnrolled) Intune enrolled"
$lines += " $(Mk $Snap.Phase1.EmTaskExists) EnterpriseMgmt sync tasks" $lines += " $(Mk $Snap.Phase1.EmTaskExists) EnterpriseMgmt sync tasks"
$lines += " $(Mk $Snap.Phase1.PoliciesArriving) Policies arriving" $lines += " $(Mk $Snap.Phase1.PoliciesArriving) Policies arriving"
if (-not $skipDsc) {
$lines += "" $lines += ""
$lines += " Phase 2: SFLD configuration" $lines += " Phase 2: SFLD configuration"
$lines += " $(Mk $Snap.Phase2.SfldRoot) SFLD reg key" $lines += " $(Mk $Snap.Phase2.SfldRoot) SFLD reg key"
@@ -518,6 +520,11 @@ function Format-Snapshot {
$lines += "" $lines += ""
$lines += " Phase 5: Final" $lines += " Phase 5: Final"
$lines += " $(Mk $Snap.Phase5.ConsumeCredsTask) SFLD - Consume Credentials task" $lines += " $(Mk $Snap.Phase5.ConsumeCredsTask) SFLD - Consume Credentials task"
} else {
$lines += ""
$lines += " (DSC phases not applicable for $pcType)"
}
$lines += "" $lines += ""
$sinceSync = ((Get-Date) - $LastSync).TotalSeconds $sinceSync = ((Get-Date) - $LastSync).TotalSeconds
$untilNext = ($NextRetrigger - (Get-Date)).TotalSeconds $untilNext = ($NextRetrigger - (Get-Date)).TotalSeconds
@@ -674,6 +681,20 @@ if ($AsTask -and (Test-Path -LiteralPath $syncCompleteMarker)) {
exit 0 exit 0
} }
# PC types that don't receive DSC (no SAS token, no DSCInstall.log).
# For these types, enrollment is "done" once Phase 1 (Identity) completes
# and policies start arriving - there is no Phase 2-5 to wait for.
$pcTypeFile = 'C:\Enrollment\pc-type.txt'
$noDscTypes = @('Display')
$skipDsc = $false
if (Test-Path $pcTypeFile) {
$pcType = (Get-Content $pcTypeFile -First 1).Trim()
if ($noDscTypes -contains $pcType) {
$skipDsc = $true
Write-Host "PC type '$pcType' does not use DSC - will complete after Phase 1."
}
}
try { try {
$qrText = Build-QRCodeText $qrText = Build-QRCodeText
$qrRefreshed = [bool]($qrText -notmatch 'not yet Azure AD joined') $qrRefreshed = [bool]($qrText -notmatch 'not yet Azure AD joined')
@@ -705,6 +726,13 @@ try {
Invoke-SetupComplete Invoke-SetupComplete
} }
# No-DSC types (Display, Lab): complete once Phase 1 identity is solid
if ($skipDsc -and $snap.Phase1.AzureAdJoined -and $snap.Phase1.IntuneEnrolled -and $snap.Phase1.PoliciesArriving) {
Write-Host ""
Write-Host "Phase 1 complete - no DSC needed for $pcType. Finishing up." -ForegroundColor Green
Invoke-SetupComplete
}
# Reboot check (boot-loop-safe). Only prompt once Phase 1 (Identity), # Reboot check (boot-loop-safe). Only prompt once Phase 1 (Identity),
# Phase 2 (SFLD config), and Phase 3's first two items (deploy log # Phase 2 (SFLD config), and Phase 3's first two items (deploy log
# present + pre-reboot deployment complete) are all green. Don't rush # present + pre-reboot deployment complete) are all green. Don't rush