mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Refactor MSI extraction for improved reliability
Implements a dedicated mutex to serialize MSI extraction operations. This prevents race conditions when multiple driver packages are processed in parallel by different tasks. Adds a post-extraction verification step to ensure the target directory is not empty. This guards against silent failures where `msiexec` exits successfully but extracts no files, triggering a retry if necessary.
This commit is contained in:
@@ -232,39 +232,48 @@ function Save-MicrosoftDriversTask {
|
||||
|
||||
### EXTRACT
|
||||
if ($fileExtension -eq ".msi") {
|
||||
$status = "Extracting MSI..." # Set initial status
|
||||
$status = "Waiting for MSI lock..." # Set initial status
|
||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
||||
|
||||
# Loop indefinitely to wait for mutex and handle MSIExec exit codes by catching errors
|
||||
# Use a named mutex to ensure only one MSI extraction happens at a time across all parallel tasks
|
||||
$msiMutexName = "Global\FFUDevelopmentMSIExtractionMutex"
|
||||
$msiMutex = New-Object System.Threading.Mutex($false, $msiMutexName)
|
||||
|
||||
try {
|
||||
WriteLog "Waiting to acquire global MSI extraction lock for '$modelName'..."
|
||||
$msiMutex.WaitOne() | Out-Null
|
||||
WriteLog "Acquired global MSI extraction lock for '$modelName'."
|
||||
|
||||
# Loop indefinitely to wait for system mutex and handle MSIExec exit codes
|
||||
while ($true) {
|
||||
$mutexClear = $false
|
||||
|
||||
# 1. Check Mutex
|
||||
# 1. Check System-level MSI Mutex
|
||||
try {
|
||||
$Mutex = [System.Threading.Mutex]::OpenExisting("Global\_MSIExecute")
|
||||
$Mutex.Dispose()
|
||||
$sysMutex = [System.Threading.Mutex]::OpenExisting("Global\_MSIExecute")
|
||||
$sysMutex.Dispose()
|
||||
$status = "Waiting for MSIExec..."
|
||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
||||
WriteLog "Another MSIExec installer is running (Mutex Held). Waiting 5 seconds before rechecking for $modelName..."
|
||||
WriteLog "Another MSIExec installer is running (System Mutex Held). Waiting 5 seconds before rechecking for $modelName..."
|
||||
Start-Sleep -Seconds 5
|
||||
continue # Go back to start of while loop to re-check mutex
|
||||
}
|
||||
catch [System.Threading.WaitHandleCannotBeOpenedException] {
|
||||
# Mutex is clear, proceed to extraction attempt
|
||||
WriteLog "Mutex clear. Proceeding with MSI extraction attempt for $modelName."
|
||||
WriteLog "System MSI mutex clear. Proceeding with MSI extraction attempt for $modelName."
|
||||
$status = "Extracting MSI..."
|
||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
||||
$mutexClear = $true
|
||||
}
|
||||
catch {
|
||||
# Handle other potential errors when checking the mutex
|
||||
WriteLog "Warning: Error checking MSIExec mutex for $($modelName): $_. Proceeding with caution."
|
||||
WriteLog "Warning: Error checking system MSI mutex for $($modelName): $_. Proceeding with caution."
|
||||
$status = "Extracting MSI (Mutex Error)..."
|
||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
||||
$mutexClear = $true # Proceed despite mutex error
|
||||
}
|
||||
|
||||
# 2. Attempt Extraction (only if mutex was clear or error occurred during check)
|
||||
# 2. Attempt Extraction (only if mutex was clear)
|
||||
if ($mutexClear) {
|
||||
WriteLog "Extracting MSI file to $modelPath"
|
||||
$arguments = "/a `"$($filePath)`" /qn TARGETDIR=`"$($modelPath)`""
|
||||
@@ -274,6 +283,18 @@ function Save-MicrosoftDriversTask {
|
||||
|
||||
# If Invoke-Process succeeded (didn't throw), extraction is complete.
|
||||
WriteLog "Extraction complete for $modelName (Exit Code 0)."
|
||||
|
||||
# Verification Step: Ensure the target folder is not empty.
|
||||
$itemsInDest = Get-ChildItem -Path $modelPath -Recurse
|
||||
if ($itemsInDest.Count -eq 0) {
|
||||
WriteLog "VERIFICATION FAILED: MSI extraction for '$modelName' produced an empty folder. Retrying..."
|
||||
$status = "Retrying (Empty Folder)"
|
||||
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelName -Status $status }
|
||||
Start-Sleep -Seconds 5
|
||||
continue # Retry the whole process
|
||||
}
|
||||
|
||||
WriteLog "VERIFICATION PASSED: Target folder for '$modelName' is not empty."
|
||||
break # Success, exit the while loop
|
||||
}
|
||||
catch {
|
||||
@@ -294,7 +315,15 @@ function Save-MicrosoftDriversTask {
|
||||
}
|
||||
}
|
||||
} # End if ($mutexClear)
|
||||
} # End while ($true) - Loop runs until break or throw
|
||||
} # End while ($true)
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $msiMutex) {
|
||||
$msiMutex.ReleaseMutex()
|
||||
$msiMutex.Dispose()
|
||||
WriteLog "Released global MSI extraction lock for '$modelName'."
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($fileExtension -eq ".zip") {
|
||||
$status = "Extracting ZIP..." # Set status before extraction
|
||||
|
||||
Reference in New Issue
Block a user