From 3c545be5c5d85982a5a1f5f8be560a0351f422ff Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 11 Aug 2025 19:36:58 -0700 Subject: [PATCH] Hardens driver downloads and cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds in‑progress markers around OEM driver downloads to enable recovery and reliable post‑run cleanup. Refactors driver cleanup to be run‑aware: maps download targets to model folders, removes temp/model content created during the run, prunes empties, and preserves existing make roots via creation‑time checks. Includes the Drivers folder in current‑run cleanup with safer rules to avoid deleting pre‑existing content. Improves Office process termination by resolving the Office path (prefers UI value) and only acting when a valid folder exists. --- FFUDevelopment/BuildFFUVM.ps1 | 185 ++++++++++++++++++++++++++++--- FFUDevelopment/BuildFFUVM_UI.ps1 | 28 ++++- 2 files changed, 195 insertions(+), 18 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index f67450b..6f56913 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -886,7 +886,9 @@ function Get-MicrosoftDrivers { ### DOWNLOAD THE FILE $filePath = Join-Path -Path $surfaceDriversPath -ChildPath ($fileName) WriteLog "Downloading $Model driver file to $filePath" + Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $filePath Start-BitsTransferWithRetry -Source $downloadLink -Destination $filePath + Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $filePath WriteLog "Download complete" # Determine file extension @@ -1128,7 +1130,9 @@ function Get-HPDrivers { # Download the driver with retry WriteLog "Downloading driver to: $DriverFilePath" + Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $DriverFilePath Start-BitsTransferWithRetry -Source $DriverUrl -Destination $DriverFilePath + Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $DriverFilePath WriteLog 'Driver downloaded' # Make folder for extraction @@ -1353,7 +1357,9 @@ function Get-LenovoDrivers { # Download the driver with retry WriteLog "Downloading driver: $driverUrl to $driverFilePath" + Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $driverFilePath Start-BitsTransferWithRetry -Source $driverUrl -Destination $driverFilePath + Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $driverFilePath WriteLog "Driver downloaded" # Make folder for extraction @@ -1536,7 +1542,9 @@ function Get-DellDrivers { WriteLog "Downloading driver: $($driver.DownloadUrl) to $driverFilePath" try { + Mark-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $driverFilePath Start-BitsTransferWithRetry -Source $driver.DownloadUrl -Destination $driverFilePath + Clear-DownloadInProgress -FFUDevelopmentPath $FFUDevelopmentPath -TargetPath $driverFilePath WriteLog "Driver downloaded" } catch { @@ -3866,6 +3874,32 @@ function Remove-InProgressItems { try { $data = Get-Content $_.FullName -Raw | ConvertFrom-Json $target = $data.TargetPath + try { + if ($DriversFolder -and $target) { + $fullTarget = [System.IO.Path]::GetFullPath($target).TrimEnd('\') + $driversRoot = [System.IO.Path]::GetFullPath($DriversFolder).TrimEnd('\') + if ($fullTarget.StartsWith($driversRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + $remainder = $fullTarget.Substring($driversRoot.Length).TrimStart('\') + $parts = $remainder -split '\\' + if ($parts.Length -ge 1) { + $knownMakes = @('Dell','HP','Lenovo','Microsoft') + if ($parts.Length -ge 2 -and $knownMakes -contains $parts[0]) { + # Drivers\\\... + $modelFolder = Join-Path (Join-Path $driversRoot $parts[0]) $parts[1] + } + else { + # Drivers\\... (when DriversFolder already includes Make) + $modelFolder = Join-Path $driversRoot $parts[0] + } + if ($modelFolder) { + WriteLog "Promoting in-progress driver target to model folder: $modelFolder (from $target)" + $target = $modelFolder + } + } + } + } + } + catch {} if (Test-Path $target) { # Special-case Office: preserve DeployFFU.xml and DownloadFFU.xml; remove everything else with retries. @@ -3916,6 +3950,53 @@ function Remove-InProgressItems { WriteLog "Failed Remove-InProgressItems marker '$($_.FullName)': $($_.Exception.Message)" } } + # Also clean up any driver content created this run (model folders and temp folders), + # even when broader current-run cleanup is not requested. + try { + if ($DriversFolder -and (Test-Path $DriversFolder)) { + $manifest = Get-CurrentRunManifest -FFUDevelopmentPath $FFUDevelopmentPath + if ($manifest -and $manifest.RunStartUtc) { + $runStart = [datetime]::Parse($manifest.RunStartUtc) + + # Remove OEM temp folders like _TEMP_* (safe to always remove) + Get-ChildItem -Path $DriversFolder -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like '_TEMP_*' } | + ForEach-Object { + WriteLog "Removing driver temp folder: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + + # Remove model folders created/modified this run; never remove top-level make roots + Get-ChildItem -Path $DriversFolder -Directory -ErrorAction SilentlyContinue | ForEach-Object { + $makeRoot = $_.FullName + # Model-level folders are immediate children under a make root (e.g. Drivers\Lenovo\) + Get-ChildItem -Path $makeRoot -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.CreationTimeUtc -ge $runStart -or $_.LastWriteTimeUtc -ge $runStart } | + ForEach-Object { + WriteLog "Removing driver model folder from current run: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + } + + # Remove make root folders created this run (if empty) + Get-ChildItem -Path $DriversFolder -Directory -ErrorAction SilentlyContinue | + Where-Object { $_.CreationTimeUtc -ge $runStart -and $_.LastWriteTimeUtc -ge $runStart } | + ForEach-Object { + $any = Get-ChildItem -Path $_.FullName -Force -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -eq $any) { + WriteLog "Removing empty make root folder created this run: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + else { + WriteLog "Skipping non-empty make root folder: $($_.FullName)" + } + } + } + } + } + catch { + WriteLog "Driver in-progress cleanup step failed: $($_.Exception.Message)" + } } function Cleanup-CurrentRunDownloads { param([string]$FFUDevelopmentPath) @@ -3931,25 +4012,101 @@ function Cleanup-CurrentRunDownloads { if ($OneDrivePath) { $roots += $OneDrivePath } if ($EdgePath) { $roots += $EdgePath } if ($KBPath) { $roots += $KBPath } + if ($DriversFolder) { $roots += $DriversFolder } if ($orchestrationPath) { $roots += $orchestrationPath } foreach ($root in $roots | Where-Object { $_ -and (Test-Path $_) }) { - WriteLog "Scanning for current-run items in $root" - # Remove folders created/modified this run - Get-ChildItem -Path $root -Directory -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTimeUtc -ge $runStart } | Sort-Object FullName -Descending | ForEach-Object { - try { - WriteLog "Removing current-run folder: $($_.FullName)" - Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + $isDriversRoot = $false + try { + if ($DriversFolder) { + $isDriversRoot = ([System.IO.Path]::GetFullPath($root).TrimEnd('\') -ieq [System.IO.Path]::GetFullPath($DriversFolder).TrimEnd('\')) } - catch { WriteLog "Failed removing folder $($_.FullName): $($_.Exception.Message)" } } - # Remove files created/modified this run - Get-ChildItem -Path $root -File -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTimeUtc -ge $runStart -and $_.Name -notin @('DeployFFU.xml', 'DownloadFFU.xml') } | ForEach-Object { - try { - WriteLog "Removing current-run file: $($_.FullName)" - Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue - } - catch { WriteLog "Failed removing file $($_.FullName): $($_.Exception.Message)" } + catch {} + + if ($isDriversRoot) { + WriteLog "Scanning Drivers folder (creation-time filter) in $root" + + # Remove driver folders created this run (skip non-empty make roots) + Get-ChildItem -Path $root -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.CreationTimeUtc -ge $runStart } | + Sort-Object FullName -Descending | ForEach-Object { + try { + $parent = Split-Path -Path $_.FullName -Parent + $parentIsDriversRoot = ([System.IO.Path]::GetFullPath($parent).TrimEnd('\') -ieq [System.IO.Path]::GetFullPath($root).TrimEnd('\')) + if ($parentIsDriversRoot) { + # Only remove top-level make folders if created this run AND empty (avoid deleting existing Lenovo/HP/Dell/Microsoft trees) + $any = Get-ChildItem -Path $_.FullName -Force -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -eq $any) { + WriteLog "Removing empty make folder created this run: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + } + else { + WriteLog "Removing current-run driver folder: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + } + catch { WriteLog "Failed removing driver folder $($_.FullName): $($_.Exception.Message)" } + } + + # Remove driver files created this run + Get-ChildItem -Path $root -File -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.CreationTimeUtc -ge $runStart } | + ForEach-Object { + try { + WriteLog "Removing current-run driver file: $($_.FullName)" + Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue + } + catch { WriteLog "Failed removing driver file $($_.FullName): $($_.Exception.Message)" } + } + + # Prune empty driver folders (skip existing make roots) + Get-ChildItem -Path $root -Directory -Recurse -ErrorAction SilentlyContinue | + Sort-Object FullName -Descending | ForEach-Object { + try { + $any = Get-ChildItem -Path $_.FullName -Force -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -eq $any) { + $parent = Split-Path -Path $_.FullName -Parent + $parentIsDriversRoot = ([System.IO.Path]::GetFullPath($parent).TrimEnd('\') -ieq [System.IO.Path]::GetFullPath($root).TrimEnd('\')) + if ($parentIsDriversRoot) { + # Only remove empty make roots if they were created this run + if ($_.CreationTimeUtc -ge $runStart) { + WriteLog "Removing empty make folder created this run: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + } + else { + WriteLog "Removing empty driver subfolder: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + } + } + catch { WriteLog "Failed pruning empty driver folder $($_.FullName): $($_.Exception.Message)" } + } + } + else { + WriteLog "Scanning for current-run items in $root" + # Remove folders created/modified this run (legacy behavior for non-Drivers roots) + Get-ChildItem -Path $root -Directory -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTimeUtc -ge $runStart } | + Sort-Object FullName -Descending | ForEach-Object { + try { + WriteLog "Removing current-run folder: $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force -ErrorAction SilentlyContinue + } + catch { WriteLog "Failed removing folder $($_.FullName): $($_.Exception.Message)" } + } + # Remove files created/modified this run (preserve Office XMLs) + Get-ChildItem -Path $root -File -Recurse -ErrorAction SilentlyContinue | + Where-Object { $_.LastWriteTimeUtc -ge $runStart -and $_.Name -notin @('DeployFFU.xml', 'DownloadFFU.xml') } | + ForEach-Object { + try { + WriteLog "Removing current-run file: $($_.FullName)" + Remove-Item -Path $_.FullName -Force -ErrorAction SilentlyContinue + } + catch { WriteLog "Failed removing file $($_.FullName): $($_.Exception.Message)" } + } } } diff --git a/FFUDevelopment/BuildFFUVM_UI.ps1 b/FFUDevelopment/BuildFFUVM_UI.ps1 index a882b59..6f117f0 100644 --- a/FFUDevelopment/BuildFFUVM_UI.ps1 +++ b/FFUDevelopment/BuildFFUVM_UI.ps1 @@ -203,10 +203,30 @@ $script:uiState.Controls.btnRun.Add_Click({ # Also stop Office ODT setup.exe if running (to avoid recreating files after cleanup) try { - $officePathForKill = Join-Path (Split-Path (Split-Path $lastConfigPath -Parent) -Parent) 'Apps\Office' - $setupProcs = Get-CimInstance Win32_Process -Filter "Name='setup.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.ExecutablePath -like "$officePathForKill*" } - foreach ($p in $setupProcs) { - try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } catch {} + $officePathForKill = $null + + # Prefer explicit UI path + $uiOfficePath = $script:uiState.Controls.txtOfficePath.Text + if (-not [string]::IsNullOrWhiteSpace($uiOfficePath)) { + $officePathForKill = $uiOfficePath + } + else { + # Fall back to the last config path only if known + $lastConfigPathLocal = $script:uiState.Data.lastConfigFilePath + if (-not [string]::IsNullOrWhiteSpace($lastConfigPathLocal)) { + $ffuDevRoot = Split-Path (Split-Path $lastConfigPathLocal -Parent) -Parent + if (-not [string]::IsNullOrWhiteSpace($ffuDevRoot)) { + $officePathForKill = Join-Path $ffuDevRoot 'Apps\Office' + } + } + } + + # Only proceed when a valid Office folder exists + if ($officePathForKill -and (Test-Path -LiteralPath $officePathForKill -PathType Container)) { + $setupProcs = Get-CimInstance Win32_Process -Filter "Name='setup.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.ExecutablePath -like "$officePathForKill*" } + foreach ($p in $setupProcs) { + try { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue } catch {} + } } } catch {