I have been looking for a help to create a Intune package to run one of my power shell script for user Interaction with defer prompts…
I have been following the v4 document by adding the Start-ADTProcess to run my power shell script with in the Invoke-AppDeployToolkit.ps1 but it is failed (attached the script and error message)
Looking at the error message, it looks like the ‘Upgrade_Windows.ps1’ script being called is probably executing an application that is not valid for the OS platform you are executing it on…
We may need a bit more info to help
What Operating System are you running this on and is it an x86 (32-bit), x64 (64-bit) or ARM64 (64-bit) version?
Do you know what the app is that is being called?
Is this app supported on the architecture or OS of the machine where you are running it?
Thanks for the reply.
What Operating System are you running this on and is it an x86 (32-bit), x64 (64-bit) or ARM64 (64-bit) version? Windows 10 x64
Do you know what the app is that is being called? it’s a power shell script
I think you misunderstood, I can see that it calls a PowerShell script, but what is the App that the PowerShell script is running....
I think your Screen grab of the comment section from the code gives some clues on lines 18 and 19
If you are allowed to share the script, it might be helpful to paste a copy of the contents using the Preformatted Text button on the toolbar <\>
Paste your code between the two lines of 3 `
It gives us an idea what your code is doing
# ---------------------------------------------------------------------------------------------------
# Begin Parameter Definitions (user-overridable)
# ---------------------------------------------------------------------------------------------------
param (
[string]$Win11WorkingDirectory = "C:\Temp\Win11",
# IMPORTANT: Set $ServiceUIPath to your own Azure Blob Storage URL containing ServiceUI.exe
# OR
# Place ServiceUI.exe in the root of your Win32 package and set this to: "$PSScriptRoot\ServiceUI.exe"
# (This script does NOT host or provide ServiceUI.exe.)
[string]$ServiceUIPath = "$PSScriptRoot\ServiceUI.exe",
[int]$MinRequiredFreeSpaceGB = 30
)
# ---------------------------------------------------------------------------------------------------
# End Parameter Definitions (user-overridable)
# ---------------------------------------------------------------------------------------------------
# ---------------------------------------------------------------------------------------------------
# Relaunch Script in 64-bit PowerShell (if currently running in 32-bit on x64 system)
# ---------------------------------------------------------------------------------------------------
if ("$env:PROCESSOR_ARCHITEW6432" -ne "ARM64") {
Write-Host "Not on ARM64"
if (Test-Path "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe") {
# Relaunch as 64-bit
& "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "$PSCommandPath"
Write-Host "Relaunched as a 64-bit process"
Exit $lastexitcode
}
}
# ------------------------------------
# Begin Defining Script Variables
# ------------------------------------
# ------------------------------------
# Script Version Info
# ------------------------------------
[int]$ScriptVersion = 26
# ========================================
# Variables: Logging
# ========================================
$Now = Get-Date -Format MM-dd-yyyy-HH-mm-ss
$LogFile = "C:\Windows\Logs\Win11_Upgrade-$Now.log"
$DriverLog = "C:\Windows\Logs\UnsignedPrinterDrivers.csv"
$TranscriptFile = "C:\Windows\Logs\Win11_Upgrade_Transcript-$Now.log"
Start-Transcript -Path $TranscriptFile
Write-Host "Starting upgrade using script version: $($ScriptVersion)"
# ========================================
# Variables: Script Configuration
# ========================================
$upgradeArgs = "/quietinstall /skipeula /auto upgrade /copylogs $Win11WorkingDirectory"
# ========================================
# Languages to keep (use RFC 5646 format)
# Use a comma seperated list if you need more languages
# ========================================
$PreserveLanguages = @('en-US')
# ========================================
# Variables: ServiceUI
# ========================================
$ServiceUIDestination = "$Win11WorkingDirectory\ServiceUI.exe"
# ========================================
# Variables: Used for the compat appraiser
# ========================================
$CompatAppraiserPath = 'C:\Windows\system32\CompatTelRunner.exe'
$RegistryPathAppCompat = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\TargetVersionUpgradeExperienceIndicators\'
$RegValueGStatus = 'GStatus'
$RegValueUpgEx = 'UpgEx'
$RegValueRedReason = 'RedReason'
# ========================================
# Variables: For idle process check (mostly unused since v21)
# ========================================
$monitoredExeName = "windows10upgraderapp.exe"
$monitoredProcName = [System.IO.Path]::GetFileNameWithoutExtension($monitoredExeName)
[int]$cpuIdleThresholdMinutes = 5
[int]$timeoutSeconds = 7200
[int][int]$checkIntervalSeconds = 15
$elapsedSeconds = 0
[int]$checkIntervalSeconds = 15
[int]$maxWaitSeconds = 7200 # 2 hours
# ========================================
# Variables: Default time (min) to sleep when calling sleepNow function
# ========================================
[int]$sleepTime = 30
# ------------------------------------
# End Defining Script Variables
# ------------------------------------
# Create the working directory if it doesn't exist
if (-not (Test-Path $Win11WorkingDirectory)) {
mkdir $Win11WorkingDirectory
}
# Make sure that we are not already in Windows 11
$isWin11 = (Get-WmiObject Win32_OperatingSystem).Caption -Match "Windows 11"
if ($isWin11) {
write-host "Windows 11"
Stop-Transcript
Exit 0
}
Else {
write-host "We are in Windows 10."
# ------------------------------------
# Begin Functions
# ------------------------------------
function LogMessage {
<#
.SYNOPSIS
Writes a formatted log message to both the console and a persistent log file, including the line number.
.DESCRIPTION
Logs messages with timestamp, severity level (INFO, WARN, ERROR), component name, and the script line number.
.PARAMETER Message
The message text to log.
.PARAMETER Component
An optional label for the log source (e.g., a function name). Defaults to 'Script'.
.PARAMETER Type
1 = INFO, 2 = WARN, 3 = ERROR
.OUTPUTS
Writes to both screen and $LogFile
#>
param (
[Parameter(Mandatory = $true)]
[string]$Message,
[string]$Component = "Script",
[ValidateSet('1', '2', '3')]
[int]$Type = 1
)
$timeStamp = Get-Date -Format "HH:mm:ss"
$dateStamp = Get-Date -Format "yyyy-MM-dd"
$levelText = switch ($Type) {
1 { "INFO" }
2 { "WARN" }
3 { "ERROR" }
}
# Try to get line number from call stack
$callStack = Get-PSCallStack
$caller = if ($callStack.Count -gt 1) { $callStack[1] } else { $null }
$lineInfo = if ($caller) { "Line $($caller.ScriptLineNumber)" } else { "Line ?" }
$formattedMessage = "[${dateStamp} ${timeStamp}] [$levelText] [$Component] [$lineInfo] $Message"
# Output to console with color
switch ($Type) {
1 { Write-Host $formattedMessage -ForegroundColor Gray }
2 { Write-Host $formattedMessage -ForegroundColor Yellow }
3 { Write-Host $formattedMessage -ForegroundColor Red }
}
# Always write to file
$formattedMessage | Out-File -FilePath $LogFile -Append -Encoding UTF8
}
function Clean-Drivers {
<#
.SYNOPSIS
Detects and removes unsigned Microsoft printer drivers that may block Windows 11 upgrades.
.DESCRIPTION
This function scans all installed printer drivers, identifies those published by Microsoft that are unsigned,
and removes them along with any associated printers. It then reinstalls built-in Microsoft virtual printers
(such as Microsoft Print to PDF and XPS Document Writer) if they were removed.
Unsigned drivers are logged to a CSV file for reference.
.OUTPUTS
None. Writes log entries and exports a CSV of unsigned drivers to $driverLog.
.NOTES
This remediation targets known upgrade blocks related to legacy unsigned Microsoft print drivers
(e.g., Type 3 kernel-mode drivers).
#>
Write-Host "Scanning installed printer drivers..."
$unsignedDrivers = @()
$removedDrivers = @()
Get-PrinterDriver | ForEach-Object {
$driver = $_
$driverName = $driver.Name
# Get detailed info from Win32_PrinterDriver
$wmiDriver = Get-WmiObject -Query "SELECT * FROM Win32_PrinterDriver WHERE Name = '$driverName'" -ErrorAction SilentlyContinue
if ($wmiDriver -and $wmiDriver.DriverPath -and (Test-Path $wmiDriver.DriverPath)) {
$sig = Get-AuthenticodeSignature -FilePath $wmiDriver.DriverPath
if ($sig.Status -ne 'Valid' -and $driver.Publisher -like '*Microsoft*') {
LogMessage -message ("Unsigned Microsoft driver found: $driverName")
$unsignedDrivers += [PSCustomObject]@{
DriverName = $driverName
DriverPath = $wmiDriver.DriverPath
SignatureStatus = $sig.Status
Publisher = $driver.Publisher
}
}
}
}
# Log results
if ($unsignedDrivers.Count -gt 0) {
$unsignedDrivers | Export-Csv -Path $driverLog -NoTypeInformation
LogMessage -message ("Logged unsigned drivers to $driverLog") -Type 1 -Component 'Clean-Drivers'
# Remove associated printers first
LogMessage -message ("Removing printers using unsigned drivers...") -Type 1 -Component 'Clean-Drivers'
Get-Printer | Where-Object { $_.DriverName -in $unsignedDrivers.DriverName } | ForEach-Object {
LogMessage -message (" - > Removing printer: $($_.Name)") -Type 1 -Component 'Clean-Drivers'
Remove-Printer -Name $_.Name -ErrorAction SilentlyContinue
}
# Remove the unsigned drivers
LogMessage -message ("Removing unsigned Microsoft printer drivers...") -Type 1 -Component 'Clean-Drivers'
foreach ($driver in $unsignedDrivers) {
LogMessage -message (" -> Removing driver: $($driver.DriverName)") -Type 1 -Component 'Clean-Drivers'
Remove-PrinterDriver -Name $driver.DriverName -ErrorAction SilentlyContinue
$removedDrivers += $driver.DriverName
}
# Conditionally reinstall Microsoft virtual printers if removed
if ($removedDrivers -match "Microsoft Print to PDF") {
LogMessage -message ("Reinstalling Microsoft Print to PDF...") -Type 1 -Component 'Clean-Drivers'
Add-WindowsCapability -Online -Name "Printing.PrintToPDF~~~~0.0.1.0" -ErrorAction SilentlyContinue
}
if ($removedDrivers -match "Microsoft XPS Document Writer") {
LogMessage -message ("Reinstalling Microsoft XPS Document Writer...") -Type 1 -Component 'Clean-Drivers'
Add-WindowsCapability -Online -Name "Printing.XPSServices~~~~0.0.1.0" -ErrorAction SilentlyContinue
}
}
else {
LogMessage -message ("No unsigned Microsoft printer drivers found.") -Type 1 -Component 'Clean-Drivers'
}
}
function ExtractNumbers([string]$str) {
<#
.SYNOPSIS
Extracts numeric characters from a string and returns them as a long integer.
.DESCRIPTION
This utility function removes all non-numeric characters from the input string
and returns the result as a 64-bit integer ([long]).
Used to parse disk or partition numbers from formatted strings
(e.g., "harddisk0" → 0).
.PARAMETER str
The input string from which to extract numeric digits.
.OUTPUTS
System.Int64 (long)
.EXAMPLE
ExtractNumbers "harddisk1" # Returns: 1
#>
$cleanString = $str -replace "[^0-9]"
return [long]$cleanString
}
# Define function to check partition style
Function Get-PartitionStyle {
$disk = Get-Disk | Where-Object { $_.PartitionStyle -ne "RAW" -and $_.IsBoot -eq $true }
if (!$disk) {
LogMessage -Message ("Could not determine the boot disk. Ensure the system is properly configured.") -Type 2 -Component 'Get-PartitionStyle'
return
}
return $disk.PartitionStyle
}
function IsProcessIdle {
<#
.SYNOPSIS
Waits for a process to remain below 1% real-time CPU usage for a defined period.
.DESCRIPTION
Uses Get-Counter to poll the process’s % Processor Time every few seconds. If CPU usage stays
under 1% for the full IdleMinutes threshold, or if the process exits, the function returns $true.
If MaxWaitSeconds is reached first, the function returns $false.
.PARAMETER ProcessName
The name of the process to monitor (without ".exe").
.PARAMETER ExpectedPathPart
Optional. A partial string to match in the process path.
.PARAMETER IdleMinutes
The number of continuous minutes the process must remain under 1% CPU usage.
.PARAMETER MaxWaitSeconds
The maximum number of seconds to wait before timing out.
.PARAMETER checkIntervalSeconds
Interval between checks. Default: 15 seconds.
.OUTPUTS
[bool] - $true if idle threshold met or process exited; $false if timed out.
.EXAMPLE
if (IsProcessIdle -ProcessName "SetupHost" -ExpectedPathPart "\$WINDOWS.~BT\Sources") {
LogMessage -message "SetupHost.exe confirmed idle"
}
#>
param (
[string]$ProcessName,
[string]$ExpectedPathPart = $null,
[int]$IdleMinutes = 5,
[int]$MaxWaitSeconds = 7200,
[int]$checkIntervalSeconds = 15
)
$idleSeconds = 0
$waitSeconds = 0
while ($waitSeconds -lt $MaxWaitSeconds) {
$process = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue | Where-Object {
if ($ExpectedPathPart) {
try { $_.Path -like "*$ExpectedPathPart*" } catch { $false }
}
else {
$true
}
}
if (-not $process) {
return $true # Treat as idle if process exited
}
try {
$counterPath = "\Process($ProcessName*)\% Processor Time"
$cpuUsage = (Get-Counter -Counter $counterPath -ErrorAction Stop).CounterSamples.CookedValue
$cpuAvg = [math]::Round(($cpuUsage | Measure-Object -Average).Average, 2)
if ($cpuAvg -lt 1) {
$idleSeconds += $checkIntervalSeconds
if ($idleSeconds -ge ($IdleMinutes * 60)) {
return $true
}
}
else {
$idleSeconds = 0
}
}
catch {
# Handle cases where Get-Counter fails (e.g., instance not found)
LogMessage -message "Failed to sample CPU for $($ProcessName): $($_.Exception.Message)" -Type 3 -Component 'Upgrade'
$idleSeconds = 0
}
Start-Sleep -Seconds $checkIntervalSeconds
$waitSeconds += $checkIntervalSeconds
}
LogMessage -message "$ProcessName did not become idle in time. Max wait of $($MaxWaitSeconds / 60) minutes exceeded." -Type 3 -Component 'Upgrade'
return $false
}
function SleepNow {
<#
.SYNOPSIS
Pauses script execution for a specified number of minutes with periodic logging.
.DESCRIPTION
This function puts the script to sleep for a given number of minutes. During the sleep,
it logs a message every 60 seconds showing the remaining time, and then logs a final
message when the wait period is over.
.PARAMETER Length
The number of minutes to sleep.
.OUTPUTS
None. This function is used for timing and logging purposes only.
.EXAMPLE
SleepNow -Length 15
Logs a message every minute for 15 minutes, then logs "Time to wake up sleepy head!".
#>
param (
[Parameter(Mandatory = $true)]
[int]$Length # Length in minutes
)
$totalSeconds = $Length * 60
$remaining = $totalSeconds
while ($remaining -gt 0) {
Start-Sleep -Seconds 60
$remaining -= 60
if ($remaining -gt 0) {
$minutes = [int]($remaining / 60)
$seconds = $remaining % 60
LogMessage -message ("Sleeping for another $minutes min and $seconds second") -Component 'SleepNow'
}
}
LogMessage -message ("Time to wake up sleepy head!") -Component 'SleepNow'
}
function DisplayPartitionInfo([string[]]$partitionPath) {
<#
.SYNOPSIS
Retrieves and logs partition size and free space for a given partition path.
.DESCRIPTION
Uses WMI (Win32_Volume) to find the volume associated with the provided device path(s).
Logs total capacity and available free space, and returns both values as a two-element array.
Used during WinRE analysis or resizing to understand disk layout and available space.
.PARAMETER partitionPath
An array of partition access paths (e.g., {"\\?\Volume{...}\"}).
.OUTPUTS
System.Object[]
Returns an array: [TotalSize (bytes), FreeSpace (bytes)]
.EXAMPLE
DisplayPartitionInfo "\\?\Volume{abc123}\"
# Logs and returns: 500107862016, 120034467840
#>
$volume = Get-WmiObject -Class Win32_Volume | Where-Object { $partitionPath -contains $_.DeviceID }
LogMessage -message (" Partition capacity: " + $volume.Capacity) -Type 1 -Component 'DisplayPartitionInfo'
LogMessage -message (" Partition free space: " + $volume.FreeSpace) -Type 1 -Component 'DisplayPartitionInfo'
return $volume.Capacity, $volume.FreeSpace
}
function Backup-WinRE {
<#
.SYNOPSIS
Backs up WinRE.wim and ReAgent.xml to C:\WinRE_Backup.
.DESCRIPTION
Copies both the recovery image (WinRE.wim) and configuration file (ReAgent.xml)
to C:\WinRE_Backup. Logs actions and returns $true if both backups succeed.
.OUTPUTS
[bool] - $true if both files were backed up, otherwise $false.
#>
$sourceWim = "$env:SystemRoot\System32\Recovery\WinRE.wim"
$sourceXml = "$env:SystemRoot\System32\Recovery\ReAgent.xml"
$backupFolder = "C:\WinRE_Backup"
$backupWim = Join-Path $backupFolder "WinRE.wim"
$backupXml = Join-Path $backupFolder "ReAgent.xml"
if (!(Test-Path $backupFolder)) {
New-Item -Path $backupFolder -ItemType Directory | Out-Null
}
$success = $true
if (Test-Path $sourceWim) {
try {
Copy-Item -Path $sourceWim -Destination $backupWim -Force
LogMessage -message "Backed up WinRE.wim to $backupWim" -Component 'Backup-WinRE'
}
catch {
LogMessage -message "Failed to back up WinRE.wim: $($_.Exception.Message)" -Type 3 -Component 'Backup-WinRE'
$success = $false
}
}
else {
LogMessage -message "No WinRE.wim found at $sourceWim to back up" -Type 2 -Component 'Backup-WinRE'
$success = $false
}
if (Test-Path $sourceXml) {
try {
Copy-Item -Path $sourceXml -Destination $backupXml -Force
LogMessage -message "Backed up ReAgent.xml to $backupXml" -Component 'Backup-WinRE'
}
catch {
LogMessage -message "Failed to back up ReAgent.xml: $($_.Exception.Message)" -Type 3 -Component 'Backup-WinRE'
$success = $false
}
}
else {
LogMessage -message "No ReAgent.xml found at $sourceXml to back up" -Type 2 -Component 'Backup-WinRE'
# not fatal, continue
}
return $success
}
function Restore-WinRE {
<#
.SYNOPSIS
Restores the WinRE.wim and ReAgent.xml files from backup if they were previously saved.
.DESCRIPTION
Copies the backed-up WinRE image and configuration file from C:\WinRE_Backup to their original system locations
in C:\Windows\System32\Recovery. Re-registers the WinRE image using ReAgentC.
.OUTPUTS
[bool] - $true if restore and re-registration were successful, otherwise $false.
#>
$backupFolder = "C:\WinRE_Backup"
$backupWim = Join-Path $backupFolder "WinRE.wim"
$backupXml = Join-Path $backupFolder "ReAgent.xml"
$recoveryFolder = "$env:SystemRoot\System32\Recovery"
$targetWim = Join-Path $recoveryFolder "WinRE.wim"
$targetXml = Join-Path $recoveryFolder "ReAgent.xml"
$restoreSuccess = $true
# --- Restore WinRE.wim ---
if (-not (Test-Path $targetWim) -and (Test-Path $backupWim)) {
try {
Copy-Item -Path $backupWim -Destination $targetWim -Force
LogMessage -message "Restored WinRE.wim to $targetWim" -Component 'Restore-WinRE'
}
catch {
LogMessage -message "Failed to restore WinRE.wim: $($_.Exception.Message)" -Type 3 -Component 'Restore-WinRE'
$restoreSuccess = $false
}
}
elseif (-not (Test-Path $backupWim)) {
LogMessage -message "Backup WinRE.wim not found at $backupWim" -Type 3 -Component 'Restore-WinRE'
$restoreSuccess = $false
}
else {
LogMessage -message "WinRE.wim already exists at $targetWim — no restore needed." -Component 'Restore-WinRE'
}
# --- Restore ReAgent.xml ---
if (-not (Test-Path $targetXml) -and (Test-Path $backupXml)) {
try {
Copy-Item -Path $backupXml -Destination $targetXml -Force
LogMessage -message "Restored ReAgent.xml to $targetXml" -Component 'Restore-WinRE'
}
catch {
LogMessage -message "Failed to restore ReAgent.xml: $($_.Exception.Message)" -Type 2 -Component 'Restore-WinRE'
# not fatal
}
}
elseif (Test-Path $targetXml) {
LogMessage -message "ReAgent.xml already exists at $targetXml — no restore needed." -Component 'Restore-WinRE'
}
# --- Re-register WinRE image if WIM is now present ---
if (Test-Path $targetWim) {
try {
$output = ReAgentC.exe /setreimage /path $recoveryFolder /target $env:SystemRoot 2>&1
if ($LASTEXITCODE -eq 0) {
LogMessage -message "ReAgentC /setreimage successful. Output:`n$($output -join "`n")" -Component 'Restore-WinRE'
}
else {
LogMessage -message "ReAgentC /setreimage failed. Output:`n$($output -join "`n")" -Type 3 -Component 'Restore-WinRE'
$restoreSuccess = $false
}
}
catch {
LogMessage -message "Exception occurred during ReAgentC /setreimage: $($_.Exception.Message)" -Type 3 -Component 'Restore-WinRE'
$restoreSuccess = $false
}
}
else {
LogMessage -message "WinRE.wim is still missing at $targetWim. Cannot re-register." -Type 3 -Component 'Restore-WinRE'
$restoreSuccess = $false
}
return $restoreSuccess
}
Thanks, So I can see that your Upgrade_Windows.ps1 script can execute a number of different executables (.exe) files during it’s run.
I’ve found references to 6 (different) .exe’s in the script on:
Line 11 [string]$ServiceUIPath = "$PSScriptRoot\ServiceUI.exe",
Line 28 & "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ExecutionPolicy bypass -NoProfile -File "$PSCommandPath"
Line 72 $ServiceUIDestination = "$Win11WorkingDirectory\ServiceUI.exe"
Line 78 $CompatAppraiserPath = 'C:\Windows\system32\CompatTelRunner.exe'
Line 88 $monitoredExeName = "windows10upgraderapp.exe"
Line 579 $output = ReAgentC.exe /setreimage /path $recoveryFolder /target $env:SystemRoot 2>&1
It could be anyone of these executables that is triggering the original error
I may not have access to all the executables you are calling , so I can’t test this script fully.
But, If you leave PSADT out of the equation, Are you able to run this script successfully (as an admin) on a test machine (a sandbox for example)?
It would be useful to know which executable it is tripping up over.
I have a suspicion it maybe one of the last 3 listed above - The last one ReAgentC.exe is part of the Windows Recovery Environment so may only work inside Windows PE (Pre-execution Environment).
I’d suggest you go away and get the script working properly outside of PSADT before asking for PSADT help
I think the issue is more that you’re doing something like Start-ADTProcess -FilePath .\SomeRandomVoodooScript.ps1, instead of Start-ADTProcess -FilePath powershell.exe -ArgumentList "-File "$($adtSession.DirFiles)\SomeRandomVoodooScript.ps1"
Thanks for reviewing the script…
Yes I have tried this script as a Admin without PSAPDT. It is working as expected and my windows 10 device got upgraded to windows 11 (while user log in).
Meanwhile will try the below solution.
" I think the issue is more that you’re doing something like Start-ADTProcess -FilePath .\SomeRandomVoodooScript.ps1 , instead of Start-ADTProcess -FilePath powershell.exe -ArgumentList "-File "$($adtSession.DirFiles)\SomeRandomVoodooScript.ps1"
Thanks for jumping on it. I will try to change the command line but I don’t see any -File parameter in Start-ADTProcess syntax. Will this be help here?