Contexte
Dans un précédent article, nous avons présenté une solution basée sur deux scripts PowerShell permettant d’installer une application Win32 après la fin de l’ESP (Enrollment Status Page) lors d’un enrôlement Windows Autopilot.
Après déploiement en production, deux comportements inattendus ont été identifiés. Cet article décrit ces problèmes et les corrections apportées dans la version 2 des scripts.
Problèmes identifiés
Problème 1 — La tâche planifiée se déclenchait sur la session defaultuser0
Dans un scénario Autopilot Pre-Provisioning (White Glove), la phase Device Setup est exécutée par le processus CloudExperienceHostBroker.exe. À la fin de cette phase, ce processus déclenche un redémarrage automatique du device.
Après ce redémarrage, Windows ouvre automatiquement une session sous le compte defaultuser0 et affiche la page Reseal — la page de fin du Pre-Provisioning qui invite le technicien à sceller le device avant sa livraison à l’utilisateur.
Le point clé : la session defaultuser0 est déjà active à ce stade, même si personne n’a encore interagi avec le device. La tâche planifiée avec le trigger AtLogOn se déclenchait donc immédiatement après le redémarrage, pendant que le device attendait simplement sur la page Reseal sans aucune action utilisateur.
Autopilot Pre-Provisioning — Device Setup
│
▼
CloudExperienceHostBroker.exe termine la phase Device Setup
│
▼
Redémarrage automatique du device
│
▼
Windows ouvre la session defaultuser0
Device affiche la page Reseal (en attente du technicien)
│
▼ ← à ce moment, defaultuser0 est déjà connecté
Trigger AtLogOn se déclenche immédiatement ❌
│
▼
Script d'installation démarre en arrière-plan
(sans que personne n'ait cliqué quoi que ce soit)
│
▼
Boucle ESP → attend jusqu'à 5h inutilement
Solution _ Problème 1 : Guard defaultuser0
Un contrôle a été ajouté au début du script pour arrêter immédiatement l’installation si la session détectée est defaultUser0
# ------------------------------------------------------------------
# 0. Guard 1 : defaultuser0 → exit immédiat ──
# ------------------------------------------------------------------
$currentUser = (Get-WmiObject -Class Win32_ComputerSystem -ErrorAction SilentlyContinue).UserName
Write-Log "Current logged user : $currentUser"
if ([string]::IsNullOrEmpty($currentUser) -or $currentUser -like "*defaultuser0*") {
Write-Log "defaultuser0 session - exiting immediately. Task will retry at real user logon."
exit 0
}
defaultuser0 logon
│
▼
Guard Win32_ComputerSystem.UserName = "defaultuser0"
│
▼
exit 0 immédiat (< 1 seconde)
│
▼
Tâche planifiée reste active → réessaie au vrai logon utilisateur ✅
Problème 2 — Timeout de la boucle ESP sans vérification de l’état réel
La boucle d’attente ESP de la version 1 utilisait la condition suivante :
do {
# vérifie les checks ESP
if ($c1 -and $c2 -and $c3 -and $c4) { break }
Start-Sleep -Seconds $pollInterval
$espWaitSeconds += $pollInterval
} while ($espWaitSeconds -lt $espMaxWait)
# ← installation lancée ici, sans savoir pourquoi on est sorti de la boucle
Si le timeout de 5h était atteint sans que les checks ESP soient validés, le script sortait de la boucle et continuait vers l’installation — exactement comme si l’ESP était terminée.
Cause
La condition while ($espWaitSeconds -lt $espMaxWait) provoque deux sorties possibles :
| Sortie | Raison | Comportement v1 |
|---|---|---|
break | Tous les checks $true | ✅ Installation correcte |
| Fin de boucle | Timeout 5h, checks toujours $false | ❌ Installation quand même |
Solution _ Problème 2 : Flag $espCompleted
Un flag explicite permet de distinguer les deux cas de sortie :
$espCompleted = $false # ← nouveau flag
do {
$c1 = Test-IsSyncDone
$c2 = Test-SidecarCompleted
$c3 = Test-HasProvisioningCompleted
$c4 = Test-TrackingPoliciesCreated
Write-Log "ESP check at ${espWaitSeconds}s → IsSyncDone=$c1 | Sidecar=$c2 | Provisioning=$c3 | Policies=$c4"
if ($c1 -and $c2 -and $c3 -and $c4) {
$espCompleted = $true # ← toutes conditions validées
break
}
Start-Sleep -Seconds $pollInterval
$espWaitSeconds += $pollInterval
} while ($espWaitSeconds -lt $espMaxWait)
# ── Vérifier POURQUOI on est sorti ──
if (-not $espCompleted) {
Write-Log "TIMEOUT: ESP did not complete after $($espMaxWait/3600)h"
Write-Log "Checks → IsSyncDone=$c1 | Sidecar=$c2 | Provisioning=$c3 | Policies=$c4"
Write-Log "Aborting - scheduled task remains for next logon retry."
exit 1 # ← ne pas installer, conditions pas remplies
}
Write-Log "ESP completed after ${espWaitSeconds}s - proceeding with installation."
# ← ici on est certain que tous les checks sont true
Boucle ESP démarre
│
├── Checks tous true → $espCompleted = true → break → Installe ✅
│
└── Timeout 5h → $espCompleted = false → exit 1
│
▼
Tâche planifiée reste active
Retry au prochain logon utilisateur ✅
Récapitulatif des corrections
| # | Problème | Version 1 | Version 2 |
|---|---|---|---|
| 1 | Tâche déclenchée sur defaultuser0 | Trigger AtLogOn ❌ | ConsoleConnect + Guard ✅ |
| 2 | Timeout ESP → installation forcée | Installe quand même ❌ | Flag $espCompleted → exit 1 ✅ |
Script 1
# ============================================================
# Install_XXXX_PostESP.ps1 - Payload Script (runs at logon)
# Purpose : Wait for Autopilot ESP Phase 3 to finish, then
# install XXXX MSI with retry logic.
#
# Triggered by : Scheduled Task "XXXXInstallIfMissing"
# RunAs : SYSTEM
# Author : Lazher YAAKOUBI
# Version : 2.7.3
# ============================================================
$XXXXFolder = "C:\ProgramData\XXXX\XXXX_Source"
$LogPath = "$env:WINDIR\Temp\install_XXXX.log"
$LogFile = "$env:WINDIR\Temp\XXXX-install-attempt.log"
$TaskName = "XXXXInstallIfMissing"
$filePath = "C:\Program Files\XXXX\XXXX"
# Check MSI file...
$InstallerSourceMSI = Get-ChildItem -Path $XXXXFolder -Filter "*.msi" | Select-Object -First 1
if (-not $InstallerSourceMSI) {
Add-Content -Path $LogFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') : ERROR - No MSI found in $XXXXFolder. Exiting." -ErrorAction SilentlyContinue
exit 1
}
$InstallerName = $InstallerSourceMSI.Name
$InstallerPath = Join-Path $XXXXFolder $InstallerName
# e.g. "XXXX-windows-4.2.0.172-x64" -> "4.2.0.172"
$MSIVersion = [System.IO.Path]::GetFileNameWithoutExtension($InstallerPath) `
-replace 'XXXX-windows-', '' `
-replace '-x64', '' `
-replace '-corp', '' `
-replace '-[a-zA-Z].*$', '' # catch any other suffix
function Write-Log {
param([string]$Message)
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') : $Message"
Add-Content -Path $LogFile -Value $line -ErrorAction SilentlyContinue
}
# ------------------------------------------------------------------
# 0. Guard 1 : defaultuser0 → exit immédiat ──
# ------------------------------------------------------------------
$currentUser = (Get-WmiObject -Class Win32_ComputerSystem -ErrorAction SilentlyContinue).UserName
Write-Log "Current logged user : $currentUser"
if ([string]::IsNullOrEmpty($currentUser) -or $currentUser -like "*defaultuser0*") {
Write-Log "defaultuser0 session - exiting immediately. Task will retry at real user logon."
exit 0
}
# -------------------------------------------------------
# 1. Guard 2 : skip if XXXX is already installed
# -------------------------------------------------------
if (Test-Path $filePath) {
if ($rawFileVersion -eq $MSIVersion) {
#>
Write-Log "XXXX $rawFileVersion already installed - removing scheduled task and exiting."
if (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue)
{
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
Write-Log "Scheduled task '$TaskName' removed."
} else
{
Write-Log "Scheduled task '$TaskName' does not exist."
}
exit 0
<#
} else {
Write-Log "Installed version ($rawFileVersion) differs from MSI version ($MSIVersion) - proceeding with installation."
}
#>
}
Write-Log "Waiting for ESP completion (using IME-equivalent checks)..."
$espWaitSeconds = 0
$espMaxWait = 18000 # 5 h
$pollInterval = 15 # seconds between checks
$espCompleted = $false
# ---------- Check 1: IsSyncDone ----------
function Test-IsSyncDone {
$enrollments = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Enrollments" -ErrorAction SilentlyContinue
foreach ($enrollment in $enrollments) {
$providerID = (Get-ItemProperty -Path $enrollment.PSPath -Name "ProviderID" -ErrorAction SilentlyContinue).ProviderID
if ($providerID -ne "MS DM Server") { continue }
$firstSyncPath = Join-Path $enrollment.PSPath "FirstSync"
$userSidKeys = Get-ChildItem -Path $firstSyncPath -ErrorAction SilentlyContinue
foreach ($sidKey in $userSidKeys) {
$isSyncDone = (Get-ItemProperty -Path $sidKey.PSPath -Name "IsSyncDone" -ErrorAction SilentlyContinue).IsSyncDone
if ($isSyncDone -eq 1) { return $true }
}
}
return $false
}
# ---------- Check 2: Sidecar InstallationState ----------update must be performed to resolve issues related to failed autopilot device!!!
function Test-SidecarCompleted {
$enrollments = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Enrollments" -ErrorAction SilentlyContinue
foreach ($enrollment in $enrollments) {
$sidecarPath = Join-Path $enrollment.PSPath "PolicyProviders\Sidecar"
$state = (Get-ItemProperty -Path $sidecarPath -Name "InstallationState" -ErrorAction SilentlyContinue).InstallationState
if ($state -eq "Completed") { return $true }
}
# If Sidecar key doesn't exist at all, it's not blocking ESP
return $true
}
# ---------- Check 3: HasProvisioningCompleted (WMI) ----------
function Test-HasProvisioningCompleted {
try {
$result = Get-WmiObject -Namespace "root\cimv2\mdm\dmmap" `
-Query "SELECT HasProvisioningCompleted FROM MDM_EnrollmentStatusTracking_Setup01" `
-ErrorAction Stop
if ($null -ne $result -and $result.HasProvisioningCompleted -eq $true) { return $true }
} catch {
return $true
}
return $false
}
# ---------- Check 4: TrackingPoliciesCreated (WMI) ----------
function Test-TrackingPoliciesCreated {
try {
$result = Get-WmiObject -Namespace "root\cimv2\mdm\dmmap" `
-Query "SELECT TrackingPoliciesCreated FROM MDM_EnrollmentStatusTracking_PolicyProviders03_01" `
-ErrorAction Stop
if ($null -ne $result -and $result.TrackingPoliciesCreated -eq $true) { return $true }
} catch {
return $true
}
return $false
}
# ---------- Check 5: WWAHost.exe Running ----------
function Test-WWAHostVisible {
$wwa = Get-Process -Name "WWAHost" -ErrorAction SilentlyContinue |
Where-Object { $_.SessionId -gt 0 }
return ($null -ne $wwa)
}
# ---------- Main polling loop ----------
do {
$c1 = Test-IsSyncDone
$c2 = Test-SidecarCompleted
$c3 = Test-HasProvisioningCompleted
$c4 = Test-TrackingPoliciesCreated
$c5 = Test-WWAHostVisible
Write-Log ("ESP/WHfB status at ${espWaitSeconds}s - " +
"IsSyncDone=$c1 | SidecarCompleted=$c2 | " +
"HasProvisioningCompleted=$c3 | TrackingPoliciesCreated=$c4 | " +
"WWAHostVisible=$c5")
if ($c1 -and $c2 -and $c3 -and $c4 -and (-not $c5)) {
Write-Log "All checks passed - ESP done and WWAHost closed. Proceeding after ${espWaitSeconds}s."
$espCompleted = $true
break
}
Start-Sleep -Seconds $pollInterval
$espWaitSeconds += $pollInterval
} while ($espWaitSeconds -lt $espMaxWait)
if (-not $espCompleted)
{
Write-Log "TIMEOUT ESP did not completed after $($espMaxWait/3600)H - IsSyncDone = $c1, SidecarCompleted = $c2, HasProvisioningCompleted = $c3, TrackingPoliciesCreated = $c4"
Write-Log "Aborting installation - scheduled task remains for next logon retry..."
exit 1
}
Write-Log "ESP completed after ${espWaitSeconds}s - proceeding with installation..."
# -------------------------------------------------------
# 2. INSTALL XXXX WITH RETRY LOGIC
# -------------------------------------------------------
if (-Not (Test-Path $InstallerPath)) {
Write-Log "ERROR: Installer not found at $InstallerPath"
exit 1
}
$msiArgs = "/i `"$InstallerPath`" /qn /l*v `"$LogPath`" " +
"STRICTENFORCEMENT=1 " +
"CLOUDNAME=XXXXX " +
"USERDOMAIN=XXXXX " +
"POLICYTOKEN=343837323A333A30323236653933352D383433632D343531352D396338312D326333383135613036613665 " +
"REBOOT=ReallySuppress"
$maxAttempts = 10
$attempts = 0
$success = $false
while (-not $success -and $attempts -lt $maxAttempts) {
$attempts++
Write-Log "Installation attempt $attempts of $maxAttempts"
try {
$process = Start-Process "msiexec.exe" `
-ArgumentList $msiArgs `
-Wait -PassThru -NoNewWindow -ErrorAction Stop
Write-Log "msiexec exit code: $($process.ExitCode)"
switch ($process.ExitCode) {
0 { $success = $true ; Write-Log "Installation succeeded." }
3010 { $success = $true ; Write-Log "Installation succeeded (reboot required - suppressed)." }
1618 { Write-Log "Another MSI installation in progress (1618) - retrying in 3 min..." ; Start-Sleep -Seconds 180 }
1619 { Write-Log "Package could not be opened (1619) - retrying in 3 min..." ; Start-Sleep -Seconds 180 }
default {
Write-Log "Unexpected exit code $($process.ExitCode) - retrying in 3 min..."
Start-Sleep -Seconds 180
}
}
} catch {
Write-Log "ERROR launching msiexec: $_"
Start-Sleep -Seconds 180
}
}
# -------------------------------------------------------
# 3. FINAL STATUS
# -------------------------------------------------------
if ($success) {
Write-Log "XXXX installed successfully - removing scheduled task."
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
Write-Log "Cleaning up XXXX_Source folder (full removal)..."
#Remove-Item -Path "$XXXXFolder\*" -Exclude $InstallerName -Confirm:$false -Recurse -ErrorAction Stop
Remove-Item -Path "$XXXXFolder" -Confirm:$false -Recurse -ErrorAction Stop
exit 0
} else {
Write-Log "FAILED after $maxAttempts attempts - scheduled task remains for next logon retry."
exit 1
}
Script 2 Copy
# ============================================================
# Copy-XXXX-task.ps1 - Payload Script (runs at AutoPilot phase 2)
# Purpose : Update source files if needed and create scheduled task.
#
# Triggered by : Intune application deployement services"
# RunAs : SYSTEM
# Author : Lazher YAAKOUBI
# Version : 2.7.3
# ============================================================
# Folder where XXXX installation files must be copied
$XXXXFolder = "C:\ProgramData\XXXX\XXXX_Source"
$LogFile = "$env:WINDIR\Temp\XXXX-Copy-TaskCreation.log"
#$filePath = "C:\Program Files\XXXX\"
# Find the MSI and script files
$InstallerSourceMSI = Get-ChildItem -Path $PSScriptRoot -Filter "*.msi" | Select-Object -First 1
$InstallerSourcePS1 = Get-ChildItem -Path $PSScriptRoot -Filter "*.ps1" | Where-Object { $_.Name -like "*Install_XXXX*" } | Select-Object -First 1
function Write-Log {
param([string]$Message)
$line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') : $Message"
Add-Content -Path $LogFile -Value $line -ErrorAction SilentlyContinue
}
# Check source files existance MSI&PS1
if (-not $InstallerSourceMSI) {
Write-Log "Error: No MSI file found in $PSScriptRoot."
exit
}
if (-not $InstallerSourcePS1) {
Write-Log "Error: No installation script found in $PSScriptRoot."
exit
}
$InstallerName = $InstallerSourceMSI.Name
$InstallerSource = $InstallerSourceMSI.FullName
$ScriptName = $InstallerSourcePS1.Name
$ScriptSource = $InstallerSourcePS1.FullName
$InstallerDest = Join-Path $XXXXFolder $InstallerName
$ScriptDest = Join-Path $XXXXFolder $ScriptName
# Name of the scheduled task
$TaskName = "XXXXInstallIfMissing"
# Check if folder exists
if (-Not (Test-Path $XXXXFolder)) {
#
try {
New-Item -ItemType Directory -Path $XXXXFolder -Force | Out-Null
Write-Log "Folder $XXXXFolder created successfully."
} catch {
Write-Log "Error: Failed to create folder $XXXXFolder. $_"
exit
}
Copy-Item -Path $InstallerSource -Destination $InstallerDest -Force
Copy-Item -Path $ScriptSource -Destination $ScriptDest -Force
} else {
Write-Log "XXXX folder already exists. Checking for updates..."
# Update MSI if needed
$ExistingMSI = Get-ChildItem -Path $XXXXFolder -Filter "*.msi" -File
if ($ExistingMSI) {
$ExistingMSIVersion = [System.IO.Path]::GetFileNameWithoutExtension($ExistingMSI.Name) -replace 'XXXX-windows-', '' -replace '-x64', '' -replace '-corp', ''
$NewMSIVersion = [System.IO.Path]::GetFileNameWithoutExtension($InstallerName) -replace 'XXXX-windows-', '' -replace '-x64', '' -replace '-corp', ''
# Version comparison
if (([version]$ExistingMSIVersion) -lt ([version]$NewMSIVersion)) {
Write-Log "Newer MSI version detected ($NewMSIVersion). Updating..."
Remove-Item -Path "$XXXXFolder\*" -Force
Copy-Item -Path $InstallerSource -Destination $InstallerDest -Force
Copy-Item -Path $ScriptSource -Destination $InstallerDest -Force
} else {
Write-Log "Existing MSI version ($ExistingMSIVersion) is up to date."
}
} else {
Copy-Item -Path $InstallerSource -Destination $InstallerDest -Force
}
# Always overwrite the script so it stays in sync with the MSI
Copy-Item -Path $ScriptSource -Destination $ScriptDest -Force
Write-Log "Installation script updated."
}
$TaskXML = @"
<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task" version="1.3">
<Triggers>
<LogonTrigger>
<Enabled>true</Enabled>
</LogonTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>SYSTEM</UserId>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>powershell.exe</Command>
<Arguments>-ExecutionPolicy Bypass -WindowStyle Hidden -File "$ScriptDest"</Arguments>
</Exec>
</Actions>
</Task>
"@
# Check if the scheduled task already exists
$ExistingTask = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if ($ExistingTask) {
Write-Log "Scheduled task '$TaskName' already exists. Skipping creation."
} else {
try {
Register-ScheduledTask -TaskName $TaskName -Xml $TaskXML -Force -ErrorAction Stop
Write-Log "Scheduled task '$TaskName' created successfully." #| Out-File -FilePath "$env:WINDIR\Temp\TaskCreation.log" -Append
} catch {
Write-Log "Failed to create scheduled task: $_" #| Out-File -FilePath "$env:WINDIR\Temp\TaskCreation.log" -Append
}
}




