From c8ef42ab2115a068e46f3d0ce1275b3417180771 Mon Sep 17 00:00:00 2001 From: w0 <33212583+w0@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:54:58 -0500 Subject: [PATCH 01/50] Update USBImagingToolCreator.ps1 added support for relative paths. formatted script and removed functions from inside if statement. --- FFUDevelopment/USBImagingToolCreator.ps1 | 298 ++++++++++++----------- 1 file changed, 158 insertions(+), 140 deletions(-) diff --git a/FFUDevelopment/USBImagingToolCreator.ps1 b/FFUDevelopment/USBImagingToolCreator.ps1 index dd3f36d..d390896 100644 --- a/FFUDevelopment/USBImagingToolCreator.ps1 +++ b/FFUDevelopment/USBImagingToolCreator.ps1 @@ -1,20 +1,18 @@ [CmdletBinding()] param( [Parameter(Mandatory = $True, Position = 0)] - $DeployISOPath, + [io.fileinfo] $DeployISOPath, [Switch]$DisableAutoPlay ) -$Host.UI.RawUI.WindowTitle = 'USB Imaging Tool Creator' -if($DeployISOPath){ -$DevelopmentPath = $DeployISOPath | Split-Path function WriteLog($LogText) { -$LogFileName = '\Script.log' -$LogFile = $DevelopmentPath + $LogFilename + $LogFileName = '\Script.log' + $LogFile = $DevelopmentPath + $LogFilename Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue Write-Verbose $LogText } -Function Get-USBDrive { + +function Get-USBDrive { $USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'") If ($USBDrives -and ($null -eq $USBDrives.count)) { $USBDrivesCount = 1 @@ -31,26 +29,27 @@ Function Get-USBDrive { } return $USBDrives, $USBDrivesCount } -Function Build-DeploymentUSB{ - param( - [Array]$Drives - ) - writelog "Checking if ffu files are present in the ffu folder" - $Images = Get-ChildItem -Path $FFUPath -Filter "*.ffu" -File -Recurse - writelog "Checking if drivers are present in the drivers folder" - $Drivers = Get-ChildItem -Path $DriversPath -Recurse - $DrivesCount = $Drives.Count - Writelog "Creating partitions..." - foreach ($USBDrive in $Drives) { - $DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") - $Model = $USBDrive.model - $ScriptBlock = { + +function Build-DeploymentUSB { + param( + [Array]$Drives + ) + writelog "Checking if ffu files are present in the ffu folder" + $Images = Get-ChildItem -Path $FFUPath -Filter "*.ffu" -File -Recurse + writelog "Checking if drivers are present in the drivers folder" + $Drivers = Get-ChildItem -Path $DriversPath -Recurse + $DrivesCount = $Drives.Count + Writelog "Creating partitions..." + foreach ($USBDrive in $Drives) { + $DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") + $Model = $USBDrive.model + $ScriptBlock = { param($DriveNumber) Clear-Disk -Number $DriveNumber -RemoveData -RemoveOEM -Confirm:$false $Disk = Get-Disk -Number $DriveNumber $PartitionStyle = $Disk.PartitionStyle - if($PartitionStyle -ne 'MBR'){ - $Disk | Set-Disk -PartitionStyle MBR + if ($PartitionStyle -ne 'MBR') { + $Disk | Set-Disk -PartitionStyle MBR } $BootPartition = New-Partition -DiskNumber $DriveNumber -Size 2GB -IsActive -AssignDriveLetter $DeployPartition = New-Partition -DiskNumber $DriveNumber -UseMaximumSize -AssignDriveLetter @@ -62,127 +61,131 @@ Function Build-DeploymentUSB{ } writelog "Wait for partitioning jobs to complete" Get-Job | Wait-Job | Out-Null - if($DrivesCount -gt 1){ - writelog "Get file system information for all drives" - $Partitions = Get-Partition | Get-Volume - }else{ - writelog "Get file system information for drive number $DiskNumber" - $Partitions = Get-Partition -DiskNumber $DriveNumber | Get-Volume + if ($DrivesCount -gt 1) { + writelog "Get file system information for all drives" + $Partitions = Get-Partition | Get-Volume } -writelog "Get drive letter for all volumes labeled:BOOT" -$BootDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "BOOT"}).DriveLetter -writelog "Get drive letter for all volumes labeled:Deploy" -$DeployDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "Deploy"}).DriveLetter -writelog "Mount Deployment .iso image" -$ISOMountPoint = (Mount-DiskImage -ImagePath "$DeployISOPath" -PassThru | Get-Volume).DriveLetter + ":\" -writelog "Copying boot files to all drives labeled BOOT concurrently" -foreach ($Drive in $BootDrives) { -$Destination = $Drive + ":\" - $jobScriptBlock = { - param ( - [string]$SFolder, - [string]$DFolder - ) - Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + else { + writelog "Get file system information for drive number $DiskNumber" + $Partitions = Get-Partition -DiskNumber $DriveNumber | Get-Volume } - WriteLog "Start job to copy all boot files to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ISOMountPoint, $Destination | Out-Null -} -if($Images){ -writelog "Copying FFU image files to all drives labeled deploy concurrently" -foreach ($Drive in $DeployDrives) { -$Destination = $Drive + ":\" - $jobScriptBlock = { - param ( - [string]$SFolder, - [string]$DFolder - ) - Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J - } - - WriteLog "Start job to copy all FFU files to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $FFUPath, $Destination | Out-Null - } -} -if($Drivers){ -writelog "Copying driver files to all drives labeled deploy concurrently" -foreach ($Drive in $DeployDrives) { -$Destination = $Drive + ":\Drivers" - $jobScriptBlock = { - param ( - [string]$SFolder, - [string]$DFolder - ) - New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null - Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J - } - WriteLog "Start job to copy all drivers to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $DriversPath, $Destination | Out-Null -} -} -if(!($Drivers)){ - foreach ($Drive in $DeployDrives) { - WriteLog "Create drivers directory" - $drivepath = $Drive + ":\" - New-Item -Path "$drivepath" -Name Drivers -ItemType Directory -Force -Confirm: $false | Out-Null + writelog "Get drive letter for all volumes labeled:BOOT" + $BootDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "BOOT" }).DriveLetter + writelog "Get drive letter for all volumes labeled:Deploy" + $DeployDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "Deploy" }).DriveLetter + writelog "Mount Deployment .iso image" + $ISOMountPoint = (Mount-DiskImage -ImagePath "$DeployISOPath" -PassThru | Get-Volume).DriveLetter + ":\" + writelog "Copying boot files to all drives labeled BOOT concurrently" + foreach ($Drive in $BootDrives) { + $Destination = $Drive + ":\" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J } -} -if($DrivesCount -gt 1){ -Writelog "Building $DrivesCount drives concurrently...Please be patient..." -}else{ -Writelog "Building the imaging tool on $model...Please be patient..." -} -Get-Job | Wait-Job | Out-Null + WriteLog "Start job to copy all boot files to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ISOMountPoint, $Destination | Out-Null + } + if ($Images) { + writelog "Copying FFU image files to all drives labeled deploy concurrently" + foreach ($Drive in $DeployDrives) { + $Destination = $Drive + ":\" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + } -Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null -Writelog "Drive creation jobs completed..." + WriteLog "Start job to copy all FFU files to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $FFUPath, $Destination | Out-Null + } + } + if ($Drivers) { + writelog "Copying driver files to all drives labeled deploy concurrently" + foreach ($Drive in $DeployDrives) { + $Destination = $Drive + ":\Drivers" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + } + WriteLog "Start job to copy all drivers to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $DriversPath, $Destination | Out-Null + } + } + if (!($Drivers)) { + foreach ($Drive in $DeployDrives) { + WriteLog "Create drivers directory" + $drivepath = $Drive + ":\" + New-Item -Path "$drivepath" -Name Drivers -ItemType Directory -Force -Confirm: $false | Out-Null + } + } + if ($DrivesCount -gt 1) { + Writelog "Building $DrivesCount drives concurrently...Please be patient..." + } + else { + Writelog "Building the imaging tool on $model...Please be patient..." + } + Get-Job | Wait-Job | Out-Null + + Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null + Writelog "Drive creation jobs completed..." } -Function New-DeploymentUSB { +function New-DeploymentUSB { param( [Array]$Drives, [int]$Count, [String]$FFUPath = "$DevelopmentPath\FFU", [String]$DriversPath = "$DevelopmentPath\Drivers" - + ) - + $Drivelist = @() writelog "Creating a USB drive selection list" - for($i=0;$i -le $Count -1;$i++){ + for ($i = 0; $i -le $Count - 1; $i++) { $DriveModel = $Drives[$i].Model - $DriveSize = [math]::round($Drives[$i].size/1GB, 2) + $DriveSize = [math]::round($Drives[$i].size / 1GB, 2) $DiskNumber = $Drives[$i].DeviceID.Replace("\\.\PHYSICALDRIVE", "") - $Properties = [ordered]@{Number = $i + 1 ; DriveNumber = $DiskNumber ; DriveModel = $driveModel ; 'Size (GB)' = $DriveSize} + $Properties = [ordered]@{Number = $i + 1 ; DriveNumber = $DiskNumber ; DriveModel = $driveModel ; 'Size (GB)' = $DriveSize } $Drivelist += New-Object PSObject -Property $Properties - } - if($Count -gt 1){ - $Last = $Count+1 + } + if ($Count -gt 1) { + $Last = $Count + 1 $Drivelist += New-Object -TypeName PSObject -Property @{ Number = "$last"; DriveModel = "Select this option to use all ($count) inserted USB Drives" } - } - $Drivelist | Format-Table -AutoSize -Property Number, DriveModel , 'Size (GB)' - do { + } + $Drivelist | Format-Table -AutoSize -Property Number, DriveModel , 'Size (GB)' + do { try { $var = $true $DriveSelected = Read-Host 'Enter the drive number to apply the .iso to' - $DriveSelected = ($DriveSelected -as [int]) -1 - if($Last){ - writelog "All drives selected" - }else{ - writelog "Drive $DriveSelected selected"} + $DriveSelected = ($DriveSelected -as [int]) - 1 + if ($Last) { + writelog "All drives selected" } - catch { + else { + writelog "Drive $DriveSelected selected" + } + } + catch { Write-Host 'Input was not in correct format. Please enter a valid FFU number' $var = $false } - } until (($DriveSelected -le $Count -1 -or $last) -and $var) + } until (($DriveSelected -le $Count - 1 -or $last) -and $var) $DisableAutoPlayCurrentSetting = (Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name DisableAutoplay).DisableAutoplay - if($DisableAutoPlay -and $DisableAutoPlayCurrentSetting -ne 1){ - writelog "Disable autoPlay current setting is $DisableAutoPlayCurrentSetting" - WriteLog "Setting the registry key to disable autoplay for all drives" - Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD + if ($DisableAutoPlay -and $DisableAutoPlayCurrentSetting -ne 1) { + writelog "Disable autoPlay current setting is $DisableAutoPlayCurrentSetting" + WriteLog "Setting the registry key to disable autoplay for all drives" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD } WriteLog "Closing all MMC windows to prevent drive lock errors" Stop-Process -Name mmc -ErrorAction SilentlyContinue @@ -190,34 +193,49 @@ Function New-DeploymentUSB { Stop-Process -Name diskpart -ErrorAction SilentlyContinue $Selection = $Drivelist[$DriveSelected].Number $totalSteps = 5 - if($Selection -eq $last){ - Read-Host -Prompt "ALL DRIVES SELECTED! WILL ERASE ALL CURRENTLY CONNECTED USB DRIVES!! Press ENTER to continue" - Build-DeploymentUSB -Drives $Drives - }else{ - Read-Host -Prompt "Drive number $Selection was selected. Press ENTER to continue" - Build-DeploymentUSB -Drives $Drives[$DriveSelected] + if ($Selection -eq $last) { + Read-Host -Prompt "ALL DRIVES SELECTED! WILL ERASE ALL CURRENTLY CONNECTED USB DRIVES!! Press ENTER to continue" + Build-DeploymentUSB -Drives $Drives + } + else { + Read-Host -Prompt "Drive number $Selection was selected. Press ENTER to continue" + Build-DeploymentUSB -Drives $Drives[$DriveSelected] } WriteLog "Setting the registry key to re-enable autoplay for all drives" - if($DisableAutoPlay){ - Writelog "Setting disable autoplay setting back to $DisableAutoPlayCurrentSetting" - Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value $DisableAutoPlayCurrentSetting -Type DWORD + if ($DisableAutoPlay) { + Writelog "Setting disable autoplay setting back to $DisableAutoPlayCurrentSetting" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value $DisableAutoPlayCurrentSetting -Type DWORD } Writelog "Completed!" -} -#Get USB Drive and create log file -if(Test-Path "$DevelopmentPath\Script.log"){ -Remove-Item -Path "$DevelopmentPath\Script.log" -Force -Confirm:$false -New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out-Null } -WriteLog 'Begin Logging' -WriteLog 'Getting USB drive information and usb drive count' -$USBDrives,$USBDrivesCount = Get-USBDrive -New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount -read-host -Prompt "USB drive creation complete. Press ENTER to exit" -Exit -}else{ -Write-Host "No .ISO file selected..." -read-host "Press ENTER to Exit..." -Exit +$Host.UI.RawUI.WindowTitle = 'USB Imaging Tool Creator' + +# Check if path is relative +if (-not [System.IO.Path]::IsPathRooted($DeployISOPath)) { + # Path is relative, Build full path + [io.fileinfo] $DeployISOPath = (Resolve-Path $DeployISOPath).Path + Write-Verbose "Path to ISO was relative. Attempting to resolve at: $($DeployISOPath.FullName)" +} + +if ($DeployISOPath.Exists) { + $DevelopmentPath = $DeployISOPath.Directory.FullName + + #Get USB Drive and create log file + if (Test-Path "$DevelopmentPath\Script.log") { + Remove-Item -Path "$DevelopmentPath\Script.log" -Force -Confirm:$false + New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out-Null + } + + WriteLog 'Begin Logging' + WriteLog 'Getting USB drive information and usb drive count' + + $USBDrives, $USBDrivesCount = Get-USBDrive + + New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount + + Read-Host -Prompt "USB drive creation complete. Press ENTER to exit" + Exit +} else { + Write-Error "Unable to locate ISO file at: $($DeployISOPath.FullName)" } From 807456de86edb7075e791205ff6b5bf4021802a7 Mon Sep 17 00:00:00 2001 From: w0 <33212583+w0@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:57:58 -0500 Subject: [PATCH 02/50] removed unused variable. more format. --- FFUDevelopment/USBImagingToolCreator.ps1 | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/FFUDevelopment/USBImagingToolCreator.ps1 b/FFUDevelopment/USBImagingToolCreator.ps1 index d390896..c5c28ab 100644 --- a/FFUDevelopment/USBImagingToolCreator.ps1 +++ b/FFUDevelopment/USBImagingToolCreator.ps1 @@ -16,8 +16,7 @@ function Get-USBDrive { $USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'") If ($USBDrives -and ($null -eq $USBDrives.count)) { $USBDrivesCount = 1 - } - else { + } else { $USBDrivesCount = $USBDrives.Count } WriteLog "Found $USBDrivesCount USB drives" @@ -64,8 +63,7 @@ function Build-DeploymentUSB { if ($DrivesCount -gt 1) { writelog "Get file system information for all drives" $Partitions = Get-Partition | Get-Volume - } - else { + } else { writelog "Get file system information for drive number $DiskNumber" $Partitions = Get-Partition -DiskNumber $DriveNumber | Get-Volume } @@ -129,8 +127,7 @@ function Build-DeploymentUSB { } if ($DrivesCount -gt 1) { Writelog "Building $DrivesCount drives concurrently...Please be patient..." - } - else { + } else { Writelog "Building the imaging tool on $model...Please be patient..." } Get-Job | Wait-Job | Out-Null @@ -192,12 +189,11 @@ function New-DeploymentUSB { WriteLog "Closing all Diskpart windows to prevent drive lock errors" Stop-Process -Name diskpart -ErrorAction SilentlyContinue $Selection = $Drivelist[$DriveSelected].Number - $totalSteps = 5 + if ($Selection -eq $last) { Read-Host -Prompt "ALL DRIVES SELECTED! WILL ERASE ALL CURRENTLY CONNECTED USB DRIVES!! Press ENTER to continue" Build-DeploymentUSB -Drives $Drives - } - else { + } else { Read-Host -Prompt "Drive number $Selection was selected. Press ENTER to continue" Build-DeploymentUSB -Drives $Drives[$DriveSelected] } From 1da28024cc79917b0a0c47e152f64876c880c00b Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sun, 15 Sep 2024 00:41:04 -0400 Subject: [PATCH 03/50] Add support for updating latest Windows Malicious Software Removal Tool --- FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 1 + FFUDevelopment/BuildFFUVM.ps1 | 90 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index 2178783..fc3477b 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -5,6 +5,7 @@ REM d:\Office\setup.exe /configure d:\office\DeployFFU.xml REM Install Defender Platform Update REM Install Defender Definitions REM Install Windows Security Platform Update +REM Install Windows Malicious Software Removal Tool REM Install OneDrive Per Machine REM Install Edge Stable REM Winget Win32 Apps diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 68cc570..bf01154 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -115,6 +115,9 @@ When set to $true, will download and install the latest .NET Framework for Windo .PARAMETER UpdateLatestDefender When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false. +.PARAMETER UpdateLatestMSRT +When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false + .PARAMETER UpdateEdge When set to $true, will download and install the latest Microsoft Edge for Windows 10/11. Default is $false. @@ -306,6 +309,7 @@ param( [bool]$UpdatePreviewCU, [bool]$UpdateLatestNet, [bool]$UpdateLatestDefender, + [bool]$UpdateLatestMSRT, [bool]$UpdateEdge, [bool]$UpdateOneDrive, [bool]$CopyPPKG, @@ -372,6 +376,7 @@ if (-not $FFUCaptureLocation) { $FFUCaptureLocation = "$FFUDevelopmentPath\FFU" if (-not $LogFile) { $LogFile = "$FFUDevelopmentPath\FFUDevelopment.log" } if (-not $KBPath) { $KBPath = "$FFUDevelopmentPath\KB" } if (-not $DefenderPath) { $DefenderPath = "$AppsPath\Defender" } +if (-not $MSRTPath) { $MSRTPath = "$AppsPath\MSRT" } if (-not $OneDrivePath) { $OneDrivePath = "$AppsPath\OneDrive" } if (-not $EdgePath) { $EdgePath = "$AppsPath\Edge" } if (-not $DriversFolder) { $DriversFolder = "$FFUDevelopmentPath\Drivers" } @@ -2309,6 +2314,39 @@ function Save-KB { return $fileName } +function Get-MSRTUrl { + param ( + [ValidateSet("x86", "x64")] + [string]$WindowsArch + ) + try { + # Retrieve content of Windows Malicious Software Removal Tool page + $pattern = '\"url\":\"(https://download\.microsoft\.com/download/[^"]+)\"' + $OriginalVerbosePreference = $VerbosePreference + $VerbosePreference = 'SilentlyContinue' + if ($WindowsArch -eq "x64") { + $MSRTWebPage = Invoke-RestMethod "https://www.microsoft.com/en-us/download/details.aspx?id=9905" -Headers $Headers -UserAgent $UserAgent + } + if ($WindowsArch -eq "x86") { + $MSRTWebPage = Invoke-RestMethod "https://www.microsoft.com/en-us/download/details.aspx?id=16" -Headers $Headers -UserAgent $UserAgent + } + $VerbosePreference = $OriginalVerbosePreference + # Extract download URL based on specified pattern + $MSRTMatch = [regex]::Match($MSRTWebPage, $pattern) + if (-not $MSRTMatch.Success) { + WriteLog "Failed to retrieve Malicious Software Removal Tool URL. Pattern match failed" + return + } + $MSRTUrl = $MSRTMatch.Groups[1].Value + return $MSRTUrl + } + catch { + WriteLog $_ + Write-Error "Error occurred while retrieving Windows Malicious Software Removal Tool link" + throw $_ + } +} + function New-AppsISO { #Create Apps ISO file $OSCDIMG = "$adkpath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe" @@ -3511,6 +3549,12 @@ function Get-FFUEnvironment { Remove-Item -Path $DefenderPath -Recurse -Force -ErrorAction SilentlyContinue WriteLog 'Removal complete' } + #Clean up $MSRTPath + if (Test-Path -Path $MSRTPath) { + WriteLog "Removing $MSRTPath" + Remove-Item -Path $MSRTPath -Recurse -Force -ErrorAction SilentlyContinue + WriteLog 'Removal complete' + } #Clean up $OneDrivePath If (Test-Path -Path $OneDrivePath) { WriteLog "Removing $OneDrivePath" @@ -3561,6 +3605,17 @@ function Clear-InstallAppsandSysprep { WriteLog 'Removal complete' } } + if ($UpdateLatestMSRT) { + WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove Windows Malicious Software Removal Tool" + $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + $CmdContent -notmatch 'd:\\MSRT*' | Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + #Clean up $MSRTPath + If (Test-Path -Path $MSRTPath) { + WriteLog "Removing $MSRTPath" + Remove-Item -Path $MSRTPath -Recurse -Force -ErrorAction SilentlyContinue + WriteLog 'Removal complete' + } + } if ($UpdateOneDrive) { WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to remove OneDrive install" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" @@ -3727,8 +3782,8 @@ if (($LogicalSectorSizeBytes -eq 4096) -and ($installdrivers -eq $true)) { if ($BuildUSBDrive -eq $true) { $USBDrives, $USBDrivesCount = Get-USBDrive } -if (($InstallApps -eq $false) -and (($UpdateLatestDefender -eq $true) -or ($UpdateOneDrive -eq $true) -or ($UpdateEdge -eq $true))) { - WriteLog 'You have selected to update Defender, OneDrive, or Edge, however you are setting InstallApps to false. These updates require the InstallApps variable to be set to true. Please set InstallApps to true and try again.' +if (($InstallApps -eq $false) -and (($UpdateLatestDefender -eq $true) -or ($UpdateOneDrive -eq $true) -or ($UpdateEdge -eq $true) -or ($UpdateLatestMSRT -eq $true))) { + WriteLog 'You have selected to update Defender, Malicious Software Removal Tool, OneDrive, or Edge, however you are setting InstallApps to false. These updates require the InstallApps variable to be set to true. Please set InstallApps to true and try again.' throw "InstallApps variable must be set to `$true to update Defender, OneDrive, or Edge" } if (($WindowsArch -eq 'ARM64') -and ($InstallOffice -eq $true)) { @@ -3741,6 +3796,11 @@ if (($WindowsArch -eq 'ARM64') -and ($UpdateOneDrive -eq $true)) { WriteLog 'OneDrive currently fails to install on ARM64 VMs (even with the OneDrive ARM setup files). Setting UpdateOneDrive to false' } +if (($WindowsArch -eq 'ARM64') -and ($UpdateLatestMSRT -eq $true)) { + $UpdateLatestMSRT = $false + WriteLog 'Windows Malicious Software Removal Tool is not available for the ARM64 architecture.' +} + ###END PARAMETER VALIDATION #Get script variable values @@ -3887,6 +3947,32 @@ if ($InstallApps) { Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent WriteLog "Update complete" } + if ($UpdateLatestMSRT) { + WriteLog "`$UpdateLatestMSRT is set to true." + #Check if $MSRTPath exists, if not, create it + if (-not (Test-Path -Path $MSRTPath)) { + WriteLog "Creating $MSRTPath" + New-Item -Path $MSRTPath -ItemType Directory -Force | Out-Null + } + WriteLog "Getting Windows Malicious Software Removal Tool URL" + $MSRTUrl = Get-MSRTUrl -WindowsArch $WindowsArch + $MSRTFileName = Split-Path -Path $MSRTUrl -Leaf + try { + WriteLog "Windows Malicious Software Removal Tool URL for $WindowsArch is $MSRTUrl" + Start-BitsTransferWithRetry -Source $MSRTUrl -Destination "$MSRTPath\$MSRTFileName" + WriteLog "Windows Malicious Software Removal Tool downloaded to $MSRTPath\$MSRTFileName" + } + catch { + Write-Error "Downloading Windows Malicious Software Removal Tool failed" + WriteLog "Downloading Windows Malicious Software Removal Tool failed" + throw $_ + } + WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Malicious Software Removal Tool" + $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + $UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Malicious Software Removal Tool)', ("REM Install Windows Malicious Software Removal Tool`r`nStart /wait d:\MSRT\$MSRTFileName /quiet") + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent + WriteLog "Update complete" + } #Download and Install OneDrive Per Machine if ($UpdateOneDrive) { WriteLog "`$UpdateOneDrive is set to true, checking for latest OneDrive client" From 5dcdd2c36f5a3a15d1a9bb160dd8f638eed7376b Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Tue, 17 Sep 2024 14:03:43 -0700 Subject: [PATCH 04/50] Clean up the Get-MicrosoftDrivers old code and version change --- FFUDevelopment/BuildFFUVM.ps1 | 159 +--------------------------------- 1 file changed, 1 insertion(+), 158 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 7829a3f..d09359b 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -336,7 +336,7 @@ param( [bool]$AllowExternalHardDiskMedia, [bool]$PromptExternalHardDiskMedia = $true ) -$version = '2409.2' +$version = '2410.1' #Check if Hyper-V feature is installed (requires only checks the module) $osInfo = Get-WmiObject -Class Win32_OperatingSystem @@ -541,163 +541,6 @@ function Start-BitsTransferWithRetry { return $false } -# function Get-MicrosoftDrivers { -# param ( -# [string]$Make, -# [string]$Model, -# [int]$WindowsRelease -# ) - -# $url = "https://support.microsoft.com/en-us/surface/download-drivers-and-firmware-for-surface-09bb2e09-2a4b-cb69-0951-078a7739e120" - -# # Download the webpage content -# WriteLog "Getting Surface driver information from $url" -# $OriginalVerbosePreference = $VerbosePreference -# $VerbosePreference = 'SilentlyContinue' -# $webContent = Invoke-WebRequest -Uri $url -UseBasicParsing -Headers $Headers -UserAgent $UserAgent -# $VerbosePreference = $OriginalVerbosePreference -# WriteLog "Complete" - -# # Parse the content of the relevant nested divs -# WriteLog "Parsing web content for models and download links" -# $html = $webContent.Content -# $nestedDivPattern = '
(.*?)
' -# $nestedDivMatches = [regex]::Matches($html, $nestedDivPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline) - -# $models = @() -# $modelPattern = '

(.*?)

\s*\s*\s*

\s* Date: Tue, 17 Sep 2024 20:36:22 -0400 Subject: [PATCH 05/50] Adjust install command for MSRT --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index bf01154..4c2cec4 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3969,7 +3969,7 @@ if ($InstallApps) { } WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Malicious Software Removal Tool" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" - $UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Malicious Software Removal Tool)', ("REM Install Windows Malicious Software Removal Tool`r`nStart /wait d:\MSRT\$MSRTFileName /quiet") + $UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Malicious Software Removal Tool)', ("REM Install Windows Malicious Software Removal Tool`r`nd:\MSRT\$MSRTFileName /quiet") Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent WriteLog "Update complete" } From ec7e9a546cc7da79ee4b12325953c08ca768a0f1 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:08:09 -0400 Subject: [PATCH 06/50] Adjust pattern for URL scraping --- FFUDevelopment/BuildFFUVM.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 4c2cec4..101df9c 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2321,7 +2321,7 @@ function Get-MSRTUrl { ) try { # Retrieve content of Windows Malicious Software Removal Tool page - $pattern = '\"url\":\"(https://download\.microsoft\.com/download/[^"]+)\"' + $pattern = 'https:\/\/download\.microsoft\.com\/download\/[^\s]*\.exe' $OriginalVerbosePreference = $VerbosePreference $VerbosePreference = 'SilentlyContinue' if ($WindowsArch -eq "x64") { @@ -2337,7 +2337,7 @@ function Get-MSRTUrl { WriteLog "Failed to retrieve Malicious Software Removal Tool URL. Pattern match failed" return } - $MSRTUrl = $MSRTMatch.Groups[1].Value + $MSRTUrl = $MSRTMatch.Value return $MSRTUrl } catch { From e3cbcab6b2944404aec397c76078bd4453177a13 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 03:41:40 +0200 Subject: [PATCH 07/50] Update BuildFFUVM.ps1 - Add script parameters to allow Windows Server image creation --- FFUDevelopment/BuildFFUVM.ps1 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 7829a3f..0c3011b 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -201,7 +201,11 @@ param( [Parameter(Mandatory = $false, Position = 0)] [ValidateScript({ Test-Path $_ })] [string]$ISOPath, - [ValidateSet('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N')] + [ValidateScript({ + $allowedSKUs = @('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)') + if ($allowedSKUs -contains $_) { $true } else { throw "Invalid WindowsSKU value. Allowed values: $($allowedSKUs -join ', ')" } + return $true + })] [string]$WindowsSKU = 'Pro', [ValidateScript({ Test-Path $_ })] [string]$FFUDevelopmentPath = $PSScriptRoot, @@ -267,7 +271,7 @@ param( [string]$ProductKey, [bool]$BuildUSBDrive, [Parameter(Mandatory = $false)] - [ValidateSet(10, 11)] + [ValidateSet(10, 11, 2016, 2019, 2022, 2025)] [int]$WindowsRelease = 11, [Parameter(Mandatory = $false)] [string]$WindowsVersion = '23h2', From cafc45dbba49948b96eff8ff31dc8b34cbbcb11f Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 03:53:45 +0200 Subject: [PATCH 08/50] Update BuildFFUVM.ps1 - Add Dell driver download support for Windows Server 2016, 2022 and fall back to 2022 drivers for other Server versions. --- FFUDevelopment/BuildFFUVM.ps1 | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 0c3011b..aa1a1e6 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1325,10 +1325,21 @@ function Get-DellDrivers { [string]$Model, [Parameter(Mandatory = $true)] [ValidateSet("x64", "x86", "ARM64")] - [string]$WindowsArch + [string]$WindowsArch, + [Parameter(Mandatory = $true)] + [string]$WindowsRelease ) - $catalogUrl = "http://downloads.dell.com/catalog/CatalogPC.cab" + if ($WindowsRelease -le 11) { + $catalogUrl = "http://downloads.dell.com/catalog/CatalogPC.cab" + $DellCabFile = "$DriversFolder\CatalogPC.cab" + $DellCatalogXML = "$DriversFolder\CatalogPC.XML" + } else { + $catalogUrl = "https://downloads.dell.com/catalog/Catalog.cab" + $DellCabFile = "$DriversFolder\Catalog.cab" + $DellCatalogXML = "$DriversFolder\Catalog.xml" + } + if (-not (Test-Url -Url $catalogUrl)) { WriteLog "Dell Catalog cab URL is not accessible: $catalogUrl Exiting" if ($VerbosePreference -ne 'Continue') { @@ -1348,12 +1359,10 @@ function Get-DellDrivers { New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null WriteLog "Dell Drivers folder created" - $DellCabFile = "$DriversFolder\CatalogPC.cab" WriteLog "Downloading Dell Catalog cab file: $catalogUrl to $DellCabFile" Start-BitsTransferWithRetry -Source $catalogUrl -Destination $DellCabFile WriteLog "Dell Catalog cab file downloaded" - $DellCatalogXML = "$DriversFolder\CatalogPC.XML" WriteLog "Extracting Dell Catalog cab file to $DellCatalogXML" Invoke-Process -FilePath Expand.exe -ArgumentList "$DellCabFile $DellCatalogXML" WriteLog "Dell Catalog cab file extracted" @@ -1367,7 +1376,19 @@ function Get-DellDrivers { $models = $component.SupportedSystems.Brand.Model foreach ($item in $models) { if ($item.Display.'#cdata-section' -match $Model) { - $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch } + + if ($WindowsRelease -le 11) { + $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch } + } elseif ($WindowsRelease -eq 2016) { + $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W14") } + } elseif ($WindowsRelease -eq 2019) { + $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W19") } + } elseif ($WindowsRelease -eq 2022) { + $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") } + } else { + $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") } + } + if ($validOS) { $driverPath = $component.path $downloadUrl = $baseLocation + $driverPath @@ -3952,7 +3973,7 @@ if (($make -and $model) -and ($installdrivers -or $copydrivers)) { if ($make -eq 'Dell'){ WriteLog 'Getting Dell drivers' #Dell mixes Win10 and 11 drivers, hence no WindowsRelease parameter - Get-DellDrivers -Model $Model -WindowsArch $WindowsArch + Get-DellDrivers -Model $Model -WindowsArch $WindowsArch -WindowsRelease $WindowsRelease WriteLog 'Getting Dell drivers completed successfully' } } From 0b151f90546b6b19d986f80164e3535c2aba9787 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 03:55:37 +0200 Subject: [PATCH 09/50] Update BuildFFUVM.ps1 - Throw error when trying to download Windows Server as it's not possible --- FFUDevelopment/BuildFFUVM.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index aa1a1e6..73f8a08 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1767,8 +1767,10 @@ function Get-WindowsESD { $cabFileUrl = if ($WindowsRelease -eq 10) { 'https://go.microsoft.com/fwlink/?LinkId=841361' } - else { + elseif ($WindowsRelease -eq 11) { 'https://go.microsoft.com/fwlink/?LinkId=2156292' + } else { + throw "Can't download Windows Server. Please download the Windows setup media from your subscription homepage." } # Download cab file From 5acac4ba5bddbed139b8c5f06d36909e0f5f5eea Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:02:28 +0200 Subject: [PATCH 10/50] Update BuildFFUVM.ps1 - Update Get-LatestWindowsKB to support searching for Windows Server updates - Improve the HTML regex to return a more precise match by using the $WindowsRelease variable for lookbehind based searching --- FFUDevelopment/BuildFFUVM.ps1 | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 73f8a08..3fc1b59 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2414,16 +2414,21 @@ function Get-KBLink { } function Get-LatestWindowsKB { param ( - [ValidateSet(10, 11)] - [int]$WindowsRelease + [Parameter(Mandatory)] + [ValidateSet(10, 11, 2016, 2019, 2022, 2025)] + [int]$WindowsRelease, + [Parameter(Mandatory)] + [string]$WindowsVersion ) # Define the URL of the update history page based on the Windows release if ($WindowsRelease -eq 11) { $updateHistoryUrl = 'https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information' } - else { + elseif ($WindowsRelease -eq 10) { $updateHistoryUrl = 'https://learn.microsoft.com/en-us/windows/release-health/release-information' + } else { + $updateHistoryUrl = 'https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info' } # Use Invoke-WebRequest to fetch the content of the page @@ -2433,7 +2438,11 @@ function Get-LatestWindowsKB { $VerbosePreference = $OriginalVerbosePreference # Use a regular expression to find the KB article number - $kbArticleRegex = 'KB\d+' + if ($WindowsRelease -le 11) { + $kbArticleRegex = "(?:Version $WindowsRelease \(OS build d+\)(?!(KB)).)*?KB\d+" + } else { + $kbArticleRegex = "(?:Windows Server $WindowsRelease \(OS build d+\)(?!(KB)).)*?KB\d+" + } $kbArticle = [regex]::Match($response.Content, $kbArticleRegex).Value return $kbArticle From 3457aedf5d760b67da6039293c2953fb280184cd Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:04:13 +0200 Subject: [PATCH 11/50] Update BuildFFUVM.ps1 - Add support for searching Windows Server SKUs in images in Get-WimIndex --- FFUDevelopment/BuildFFUVM.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3fc1b59..0035aa9 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2563,9 +2563,13 @@ function Get-WimIndex { If ($ISOPath) { $wimindex = switch ($WindowsSKU) { 'Home' { 1 } + 'Standard' { 1 } 'Home_N' { 2 } + 'Standard (Desktop Experience)' { 1 } 'Home_SL' { 3 } + 'Datacenter' { 3 } 'EDU' { 4 } + 'Datacenter (Desktop Experience)' { 4 } 'EDU_N' { 5 } 'Pro' { 6 } 'Pro_N' { 7 } From af28624e2d4f5eeb6cd775ee39a07a799416d1fd Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:08:01 +0200 Subject: [PATCH 12/50] Update BuildFFUVM.ps1 - Update Get-Index to support processing Windows Server images --- FFUDevelopment/BuildFFUVM.ps1 | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 0035aa9..cf9bfc3 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2600,8 +2600,13 @@ function Get-Index { # Get the ImageName of ImageIndex 1 if an ISO was specified, else use ImageIndex 4 - this is usually Home or Education SKU on ESD MCT media if($ISOPath){ - $imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1 - $WindowsImage = $imageIndex.ImageName.Substring(0, 10) + if ($WindowsSKU -notmatch "Standard|Datacenter") { + $imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1 + $WindowsImage = $imageIndex.ImageName.Substring(0, 10) + } else { + $imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1 + $WindowsImage = $imageIndex.ImageName.Substring(0, 19) + } } else{ $imageIndex = $imageIndexes | Where-Object ImageIndex -eq 4 @@ -2619,8 +2624,8 @@ function Get-Index { return $matchingImageIndex.ImageIndex } else { - # Look for either the number 10 or 11 in the ImageName - $relevantImageIndexes = $imageIndexes | Where-Object { ($_.ImageName -like "*10*") -or ($_.ImageName -like "*11*") } + # Look for the numbers 10, 11, 2016, 2019, 2022+ in the ImageName + $relevantImageIndexes = $imageIndexes | Where-Object { ($_.ImageName -match "(10|11|2016|2019|202\d)") } while ($true) { # Present list of ImageNames to the end user if no matching ImageIndex is found From 4b33627d197fd5153e9f9a3a2d4d8aea92f2c22e Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:11:15 +0200 Subject: [PATCH 13/50] Update BuildFFUVM.ps1 - Fix bug in New-OSPartition - CompactOS is now avoided on Windows Server --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index cf9bfc3..832949e 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2737,7 +2737,7 @@ function New-OSPartition { if ((Get-CimInstance Win32_OperatingSystem).Caption -match "Server") { WriteLog (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\") } - if ($CompactOS) { + elseif ($CompactOS) { WriteLog '$CompactOS is set to true, using -Compact switch to apply the WIM file to the OS partition.' WriteLog (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\" -Compact) } From ac485f9c871695f90f7f2d6fb2ecc77978b71630 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:16:57 +0200 Subject: [PATCH 14/50] Update BuildFFUVM.ps1 - Update Get-WindowsVersionInfo - Server Standard is now called 'Srv_Std' - Server Datacenter is now called 'Srv_Dtc' - 17763 is matched as Windows Server 2019 - 20348 is matched as Windows Server 2022 - 26100 is matched as Windows Server 2025 - other versions are matched as $DisplayVersion as a fallback Please remember to update the Windows Server version of 2025 in case it changes until release. --- FFUDevelopment/BuildFFUVM.ps1 | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 832949e..48bd118 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3265,14 +3265,25 @@ Function Get-WindowsVersionInfo { Enterprise { 'Ent' } Education { 'Edu' } ProfessionalWorkstation { 'Pro_Wks' } + ServerStandard { 'Srv_Std' } + ServerDatacenter { 'Srv_Dtc' } } WriteLog "Windows SKU Modified to: $SKU" - if ($CurrentBuild -ge 22000) { - $Name = 'Win11' - } - else { - $Name = 'Win10' + if ($SKU -notmatch "Srv") { + if ($CurrentBuild -ge 22000) { + $Name = 'Win11' + } + else { + $Name = 'Win10' + } + } else { + $Name = switch ($CurrentBuild) { + 26100 { '2025' } + 20348 { '2022' } + 17763 { '2019' } + Default { $DisplayVersion } + } } WriteLog "Unloading registry" From b8bda93e8df60ef00f1f48028231f91c504e161d Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:26:30 +0200 Subject: [PATCH 15/50] Update BuildFFUVM.ps1 - Updated MU Catalog search - Search now includes searching for Windows Server updates Has been verified to work for Windows Server 2022 and Windows Server 2025 only. --- FFUDevelopment/BuildFFUVM.ps1 | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 48bd118..0dba935 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -4233,7 +4233,15 @@ try { #The Windows release info page is updated later than the MU Catalog if ($UpdateLatestCU -and -not $UpdatePreviewCU) { Writelog "`$UpdateLatestCU is set to true, checking for latest CU" - $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" + if ($WindowsRelease -le 11) { + $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -eq 2022) { + $Name = """Cumulative Update for Microsoft server operating system, version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -lt 2022) { + $Name = """Cumulative update for Windows 10 Version $WindowsVersion for $WindowsArch""" + } else { + $Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch""" + } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" @@ -4248,7 +4256,15 @@ try { #will take Precendence over $UpdateLastestCU if both were set to $true if ($UpdatePreviewCU) { Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" - $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" + if ($WindowsRelease -le 11) { + $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -eq 2022) { + $Name = """Cumulative Update Preview for Microsoft server operating system, version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -lt 2022) { + $Name = """Cumulative update Preview for Windows 10 Version $WindowsVersion for $WindowsArch""" + } else { + $Name = """Cumulative update Preview for Windows 11 Version $WindowsVersion for $WindowsArch""" + } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" From b6dda55a827306b1b5b619175ef342cf335af355 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:29:04 +0200 Subject: [PATCH 16/50] Update BuildFFUVM.ps1 - Updated the .NET Framework download code - Download the appropriate .NET Framework for Windows Server --- FFUDevelopment/BuildFFUVM.ps1 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 0dba935..67552ad 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -4278,7 +4278,13 @@ try { #Update Latest .NET Framework if ($UpdateLatestNet) { Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework" - $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" + if ($WindowsRelease -le 11) { + $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" + } elseif ($WindowsRelease -le 2022) { + $Name = "Cumulative update for .net framework windows 10 $WindowsVersion for $WindowsArch -preview" + } else { + $Name = "Cumulative update for .net framework windows 11 $WindowsVersion for $WindowsArch -preview" + } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" From 412e3a078c7422975edb472bc007b1f163ea0be7 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 23 Sep 2024 04:37:09 +0200 Subject: [PATCH 17/50] Update CaptureFFU.ps1 - Add Windows Server support like in BuildFFUVM.ps1 --- .../WinPECaptureFFUFiles/CaptureFFU.ps1 | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index fb4b964..8a5cee8 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -28,13 +28,24 @@ $SKU = switch ($SKU) { EducationN { 'EduN'} ProfessionalWorkstation { 'Pro_Wks' } ProfessionalWorkstationN { 'Pro_WksN' } + ServerStandard { 'Srv_Std' } + ServerDatacenter { 'Srv_Dtc' } } -if($CurrentBuild -ge 22000){ - $Name = 'Win11' -} -else{ - $Name = 'Win10' +if ($SKU -notmatch "Srv") { + if ($CurrentBuild -ge 22000) { + $Name = 'Win11' + } + else { + $Name = 'Win10' + } +} else { + $Name = switch ($CurrentBuild) { + 26100 { '2025' } + 20348 { '2022' } + 17763 { '2019' } + Default { $DisplayVersion } + } } #If Office is installed, modify the file name of the FFU From c93c417dba2afb27c3b2e4753598a8d498a7bb3e Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Mon, 23 Sep 2024 23:42:09 -0400 Subject: [PATCH 18/50] Removed Get-MSRTUrl function and use Microsoft Update Catalog to retrieve latest MSRT --- FFUDevelopment/BuildFFUVM.ps1 | 48 +++-------------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 101df9c..6fa9984 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2314,39 +2314,6 @@ function Save-KB { return $fileName } -function Get-MSRTUrl { - param ( - [ValidateSet("x86", "x64")] - [string]$WindowsArch - ) - try { - # Retrieve content of Windows Malicious Software Removal Tool page - $pattern = 'https:\/\/download\.microsoft\.com\/download\/[^\s]*\.exe' - $OriginalVerbosePreference = $VerbosePreference - $VerbosePreference = 'SilentlyContinue' - if ($WindowsArch -eq "x64") { - $MSRTWebPage = Invoke-RestMethod "https://www.microsoft.com/en-us/download/details.aspx?id=9905" -Headers $Headers -UserAgent $UserAgent - } - if ($WindowsArch -eq "x86") { - $MSRTWebPage = Invoke-RestMethod "https://www.microsoft.com/en-us/download/details.aspx?id=16" -Headers $Headers -UserAgent $UserAgent - } - $VerbosePreference = $OriginalVerbosePreference - # Extract download URL based on specified pattern - $MSRTMatch = [regex]::Match($MSRTWebPage, $pattern) - if (-not $MSRTMatch.Success) { - WriteLog "Failed to retrieve Malicious Software Removal Tool URL. Pattern match failed" - return - } - $MSRTUrl = $MSRTMatch.Value - return $MSRTUrl - } - catch { - WriteLog $_ - Write-Error "Error occurred while retrieving Windows Malicious Software Removal Tool link" - throw $_ - } -} - function New-AppsISO { #Create Apps ISO file $OSCDIMG = "$adkpath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe" @@ -3949,24 +3916,15 @@ if ($InstallApps) { } if ($UpdateLatestMSRT) { WriteLog "`$UpdateLatestMSRT is set to true." + $Name = "Windows Malicious Software Removal Tool" #Check if $MSRTPath exists, if not, create it if (-not (Test-Path -Path $MSRTPath)) { WriteLog "Creating $MSRTPath" New-Item -Path $MSRTPath -ItemType Directory -Force | Out-Null } WriteLog "Getting Windows Malicious Software Removal Tool URL" - $MSRTUrl = Get-MSRTUrl -WindowsArch $WindowsArch - $MSRTFileName = Split-Path -Path $MSRTUrl -Leaf - try { - WriteLog "Windows Malicious Software Removal Tool URL for $WindowsArch is $MSRTUrl" - Start-BitsTransferWithRetry -Source $MSRTUrl -Destination "$MSRTPath\$MSRTFileName" - WriteLog "Windows Malicious Software Removal Tool downloaded to $MSRTPath\$MSRTFileName" - } - catch { - Write-Error "Downloading Windows Malicious Software Removal Tool failed" - WriteLog "Downloading Windows Malicious Software Removal Tool failed" - throw $_ - } + $MSRTFileName = Save-KB -Name $Name -Path $MSRTPath + WriteLog "Latest Windows Malicious Software Removal Tool saved to $MSRTPath\$MSRTFileName" WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Malicious Software Removal Tool" $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" $UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Malicious Software Removal Tool)', ("REM Install Windows Malicious Software Removal Tool`r`nd:\MSRT\$MSRTFileName /quiet") From 15c0478710dc463b3c1bf3e0b6ee94a2ac476956 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Thu, 26 Sep 2024 17:15:17 +0200 Subject: [PATCH 19/50] Update BuildFFUVM.ps1 1/2 - Add parameter $AppsScriptVariablesvariable - Allow value based behavior changes during the app install phase in InstallAppsandSysprep.cmd --- FFUDevelopment/BuildFFUVM.ps1 | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3d98155..d97fd16 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -210,6 +210,7 @@ param( [ValidateScript({ Test-Path $_ })] [string]$FFUDevelopmentPath = $PSScriptRoot, [bool]$InstallApps, + [hashtable]$AppsScriptVariables, [bool]$InstallOffice, [ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')] [string]$Make, @@ -4028,6 +4029,31 @@ if ($InstallApps) { Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent WriteLog "Update complete" } + + if (-not $AppsScriptVariables) { + #Modify InstallAppsandSysprep.cmd to remove the script variables + $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + $StartIndex = $CmdContent.IndexOf("REM START Batch variables placeholder") + $EndIndex = $CmdContent.IndexOf("REM END Batch variables placeholder") + if (($StartIndex + 1) -lt $EndIndex) { + for ($i = ($StartIndex + 1); $i -lt $EndIndex; $i++) { + $CmdContent[$i] = $null + } + } + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $CmdContent + } + + if ($AppsScriptVariables) { + #Modify InstallAppsandSysprep.cmd to add the script variables + $CmdContent = [System.Collections.ArrayList](Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd") + $ScriptIndex = $CmdContent.IndexOf("REM START Batch variables placeholder") + 1 + foreach ($VariableKey in $AppsScriptVariables.Keys) { + $CmdContent.Insert($ScriptIndex, ("set {0}={1}" -f $VariableKey, $AppsScriptVariables[$VariableKey])) + $ScriptIndex++ + } + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $CmdContent + } + #Create Apps ISO WriteLog "Creating $AppsISO file" New-AppsISO From 6a0faa958ec8dc9674abaade6b2a1446629af271 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Thu, 26 Sep 2024 17:16:22 +0200 Subject: [PATCH 20/50] Update InstallAppsandSysprep.cmd 2/2 - Add parameter $AppsScriptVariablesvariable - Allow value based behavior changes during the app install phase in InstallAppsandSysprep.cmd --- FFUDevelopment/Apps/InstallAppsandSysprep.cmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd index aba7da8..ba69335 100644 --- a/FFUDevelopment/Apps/InstallAppsandSysprep.cmd +++ b/FFUDevelopment/Apps/InstallAppsandSysprep.cmd @@ -8,6 +8,8 @@ REM Install Windows Security Platform Update REM Install OneDrive Per Machine REM Install Edge Stable REM Winget Win32 Apps +REM START Batch variables placeholder +REM END Batch variables placeholder REM Add additional apps below here REM Contoso App (Example) REM msiexec /i d:\Contoso\setup.msi /qn /norestart From 5545554d7e07a57e2cdd2e9e57a782c2ba558bc4 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Thu, 26 Sep 2024 20:08:47 +0200 Subject: [PATCH 21/50] Update BuildFFUVM.ps1 - Fix accidental variable accumulation in InstallAppsandSysprep.cmd --- FFUDevelopment/BuildFFUVM.ps1 | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index d97fd16..5e2f5b6 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -4030,18 +4030,16 @@ if ($InstallApps) { WriteLog "Update complete" } - if (-not $AppsScriptVariables) { - #Modify InstallAppsandSysprep.cmd to remove the script variables - $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" - $StartIndex = $CmdContent.IndexOf("REM START Batch variables placeholder") - $EndIndex = $CmdContent.IndexOf("REM END Batch variables placeholder") - if (($StartIndex + 1) -lt $EndIndex) { - for ($i = ($StartIndex + 1); $i -lt $EndIndex; $i++) { - $CmdContent[$i] = $null - } + #Modify InstallAppsandSysprep.cmd to remove old script variables + $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" + $StartIndex = $CmdContent.IndexOf("REM START Batch variables placeholder") + $EndIndex = $CmdContent.IndexOf("REM END Batch variables placeholder") + if (($StartIndex + 1) -lt $EndIndex) { + for ($i = ($StartIndex + 1); $i -lt $EndIndex; $i++) { + $CmdContent[$i] = $null } - Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $CmdContent } + Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $CmdContent if ($AppsScriptVariables) { #Modify InstallAppsandSysprep.cmd to add the script variables From 6e5d634af65796aad3ec7dbfcd99103184d41bc5 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Fri, 27 Sep 2024 02:15:25 +0200 Subject: [PATCH 22/50] Update BuildFFUVM.ps1 - Bypass VMSwitchIPAddress to VMHostAddress check on systems with a configured NAT setup --- FFUDevelopment/BuildFFUVM.ps1 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index dadc8cf..2e6e681 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3805,7 +3805,19 @@ if (($VMHostIPAddress) -and ($VMSwitchName)){ throw "IP address for -VMSwitchName $VMSwitchName not found. Please check the -VMSwitchName parameter and try again." } if ($VMSwitchIPAddress -ne $VMHostIPAddress) { - throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." + try { + # Bypass the check for systems that could have a Hyper-V NAT switch + $null = Get-NetNat -ErrorAction Stop + $NetNat = @(Get-NetNat -ErrorAction Stop); + } catch { + throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." + } + if ($NetNat.Count -gt 0) { + WriteLog "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress!" + WriteLog "NAT setup detected, remember to configure NATing if the FFU image can't be captured to the network share on the host." + } else { + throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." + } } WriteLog '-VMSwitchName and -VMHostIPAddress validation complete' } From 5194133a78eaafc0f4e6aa626f680651e9f2133a Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Fri, 27 Sep 2024 18:34:41 +0200 Subject: [PATCH 23/50] Update BuildFFUVM.ps1 - Add help for parameter AppsScriptVariables --- FFUDevelopment/BuildFFUVM.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 2e6e681..3dcf517 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -169,6 +169,9 @@ Make of the device to download drivers. Accepted values are: 'Microsoft', 'Dell' .PARAMETER Model Model of the device to download drivers. This is required if Make is set. +.PARAMETER AppsScriptVariables +When passed a hashtable, the script will alter the $FFUDevelopmentPath\Apps\InstallAppsandSysprep.cmd file to set variables with the hashtable keys as variable names and the hashtable values their content. + .EXAMPLE Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates. .\BuildFFUVM.ps1 -WindowsSKU 'Pro' -Installapps $true -InstallOffice $true -InstallDrivers $true -VMSwitchName 'Name of your VM Switch in Hyper-V' -VMHostIPAddress 'Your IP Address' -CreateCaptureMedia $true -CreateDeploymentMedia $true -BuildUSBDrive $true -UpdateLatestCU $true -UpdateLatestNet $true -UpdateLatestDefender $true -UpdateEdge $true -UpdateOneDrive $true -verbose From d45b6dc8dcc80e3ae165c1065a4effcf6d6bd39a Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 30 Sep 2024 14:14:33 +0200 Subject: [PATCH 24/50] Update BuildFFUVM.ps1 - improve Optimize-FFUCaptureDrive - use the drive letter that the Windows partition gets after mounting the vhdx file --- FFUDevelopment/BuildFFUVM.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 40a45c0..dda476f 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -2863,11 +2863,12 @@ function Optimize-FFUCaptureDrive { ) try { WriteLog 'Mounting VHDX for volume optimization' - Mount-VHD -Path $VhdxPath + $mountedDisk = Mount-VHD -Path $VhdxPath -Passthru | Get-Disk + $osPartition = $mountedDisk | Get-Partition | Where-Object { $_.GptType -eq "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" } WriteLog 'Defragmenting Windows partition...' - Optimize-Volume -DriveLetter W -Defrag -NormalPriority -Verbose + Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority -Verbose WriteLog 'Performing slab consolidation on Windows partition...' - Optimize-Volume -DriveLetter W -SlabConsolidate -NormalPriority -Verbose + Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority -Verbose WriteLog 'Dismounting VHDX' Dismount-ScratchVhdx -VhdxPath $VhdxPath WriteLog 'Mounting VHDX as read-only for optimization' From 3694e1a6e408465c42d4a79fca097b868275b9a5 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 30 Sep 2024 15:44:40 +0200 Subject: [PATCH 25/50] Update BuildFFUVM.ps1 - Add parameter $AllowVHDXCaching - Adds the ability to cache VHDX files together with their configuration - This should roughly cut the build time in half as long as there are no new patches released since the last cached VHDX file --- FFUDevelopment/BuildFFUVM.ps1 | 258 ++++++++++++++++++++++++++-------- 1 file changed, 201 insertions(+), 57 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index dda476f..94ebe9d 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -312,6 +312,7 @@ param( [bool]$UpdateLatestMSRT, [bool]$UpdateEdge, [bool]$UpdateOneDrive, + [bool]$AllowVHDXCaching, [bool]$CopyPPKG, [bool]$CopyUnattend, [bool]$CopyAutopilot, @@ -342,6 +343,23 @@ param( ) $version = '2410.1' +#Class definition for vhdx cache +class VhdxCacheUpdateItem { + [string]$Name + VhdxCacheUpdateItem([string]$Name) { + $this.Name = $Name + } +} + +class VhdxCacheItem { + [string]$VhdxFileName = "" + [string]$WindowsSKU = "" + [string]$WindowsRelease = "" + [string]$WindowsVersion = "" + [string]$OptionalFeatures = "" + [VhdxCacheUpdateItem[]]$IncludedUpdates = @() +} + #Check if Hyper-V feature is installed (requires only checks the module) $osInfo = Get-WmiObject -Class Win32_OperatingSystem $isServer = $osInfo.Caption -match 'server' @@ -384,6 +402,7 @@ if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" } if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" } if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" } if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" } +if (-not $VHDXCacheFolder) { $VHDXCacheFolder = "$FFUDevelopmentPath\VHDXCache" } #FUNCTIONS @@ -4032,6 +4051,127 @@ if ($InstallApps) { #Create VHDX try { + #Update latest Cumulative Update if both $UpdateLatestCU is $true and $UpdatePreviewCU is $false + #Changed to use MU Catalog instead of using Get-LatestWindowsKB + #The Windows release info page is updated later than the MU Catalog + if ($UpdateLatestCU -and -not $UpdatePreviewCU) { + Writelog "`$UpdateLatestCU is set to true, checking for latest CU" + if ($WindowsRelease -le 11) { + $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -eq 2022) { + $Name = """Cumulative Update for Microsoft server operating system, version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -lt 2022) { + $Name = """Cumulative update for Windows 10 Version $WindowsVersion for $WindowsArch""" + } else { + $Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch""" + } + WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" + $KBFilePath = Save-KB -Name $Name -Path $KBPath + WriteLog "Latest CU saved to $KBPath\$KBFilePath" + } + + #Update Latest Preview Cumlative Update + #will take Precendence over $UpdateLastestCU if both were set to $true + if ($UpdatePreviewCU) { + Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" + if ($WindowsRelease -le 11) { + $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -eq 2022) { + $Name = """Cumulative Update Preview for Microsoft server operating system, version $WindowsVersion for $WindowsArch""" + } elseif ($WindowsRelease -lt 2022) { + $Name = """Cumulative update Preview for Windows 10 Version $WindowsVersion for $WindowsArch""" + } else { + $Name = """Cumulative update Preview for Windows 11 Version $WindowsVersion for $WindowsArch""" + } + WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" + $KBFilePath = Save-KB -Name $Name -Path $KBPath + WriteLog "Latest Preview CU saved to $KBPath\$KBFilePath" + } + + #Update Latest .NET Framework + if ($UpdateLatestNet) { + Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework" + if ($WindowsRelease -le 11) { + $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" + } elseif ($WindowsRelease -le 2022) { + $Name = "Cumulative update for .net framework windows 10 $WindowsVersion for $WindowsArch -preview" + } else { + $Name = "Cumulative update for .net framework windows 11 $WindowsVersion for $WindowsArch -preview" + } + WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" + $KBFilePath = Save-KB -Name $Name -Path $KBPath + WriteLog "Latest .NET saved to $KBPath\$KBFilePath" + } + # #Update Latest Security Platform Update + # if ($UpdateSecurityPlatform) { + # WriteLog "`$UpdateSecurityPlatform is set to true, checking for latest Security Platform Update" + # $Name = "Windows Security platform definition updates" + # #Check if $KBPath exists, if not, create it + # If (-not (Test-Path -Path $KBPath)) { + # WriteLog "Creating $KBPath" + # New-Item -Path $KBPath -ItemType Directory -Force | Out-Null + # } + # WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $KBPath" + # $KBFilePath = Save-KB -Name $Name -Path $KBPath + # WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath" + # } + + #Search for cached VHDX and skip VHDX creation if there's a cached version + if ($AllowVHDXCaching) { + if (Test-Path -Path $VHDXCacheFolder) { + $vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter "*_config.json" | Sort-Object -Property CreationTime -Descending) + $downloadedKBs = @(Get-ChildItem -File -Path $KBPath) + #$jsonDeserializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new() + + foreach ($vhdxJson in $vhdxJsons) { + try { + #$vhdxCacheItem = $jsonDeserializer.Deserialize((Get-Content -Path $vhdxJson.FullName -Raw), [VhdxCacheItem]) + $vhdxCacheItem = Get-Content -Path $vhdxJson.FullName -Raw | ConvertFrom-Json + + if ((($vhdxCacheItem.WindowsSKU -ne $WindowsSKU) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsSKU) -xor [string]::IsNullOrEmpty($WindowsSKU)))) { + WriteLog "WindowsSKU not equal" + continue + } + + if ((($vhdxCacheItem.WindowsRelease -ne $WindowsRelease) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsRelease) -xor [string]::IsNullOrEmpty($WindowsRelease)))) { + WriteLog "WindowsRelease not equal" + continue + } + + if ((($vhdxCacheItem.WindowsVersion -ne $WindowsVersion) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsVersion) -xor [string]::IsNullOrEmpty($WindowsVersion)))) { + WriteLog "WindowsVersion not equal" + continue + } + + if ((($vhdxCacheItem.OptionalFeatures -ne $OptionalFeatures) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.OptionalFeatures) -xor [string]::IsNullOrEmpty($OptionalFeatures)))) { + WriteLog "OptionalFeatures not equal" + continue + } + + if ((Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name).Length -gt 0) { + (Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name) + $downloadedKBs.Name + $vhdxCacheItem.IncludedUpdates.Name + WriteLog "Updates not equal" + continue + } + + WriteLog "Found cached VHDX file with same parameters and patches" + $cachedVHDXFileFound = $true + $cachedVHDXInfo = $vhdxCacheItem + break + } catch { + WriteLog "Reading $vhdxJson Failed with error $_" + } + } + } + } + + if (-Not $cachedVHDXFileFound) { if ($ISOPath) { $wimPath = Get-WimFromISO } @@ -4060,63 +4200,16 @@ try { Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] - #Update latest Cumulative Update if both $UpdateLatestCU is $true and $UpdatePreviewCU is $false - #Changed to use MU Catalog instead of using Get-LatestWindowsKB - #The Windows release info page is updated later than the MU Catalog - if ($UpdateLatestCU -and -not $UpdatePreviewCU) { - Writelog "`$UpdateLatestCU is set to true, checking for latest CU" - $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" - #Check if $KBPath exists, if not, create it - If (-not (Test-Path -Path $KBPath)) { - WriteLog "Creating $KBPath" - New-Item -Path $KBPath -ItemType Directory -Force | Out-Null + if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { + #Check if $KBCachePath exists, if not, create it + if ($AllowUpdateCaching) { + if (-not (Test-Path -Path $KBCachePath)) { + WriteLog "Creating $KBCachePath" + New-Item -Path $KBCachePath -ItemType Directory -Force | Out-Null + } } - WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" - $KBFilePath = Save-KB -Name $Name -Path $KBPath - WriteLog "Latest CU saved to $KBPath\$KBFilePath" } - #Update Latest Preview Cumlative Update - #will take Precendence over $UpdateLastestCU if both were set to $true - if ($UpdatePreviewCU) { - Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" - $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" - #Check if $KBPath exists, if not, create it - If (-not (Test-Path -Path $KBPath)) { - WriteLog "Creating $KBPath" - New-Item -Path $KBPath -ItemType Directory -Force | Out-Null - } - WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" - $KBFilePath = Save-KB -Name $Name -Path $KBPath - WriteLog "Latest Preview CU saved to $KBPath\$KBFilePath" - } - - #Update Latest .NET Framework - if ($UpdateLatestNet) { - Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework" - $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" - #Check if $KBPath exists, if not, create it - If (-not (Test-Path -Path $KBPath)) { - WriteLog "Creating $KBPath" - New-Item -Path $KBPath -ItemType Directory -Force | Out-Null - } - WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" - $KBFilePath = Save-KB -Name $Name -Path $KBPath - WriteLog "Latest .NET saved to $KBPath\$KBFilePath" - } - # #Update Latest Security Platform Update - # if ($UpdateSecurityPlatform) { - # WriteLog "`$UpdateSecurityPlatform is set to true, checking for latest Security Platform Update" - # $Name = "Windows Security platform definition updates" - # #Check if $KBPath exists, if not, create it - # If (-not (Test-Path -Path $KBPath)) { - # WriteLog "Creating $KBPath" - # New-Item -Path $KBPath -ItemType Directory -Force | Out-Null - # } - # WriteLog "Searching for $Name from Microsoft Update Catalog and saving to $KBPath" - # $KBFilePath = Save-KB -Name $Name -Path $KBPath - # WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath" - # } #Add Windows packages @@ -4126,9 +4219,17 @@ try { WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null WriteLog "KBs added to $WindowsPartition" + if ($AllowVHDXCaching) { + $cachedVHDXInfo = [VhdxCacheItem]::new() + $includedUpdates = Get-ChildItem -Path $KBPath -File + + foreach ($includedUpdate in $includedUpdates) { + $cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name)) + } + } WriteLog "Removing $KBPath" Remove-Item -Path $KBPath -Recurse -Force | Out-Null - WriteLog "Clean Up the WinSxS Folder" + WriteLog "Clean Up the WinSxS Folder" Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null WriteLog "Clean Up the WinSxS Folder completed" } @@ -4139,11 +4240,25 @@ try { } } - #Enable Windows Optional Features (e.g. .Net3, etc) If ($OptionalFeatures) { $Source = Join-Path (Split-Path $wimpath) "sxs" Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source + } + + } else { + #Use cached vhdx file + WriteLog "Using cached VHDX file to speed up build proces" + WriteLog "VHDX file is: $($cachedVHDXInfo.VhdxFileName)" + + Robocopy.exe $($VHDXCacheFolder) $($VMPath) $($cachedVHDXInfo.VhdxFileName) /E /COPY:DAT /R:5 /W:5 /J + $VHDXPath = Join-Path $($VMPath) $($cachedVHDXInfo.VhdxFileName) + + $vhdxDisk = Get-VHD -Path $VHDXPath | Mount-VHD -Passthru | Get-Disk + $osPartition = $vhdxDisk | Get-Partition | Where-Object { $_.GptType -eq "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" } + $osPartitionDriveLetter = $osPartition.DriveLetter + $WindowsPartition = $osPartitionDriveLetter + ":\" + } #Set Product key @@ -4165,7 +4280,7 @@ try { If ($InstallApps) { #Copy Unattend file so VM Boots into Audit Mode WriteLog 'Copying unattend file to boot to audit mode' - New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory | Out-Null + New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory -Force | Out-Null if($WindowsArch -eq 'x64'){ Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_x64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null } @@ -4173,6 +4288,35 @@ try { Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_arm64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null } WriteLog 'Copy completed' + } + + if ($AllowVHDXCaching -and !$cachedVHDXFileFound) { + WriteLog 'New cachabe VHDX created' + + WriteLog 'Defragmenting Windows partition...' + Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority -Verbose + WriteLog 'Performing slab consolidation on Windows partition...' + Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority -Verbose + WriteLog 'Dismounting VHDX' + Dismount-ScratchVhdx -VhdxPath $VHDXPath + + WriteLog 'Copying to cache dir' + + #Assuming there are now name collisons + Robocopy.exe $($VMPath) $($VHDXCacheFolder) $("$VMName.vhdx") /E /COPY:DAT /R:5 /W:5 /J + + #Only create new instance if not created during patching + if ($null -eq $cachedVHDXInfo) { + $cachedVHDXInfo = [VhdxCacheItem]::new() + } + $cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx") + $cachedVHDXInfo.WindowsSKU = $WindowsSKU + $cachedVHDXInfo.WindowsRelease = $WindowsRelease + $cachedVHDXInfo.WindowsVersion = $WindowsVersion + $cachedVHDXInfo.OptionalFeatures = $OptionalFeatures + + $cachedVHDXInfo | ConvertTo-Json | Out-File -FilePath ("{0}\{1}_config.json" -f $($VHDXCacheFolder), $VMName) + } else { Dismount-ScratchVhdx -VhdxPath $VHDXPath } } From f144f1d71c64f4a4561548202f1750120df3c7aa Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 30 Sep 2024 16:52:15 +0200 Subject: [PATCH 26/50] Update CaptureFFU.ps1 - Add support for Server Core in SKU name --- FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index 8a5cee8..81a7907 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -12,6 +12,7 @@ reg load "HKLM\FFU" $Software $SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID' [int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild' $DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' +$InstallationType = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'InstallationType' $BuildDate = Get-Date -uformat %b%Y $SKU = switch ($SKU) { @@ -32,7 +33,7 @@ $SKU = switch ($SKU) { ServerDatacenter { 'Srv_Dtc' } } -if ($SKU -notmatch "Srv") { +if ($InstallationType -eq "Client") { if ($CurrentBuild -ge 22000) { $Name = 'Win11' } @@ -46,6 +47,9 @@ if ($SKU -notmatch "Srv") { 17763 { '2019' } Default { $DisplayVersion } } + if ($InstallationType -eq "Server Core") { + $SKU += "_Core" + } } #If Office is installed, modify the file name of the FFU From fbe8eca26367c5b0a3eb2b7a8139f0f40ff8e03d Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Wed, 2 Oct 2024 15:05:41 +0200 Subject: [PATCH 27/50] Update Search Strings: Update BuildFFUVM.ps1 - Update search strings for Windows / .NET Framework updates to get more consistent and reliable results --- FFUDevelopment/BuildFFUVM.ps1 | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3dcf517..b068b96 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -4166,7 +4166,7 @@ try { } elseif ($WindowsRelease -lt 2022) { $Name = """Cumulative update for Windows 10 Version $WindowsVersion for $WindowsArch""" } else { - $Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch""" + $Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch"" ""Security Updates""" } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { @@ -4206,10 +4206,14 @@ try { Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework" if ($WindowsRelease -le 11) { $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" - } elseif ($WindowsRelease -le 2022) { - $Name = "Cumulative update for .net framework windows 10 $WindowsVersion for $WindowsArch -preview" + } elseif ($WindowsRelease -eq 2016) { + $Name = '"Cumulative Update for .NET Framework" "4.8" for Windows 10 Version ' + $WindowsVersion + ' x64 -preview' + } elseif ($WindowsRelease -eq 2019) { + $Name = '"Cumulative Update for .NET Framework" "3.5, 4.7.2 and 4.8" for Windows 10 Version ' + $WindowsVersion + ' x64 -preview' + } elseif ($WindowsRelease -eq 2022) { + $Name = '"Cumulative Update" ".NET Framework" "3.5, 4.8 and 4.8.1" "server operating system " ' + $WindowsVersion + ' x64 -preview' } else { - $Name = "Cumulative update for .net framework windows 11 $WindowsVersion for $WindowsArch -preview" + $Name = '"Cumulative Update for .NET Framework" "3.5 and 4.8.1" for Windows 11 ' + $WindowsVersion + ' x64 -preview' } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { From f09c404f65e805e6ca227c5190102cf69e77803b Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Thu, 3 Oct 2024 03:47:33 +0200 Subject: [PATCH 28/50] Update BuildFFUVM.ps1 - Find better results for Windows Server 2019 .NET Framework updates --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index b068b96..0fc53bc 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -4209,7 +4209,7 @@ try { } elseif ($WindowsRelease -eq 2016) { $Name = '"Cumulative Update for .NET Framework" "4.8" for Windows 10 Version ' + $WindowsVersion + ' x64 -preview' } elseif ($WindowsRelease -eq 2019) { - $Name = '"Cumulative Update for .NET Framework" "3.5, 4.7.2 and 4.8" for Windows 10 Version ' + $WindowsVersion + ' x64 -preview' + $Name = '"Cumulative Update for .NET Framework" "3.5, 4.7.2 and 4.8" Server ' + $WindowsRelease + ' x64 -preview' } elseif ($WindowsRelease -eq 2022) { $Name = '"Cumulative Update" ".NET Framework" "3.5, 4.8 and 4.8.1" "server operating system " ' + $WindowsVersion + ' x64 -preview' } else { From 658d2d7af454160e380cec45ae3671a94421cb7b Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:58:21 -0700 Subject: [PATCH 29/50] - Added server skus to validateset for $WindowsSKU - Added new variable $installationType which uses $WindowsRelease to determine Server or Client. If $installationType is Server, $WindowsRelease version is used to set $WindowsVersion to the appropriate version (1607, 1809, 21H2) - Fixed an issue where the recovery partition wouldn't be created on server OSes due to winre.wim being hidden. Never saw this on client OSes even though it also was hidden IIRC. - Removed verbosity for Optimize-Volume as it was outputting when -verbose was not specified. - Modified some search strings for .NET CUs when installing on Server OS - Included SSU for Windows Server 2016 as it's mandatory - Added some error checking for Server 2019 and 2022 CU installations when CU fails due to lack of SSU. If you run into this error, you're using old media and should use the latest. Always use the latest ISO if you can. --- FFUDevelopment/BuildFFUVM.ps1 | 140 ++++++++++++------ .../WinPECaptureFFUFiles/CaptureFFU.ps1 | 22 ++- 2 files changed, 106 insertions(+), 56 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 3dcf517..a057493 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -207,15 +207,13 @@ param( [Parameter(Mandatory = $false, Position = 0)] [ValidateScript({ Test-Path $_ })] [string]$ISOPath, - [ValidateScript({ - $allowedSKUs = @('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)') - if ($allowedSKUs -contains $_) { $true } else { throw "Invalid WindowsSKU value. Allowed values: $($allowedSKUs -join ', ')" } - return $true - })] + [ValidateSet('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)')] [string]$WindowsSKU = 'Pro', [ValidateScript({ Test-Path $_ })] [string]$FFUDevelopmentPath = $PSScriptRoot, + [bool]$InstallApps, + [hashtable]$AppsScriptVariables, [bool]$InstallOffice, [ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')] @@ -392,7 +390,15 @@ if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" } if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" } if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" } if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" } - +if (-not $installationType) { $installationType = if ($WindowsRelease.ToString().Length -eq 2) { 'Client' } else { 'Server' } } +if ($installationType -eq 'Server'){ + #Map $WindowsRelease to $WindowsVersion for Windows Server + switch ($WindowsRelease) { + 2016 { $WindowsVersion = '1607' } + 2019 { $WindowsVersion = '1809' } + 2022 { $WindowsVersion = '21H2' } + } +} #FUNCTIONS function WriteLog($LogText) { @@ -1179,9 +1185,10 @@ function Get-DellDrivers { [ValidateSet("x64", "x86", "ARM64")] [string]$WindowsArch, [Parameter(Mandatory = $true)] - [string]$WindowsRelease + [int]$WindowsRelease ) + #CatalogPC.cab is the catalog for Windows client PCs, Catalog.cab is the catalog for Windows Server if ($WindowsRelease -le 11) { $catalogUrl = "http://downloads.dell.com/catalog/CatalogPC.cab" $DellCabFile = "$DriversFolder\CatalogPC.cab" @@ -1622,7 +1629,7 @@ function Get-WindowsESD { elseif ($WindowsRelease -eq 11) { 'https://go.microsoft.com/fwlink/?LinkId=2156292' } else { - throw "Can't download Windows Server. Please download the Windows setup media from your subscription homepage." + throw "Downloading Windows Server is not supported. Please use the -ISOPath parameter to specify the path to the Windows Server ISO file." } # Download cab file @@ -2620,7 +2627,7 @@ function New-RecoveryPartition { $calculatedRecoverySize = $RecoveryPartitionSize } else { - $winReWim = Get-ChildItem "$($OsPartition.DriveLetter):\Windows\System32\Recovery\Winre.wim" + $winReWim = Get-ChildItem "$($OsPartition.DriveLetter):\Windows\System32\Recovery\Winre.wim" -Attributes Hidden -ErrorAction SilentlyContinue if (($null -ne $winReWim) -and ($winReWim.Count -eq 1)) { # Wim size + 100MB is minimum WinRE partition size. @@ -2914,9 +2921,9 @@ function Optimize-FFUCaptureDrive { WriteLog 'Mounting VHDX for volume optimization' Mount-VHD -Path $VhdxPath WriteLog 'Defragmenting Windows partition...' - Optimize-Volume -DriveLetter W -Defrag -NormalPriority -Verbose + Optimize-Volume -DriveLetter W -Defrag -NormalPriority WriteLog 'Performing slab consolidation on Windows partition...' - Optimize-Volume -DriveLetter W -SlabConsolidate -NormalPriority -Verbose + Optimize-Volume -DriveLetter W -SlabConsolidate -NormalPriority WriteLog 'Dismounting VHDX' Dismount-ScratchVhdx -VhdxPath $VhdxPath WriteLog 'Mounting VHDX as read-only for optimization' @@ -3106,8 +3113,12 @@ Function Get-WindowsVersionInfo { WriteLog "Windows SKU: $SKU" [int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild' WriteLog "Windows Build: $CurrentBuild" + #DisplayVersion does not exist for 1607 builds (RS1 and Server 2016) and Server 2019 + if($CurrentBuild -notin (14393, 17763)) { $DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' WriteLog "Windows Version: $DisplayVersion" + } + $BuildDate = Get-Date -uformat %b%Y $SKU = switch ($SKU) { @@ -3117,7 +3128,7 @@ Function Get-WindowsVersionInfo { Enterprise { 'Ent' } Education { 'Edu' } ProfessionalWorkstation { 'Pro_Wks' } - ServerStandard { 'Srv_Std' } + ServerStandard { 'Srv_Std' } ServerDatacenter { 'Srv_Dtc' } } WriteLog "Windows SKU Modified to: $SKU" @@ -3129,11 +3140,13 @@ Function Get-WindowsVersionInfo { else { $Name = 'Win10' } - } else { + } + else { $Name = switch ($CurrentBuild) { 26100 { '2025' } 20348 { '2022' } 17763 { '2019' } + 14393 { '2016' } Default { $DisplayVersion } } } @@ -3809,18 +3822,20 @@ if (($VMHostIPAddress) -and ($VMSwitchName)){ } if ($VMSwitchIPAddress -ne $VMHostIPAddress) { try { - # Bypass the check for systems that could have a Hyper-V NAT switch - $null = Get-NetNat -ErrorAction Stop - $NetNat = @(Get-NetNat -ErrorAction Stop); - } catch { - throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." + # Bypass the check for systems that could have a Hyper-V NAT switch + $null = Get-NetNat -ErrorAction Stop + $NetNat = @(Get-NetNat -ErrorAction Stop) } - if ($NetNat.Count -gt 0) { + catch { + throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." + } + if ($NetNat.Count -gt 0) { WriteLog "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress!" - WriteLog "NAT setup detected, remember to configure NATing if the FFU image can't be captured to the network share on the host." - } else { - throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." - } + WriteLog "NAT setup detected, remember to configure NATing if the FFU image can't be captured to the network share on the host." + } + else { + throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again." + } } WriteLog '-VMSwitchName and -VMHostIPAddress validation complete' } @@ -4159,38 +4174,38 @@ try { #The Windows release info page is updated later than the MU Catalog if ($UpdateLatestCU -and -not $UpdatePreviewCU) { Writelog "`$UpdateLatestCU is set to true, checking for latest CU" - if ($WindowsRelease -le 11) { + if ($installationType -eq 'Client') { $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" - } elseif ($WindowsRelease -eq 2022) { - $Name = """Cumulative Update for Microsoft server operating system, version $WindowsVersion for $WindowsArch""" - } elseif ($WindowsRelease -lt 2022) { - $Name = """Cumulative update for Windows 10 Version $WindowsVersion for $WindowsArch""" - } else { - $Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch""" + } + if ($WindowsRelease -eq 2022) { + $Name = """Cumulative Update for Microsoft server operating system, version 22h2 for $WindowsArch""" + } + if ($WindowsRelease -in 2016, 2019) { + $Name = """Cumulative update for Windows Server $WindowsRelease for $WindowsArch""" } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" New-Item -Path $KBPath -ItemType Directory -Force | Out-Null } + #Get latest Servicing Stack Update for Windows Server 2016 + if ($WindowsRelease -eq 2016) { + $SSUName = """Servicing stack update for Windows Server $WindowsRelease for $WindowsArch""" + WriteLog "Searching for $SSUName from Microsoft Update Catalog and saving to $KBPath" + $SSUFile = Save-KB -Name $SSUName -Path $KBPath + $SSUFilePath = "$KBPath\$SSUFile" + WriteLog "Latest SSU saved to $SSUFilePath" + } WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" $KBFilePath = Save-KB -Name $Name -Path $KBPath WriteLog "Latest CU saved to $KBPath\$KBFilePath" } - #Update Latest Preview Cumlative Update + #Update Latest Preview Cumlative Update for Client OS only #will take Precendence over $UpdateLastestCU if both were set to $true - if ($UpdatePreviewCU) { + if ($UpdatePreviewCU -and $installationType -eq 'Client') { Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" - if ($WindowsRelease -le 11) { - $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" - } elseif ($WindowsRelease -eq 2022) { - $Name = """Cumulative Update Preview for Microsoft server operating system, version $WindowsVersion for $WindowsArch""" - } elseif ($WindowsRelease -lt 2022) { - $Name = """Cumulative update Preview for Windows 10 Version $WindowsVersion for $WindowsArch""" - } else { - $Name = """Cumulative update Preview for Windows 11 Version $WindowsVersion for $WindowsArch""" - } + $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" @@ -4204,12 +4219,17 @@ try { #Update Latest .NET Framework if ($UpdateLatestNet) { Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework" - if ($WindowsRelease -le 11) { - $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" - } elseif ($WindowsRelease -le 2022) { - $Name = "Cumulative update for .net framework windows 10 $WindowsVersion for $WindowsArch -preview" - } else { - $Name = "Cumulative update for .net framework windows 11 $WindowsVersion for $WindowsArch -preview" + if ($installationType -eq 'Client') { + $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" + } + if ($WindowsRelease -eq 2022) { + $Name = """Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1"" " + """operating system version 21H2 for x64""" + } + if ($WindowsRelease -eq 2019) { + $Name = """Cumulative Update for .NET Framework 3.5, 4.7.2 and 4.8 for Windows Server 2019 for x64""" + } + if ($WindowsRelease -eq 2016) { + $Name = """Cumulative Update for .NET Framework 4.8 for Windows Server 2016 for x64""" } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { @@ -4240,17 +4260,41 @@ try { try { WriteLog "Adding KBs to $WindowsPartition" WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null + # If WindowsRelease is 2016, we need to add the SSU first + if ($WindowsRelease -eq 2016) { + WriteLog "WindowsRelease is 2016, adding SSU first" + WriteLog "Adding SSU to $WindowsPartition" + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null + # Commenting out -preventpending as it causes an issue with the SSU being applied + # Seems to be because of the registry being mounted per dism.log + Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null + WriteLog "SSU added to $WindowsPartition" + WriteLog "Removing $SSUFilePath" + Remove-Item -Path $SSUFilePath -Force | Out-Null + WriteLog 'SSU removed' + WriteLog "Adding CU to $WindowsPartition" + } + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null + Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null WriteLog "KBs added to $WindowsPartition" WriteLog "Removing $KBPath" Remove-Item -Path $KBPath -Recurse -Force | Out-Null WriteLog "Clean Up the WinSxS Folder" + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null WriteLog "Clean Up the WinSxS Folder completed" } catch { Write-Host "Adding KB to VHDX failed with error $_" WriteLog "Adding KB to VHDX failed with error $_" + if ($_.Exception.HResult -eq -2146498525){ + Write-Host 'Missing latest Servicing Stack Update' + Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + Write-Host 'Recommended to use the latest media' + WriteLog 'Missing latest Servicing Stack Update' + WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + WriteLog 'Recommended to use the latest media' + } throw $_ } } diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index 81a7907..1a8fdd7 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -11,22 +11,24 @@ reg load "HKLM\FFU" $Software $SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID' [int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild' -$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' +if ($CurrentBuild -notin 14393, 17763) { + $DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' +} $InstallationType = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'InstallationType' $BuildDate = Get-Date -uformat %b%Y $SKU = switch ($SKU) { Core { 'Home' } - CoreN { 'HomeN'} - CoreSingleLanguage { 'HomeSL'} + CoreN { 'HomeN' } + CoreSingleLanguage { 'HomeSL' } Professional { 'Pro' } - ProfessionalN { 'ProN'} + ProfessionalN { 'ProN' } ProfessionalEducation { 'Pro_Edu' } ProfessionalEducationN { 'Pro_EduN' } Enterprise { 'Ent' } - EnterpriseN { 'EntN'} + EnterpriseN { 'EntN' } Education { 'Edu' } - EducationN { 'EduN'} + EducationN { 'EduN' } ProfessionalWorkstation { 'Pro_Wks' } ProfessionalWorkstationN { 'Pro_WksN' } ServerStandard { 'Srv_Std' } @@ -40,11 +42,13 @@ if ($InstallationType -eq "Client") { else { $Name = 'Win10' } -} else { +} +else { $Name = switch ($CurrentBuild) { 26100 { '2025' } 20348 { '2022' } 17763 { '2019' } + 14393 { '2016' } Default { $DisplayVersion } } if ($InstallationType -eq "Server Core") { @@ -70,8 +74,10 @@ else{ #Unload Registry Set-Location X:\ Remove-Variable SKU +if ($CurrentBuild -notin 14393, 17763) { + Remove-Variable DisplayVersion +} Remove-Variable CurrentBuild -Remove-Variable DisplayVersion Remove-Variable Office reg unload "HKLM\FFU" #This prevents Critical Process Died errors you can have during deployment of the FFU - may not happen during capture from WinPE, but adding here to be consistent with VHDX capture From 1f65198803d730c897198436edfd17c3eaf10564 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:17:11 -0700 Subject: [PATCH 30/50] - $WindowsVersion set to 24h2, can override by using -WindowsVersion 23H2 if you want the old behavior - Removed the "Downloading information GUID" messages when downloading content from the Microsoft Update Catalog while -verbose was specified in the command line - In Get-KBLink, made a change to just grab the first result returned instead of the entire results page. This removes the need to use a break statement in Save-KB when downloading updates. This fixed an issue with the new 24H2 Checkpoint Cumulative Updates in Win11 and Server 2025. - Changed Windows MSRT search string for x64 and x86. This was mainly to get x64 to the top of the search results. x86 won't actually download since the code isn't in place for content from the MU Catalog to download x86 content (no idea if anyone actually builds x86 FFUs for Win10 - I hope not) --- FFUDevelopment/BuildFFUVM.ps1 | 74 +++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index a057493..056ead9 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -279,7 +279,7 @@ param( [ValidateSet(10, 11, 2016, 2019, 2022, 2025)] [int]$WindowsRelease = 11, [Parameter(Mandatory = $false)] - [string]$WindowsVersion = '23h2', + [string]$WindowsVersion = '24h2', [Parameter(Mandatory = $false)] [ValidateSet('x86', 'x64', 'arm64')] [string]$WindowsArch = 'x64', @@ -2238,9 +2238,16 @@ function Get-KBLink { return } + # $guids = $results.Links | + # Where-Object ID -match '_link' | + # Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } | + # ForEach-Object { $_.id.replace('_link', '') } | + # Where-Object { $_ -in $kbids } + $guids = $results.Links | Where-Object ID -match '_link' | Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } | + Select-Object -First 1 | ForEach-Object { $_.id.replace('_link', '') } | Where-Object { $_ -in $kbids } @@ -2250,7 +2257,7 @@ function Get-KBLink { } foreach ($guid in $guids) { - Write-Verbose -Message "Downloading information for $guid" + # Write-Verbose -Message "Downloading information for $guid" $post = @{ size = 0; updateID = $guid; uidInfo = $guid } | ConvertTo-Json -Compress $body = @{ updateIDs = "[$post]" } $OriginalVerbosePreference = $VerbosePreference @@ -2321,35 +2328,35 @@ function Save-KB { $links = Get-KBLink -Name $kb foreach ($link in $links) { if (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) { - WriteLog "No architecture found in $link, assume this is for all architectures" + WriteLog "No architecture found in $link, skipping" #FIX: 3/22/2024 - the SecurityHealthSetup fix was updated and now includes two files (one is x64 and the other is arm64) #Unfortunately there is no easy way to determine the architecture from the file name #There is a support doc that include links to download, but it's out of date (n-1) #https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3 #These files don't change that often, so will check the link above to see when it updates and may use that #For now this is hard-coded for these specific file names - if ($link -match 'security') { - #Make sure we're getting the correct architecture for the Security Health Setup update - WriteLog "Link: $link matches security" - if ($WindowsArch -eq 'x64') { - if ($link -match 'securityhealthsetup_e1') { - Writelog "Downloading $Link for $WindowsArch to $Path" - Start-BitsTransferWithRetry -Source $link -Destination $Path - $fileName = ($link -split '/')[-1] - Writelog "Returning $fileName" - break - } - } - if ($WindowsArch -eq 'arm64') { - if ($link -match 'securityhealthsetup_25') { - Writelog "Downloading $Link for $WindowsArch to $Path" - Start-BitsTransferWithRetry -Source $link -Destination $Path - $fileName = ($link -split '/')[-1] - Writelog "Returning $fileName" - break - } - } - } + # if ($link -match 'security') { + # #Make sure we're getting the correct architecture for the Security Health Setup update + # WriteLog "Link: $link matches security" + # if ($WindowsArch -eq 'x64') { + # if ($link -match 'securityhealthsetup_e1') { + # Writelog "Downloading $Link for $WindowsArch to $Path" + # Start-BitsTransferWithRetry -Source $link -Destination $Path + # $fileName = ($link -split '/')[-1] + # Writelog "Returning $fileName" + # break + # } + # } + # if ($WindowsArch -eq 'arm64') { + # if ($link -match 'securityhealthsetup_25') { + # Writelog "Downloading $Link for $WindowsArch to $Path" + # Start-BitsTransferWithRetry -Source $link -Destination $Path + # $fileName = ($link -split '/')[-1] + # Writelog "Returning $fileName" + # break + # } + # } + # } } if ($link -match 'x64' -or $link -match 'amd64') { @@ -2359,7 +2366,8 @@ function Save-KB { Start-BitsTransferWithRetry -Source $link -Destination $Path $fileName = ($link -split '/')[-1] Writelog "Returning $fileName" - break + #With Windows 11 24H2 and Checkpoint CUs, there are multiple files that are downloaded + # break } } @@ -2370,7 +2378,8 @@ function Save-KB { Start-BitsTransferWithRetry -Source $link -Destination $Path $fileName = ($link -split '/')[-1] Writelog "Returning $fileName" - break + #With Windows 11 24H2 and Checkpoint CUs, there are multiple files that are downloaded + # break } } } @@ -3265,12 +3274,12 @@ Function Get-USBDrive { if ($VerbosePreference -ne 'Continue') { Write-Host "Found $USBDrivesCount total USB drives" If ($ExternalCount -gt 0) { - Write-Host "$ExternalCount are external drives" + Write-Host "$ExternalCount external drives" } } WriteLog "Found $USBDrivesCount total USB drives" If ($ExternalCount -gt 0) { - WriteLog "$ExternalCount are external drives" + WriteLog "$ExternalCount external drives" } } } @@ -4020,7 +4029,12 @@ if ($InstallApps) { } if ($UpdateLatestMSRT) { WriteLog "`$UpdateLatestMSRT is set to true." - $Name = "Windows Malicious Software Removal Tool" + if ($WindowsArch -eq 'x64') { + $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows $WindowsRelease""" + } + if ($WindowsArch -eq 'x86') { + $Name = """Windows Malicious Software Removal Tool""" + " " + """Windows $WindowsRelease""" + } #Check if $MSRTPath exists, if not, create it if (-not (Test-Path -Path $MSRTPath)) { WriteLog "Creating $MSRTPath" From eae619e7e83976c26fadbab71c0bdba2c9d81569 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 4 Nov 2024 18:23:57 +0100 Subject: [PATCH 31/50] Update BuildFFUVM.ps1 - Fix update search for Windows Server 2025 release --- FFUDevelopment/BuildFFUVM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index e4f1ae7..7ff37ad 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -4192,7 +4192,7 @@ try { $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" } if ($WindowsRelease -eq 2025) { - $Name = """Cumulative update for Windows 11 Version 24h2 for $WindowsArch"" ""Security Updates""" + $Name = """Cumulative Update for Microsoft server operating system, version 24h2 for $WindowsArch""" } if ($WindowsRelease -eq 2022) { $Name = """Cumulative Update for Microsoft server operating system, version 21h2 for $WindowsArch""" From db788c3c3045586a8be4d7b8fc5e69fe2bf6ff5d Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:13:29 -0800 Subject: [PATCH 32/50] =?UTF-8?q?-=20If=20not=20passing=20an=20ISO,=20hard?= =?UTF-8?q?coded=20WindowsVersion=20of=2022H2=20for=20Windows=2010=20or=20?= =?UTF-8?q?24H2=20for=20Windows=2011=20since=20the=20ESD=20media=20only=20?= =?UTF-8?q?provides=20those=20two=20versions.=20Not=20doing=20this=20allow?= =?UTF-8?q?ed=20for=20unnecessary=20VHDX=20creation=20since=20it=20checks?= =?UTF-8?q?=20the=20WindowsVersion=20via=20the=20json=20file.=20This=20als?= =?UTF-8?q?o=20fixes=20an=20issue=20where=20CUs=20could=20be=20searched=20?= =?UTF-8?q?for=20that=20didn=E2=80=99t=20exist,=20but=20the=20media=20woul?= =?UTF-8?q?d=20still=20download=20-=20Added=20some=20additional=20logging?= =?UTF-8?q?=20entries=20-=20Removed=20verbose=20output=20of=20the=20Optimi?= =?UTF-8?q?ze-Volume=20command=20-=20Fixed=20an=20issue=20where=20not=20pa?= =?UTF-8?q?ssing=20an=20ISO=20caused=20the=20script=20to=20fail=20-=20Clea?= =?UTF-8?q?ned=20up=20the=20KBPath=20folder=20at=20the=20end=20of=20the=20?= =?UTF-8?q?run=20-=20Changed=20some=20minor=20formatting=20items?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FFUDevelopment/BuildFFUVM.ps1 | 235 ++++++++++++++++++---------------- 1 file changed, 124 insertions(+), 111 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index f0ced96..1148ae2 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -3900,6 +3900,17 @@ if (($WindowsArch -eq 'ARM64') -and ($UpdateLatestMSRT -eq $true)) { $UpdateLatestMSRT = $false WriteLog 'Windows Malicious Software Removal Tool is not available for the ARM64 architecture.' } +#If downloading ESD from MCT, hardcode WindowsVersion to 22H2 for Windows 10 and 24H2 for Windows 11 +#MCT media only provides 22H2 and 24H2 media +#This prevents issues with VHDX Caching unecessarily and with searching for CUs +if ($ISOPath -eq '') { + if ($WindowsRelease -eq '10') { + $WindowsVersion = '22H2' + } + if ($WindowsRelease -eq '11') { + $WindowsVersion = '24H2' + } +} ###END PARAMETER VALIDATION @@ -4208,7 +4219,7 @@ try { } #Update Latest Preview Cumlative Update for Client OS only - #will take Precendence over $UpdateLastestCU if both were set to $true + #will take Precendence over $UpdateLatestCU if both were set to $true if ($UpdatePreviewCU -and $installationType -eq 'Client') { Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" @@ -4262,37 +4273,41 @@ try { #Search for cached VHDX and skip VHDX creation if there's a cached version if ($AllowVHDXCaching) { + WriteLog 'AllowVHDXCaching is true, checking for cached VHDX file' if (Test-Path -Path $VHDXCacheFolder) { - $vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter "*_config.json" | Sort-Object -Property CreationTime -Descending) + WriteLog "Found $VHDXCacheFolder" + $vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending) + WriteLog "Found $($vhdxJsons.Count) cached VHDX files" $downloadedKBs = @(Get-ChildItem -File -Path $KBPath) #$jsonDeserializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new() foreach ($vhdxJson in $vhdxJsons) { try { + WriteLog "Processing $($vhdxJson.FullName)" #$vhdxCacheItem = $jsonDeserializer.Deserialize((Get-Content -Path $vhdxJson.FullName -Raw), [VhdxCacheItem]) $vhdxCacheItem = Get-Content -Path $vhdxJson.FullName -Raw | ConvertFrom-Json if ((($vhdxCacheItem.WindowsSKU -ne $WindowsSKU) -or ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsSKU) -xor [string]::IsNullOrEmpty($WindowsSKU)))) { - WriteLog "WindowsSKU not equal" + WriteLog 'WindowsSKU mismatch, continuing' continue } if ((($vhdxCacheItem.WindowsRelease -ne $WindowsRelease) -or ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsRelease) -xor [string]::IsNullOrEmpty($WindowsRelease)))) { - WriteLog "WindowsRelease not equal" + WriteLog 'WindowsRelease mismatch, continuing' continue } if ((($vhdxCacheItem.WindowsVersion -ne $WindowsVersion) -or ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsVersion) -xor [string]::IsNullOrEmpty($WindowsVersion)))) { - WriteLog "WindowsVersion not equal" + Writelog 'WindowsVersion mismatch, continuing' continue } if ((($vhdxCacheItem.OptionalFeatures -ne $OptionalFeatures) -or ([string]::IsNullOrEmpty($vhdxCacheItem.OptionalFeatures) -xor [string]::IsNullOrEmpty($OptionalFeatures)))) { - WriteLog "OptionalFeatures not equal" + WriteLog 'OptionalFeatures mismatch, continuing' continue } @@ -4300,11 +4315,11 @@ try { (Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name) $downloadedKBs.Name $vhdxCacheItem.IncludedUpdates.Name - WriteLog "Updates not equal" + WriteLog 'IncludedUpdates mismatch, continuing' continue } - WriteLog "Found cached VHDX file with same parameters and patches" + WriteLog "Found cached VHDX file $vhdxCacheFolder\$($vhdxCacheItem.VhdxFileName) with matching parameters and included updates" $cachedVHDXFileFound = $true $cachedVHDXInfo = $vhdxCacheItem break @@ -4316,116 +4331,102 @@ try { } if (-Not $cachedVHDXFileFound) { - if ($ISOPath) { - $wimPath = Get-WimFromISO - } - else { - $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType - } - #If index not specified by user, try and find based on WindowsSKU - if (-not($index) -and ($WindowsSKU)) { - $index = Get-Index -WindowsImagePath $wimPath -WindowsSKU $WindowsSKU - } - - $vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes - - $systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk - - New-MSRPartition -VhdxDisk $vhdxDisk - - $osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $index - $osPartitionDriveLetter = $osPartition[1].DriveLetter - $WindowsPartition = $osPartitionDriveLetter + ":\" - - #$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition - $recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition - - WriteLog "All necessary partitions created." - - Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] - - if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { - #Check if $KBCachePath exists, if not, create it - if ($AllowUpdateCaching) { - if (-not (Test-Path -Path $KBCachePath)) { - WriteLog "Creating $KBCachePath" - New-Item -Path $KBCachePath -ItemType Directory -Force | Out-Null - } + if ($ISOPath) { + $wimPath = Get-WimFromISO + } else { + $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType + } + #If index not specified by user, try and find based on WindowsSKU + if (-not($index) -and ($WindowsSKU)) { + $index = Get-Index -WindowsImagePath $wimPath -WindowsSKU $WindowsSKU } - } + $vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes + + $systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk + New-MSRPartition -VhdxDisk $vhdxDisk - #Add Windows packages - if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { - try { - WriteLog "Adding KBs to $WindowsPartition" - WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - # If WindowsRelease is 2016, we need to add the SSU first - if ($WindowsRelease -eq 2016) { - WriteLog "WindowsRelease is 2016, adding SSU first" - WriteLog "Adding SSU to $WindowsPartition" - # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null - # Commenting out -preventpending as it causes an issue with the SSU being applied - # Seems to be because of the registry being mounted per dism.log - Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null - WriteLog "SSU added to $WindowsPartition" - WriteLog "Removing $SSUFilePath" - Remove-Item -Path $SSUFilePath -Force | Out-Null - WriteLog 'SSU removed' - WriteLog "Adding CU to $WindowsPartition" - } - # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null - Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null - WriteLog "KBs added to $WindowsPartition" - if ($AllowVHDXCaching) { - $cachedVHDXInfo = [VhdxCacheItem]::new() - $includedUpdates = Get-ChildItem -Path $KBPath -File - - foreach ($includedUpdate in $includedUpdates) { - $cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name)) + $osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $index + $osPartitionDriveLetter = $osPartition[1].DriveLetter + $WindowsPartition = $osPartitionDriveLetter + ':\' + + #$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition + $recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition + + WriteLog 'All necessary partitions created.' + + Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] + + #Add Windows packages + if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { + try { + WriteLog "Adding KBs to $WindowsPartition" + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' + # If WindowsRelease is 2016, we need to add the SSU first + if ($WindowsRelease -eq 2016) { + WriteLog 'WindowsRelease is 2016, adding SSU first' + WriteLog "Adding SSU to $WindowsPartition" + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null + # Commenting out -preventpending as it causes an issue with the SSU being applied + # Seems to be because of the registry being mounted per dism.log + Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null + WriteLog "SSU added to $WindowsPartition" + WriteLog "Removing $SSUFilePath" + Remove-Item -Path $SSUFilePath -Force | Out-Null + WriteLog 'SSU removed' + WriteLog "Adding CU to $WindowsPartition" } - } - WriteLog "Removing $KBPath" - Remove-Item -Path $KBPath -Recurse -Force | Out-Null - WriteLog "Clean Up the WinSxS Folder" - WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null - WriteLog "Clean Up the WinSxS Folder completed" + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null + Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null + WriteLog "KBs added to $WindowsPartition" + if ($AllowVHDXCaching) { + $cachedVHDXInfo = [VhdxCacheItem]::new() + $includedUpdates = Get-ChildItem -Path $KBPath -File + + foreach ($includedUpdate in $includedUpdates) { + $cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name)) + } + } + WriteLog "Removing $KBPath" + Remove-Item -Path $KBPath -Recurse -Force | Out-Null + WriteLog 'Clean Up the WinSxS Folder' + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' + Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null + WriteLog 'Clean Up the WinSxS Folder completed' + } catch { + Write-Host "Adding KB to VHDX failed with error $_" + WriteLog "Adding KB to VHDX failed with error $_" + if ($_.Exception.HResult -eq -2146498525) { + Write-Host 'Missing latest Servicing Stack Update' + Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + Write-Host 'Recommended to use the latest media' + WriteLog 'Missing latest Servicing Stack Update' + WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + WriteLog 'Recommended to use the latest media' + } + throw $_ + } } - catch { - Write-Host "Adding KB to VHDX failed with error $_" - WriteLog "Adding KB to VHDX failed with error $_" - if ($_.Exception.HResult -eq -2146498525){ - Write-Host 'Missing latest Servicing Stack Update' - Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' - Write-Host 'Recommended to use the latest media' - WriteLog 'Missing latest Servicing Stack Update' - WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' - WriteLog 'Recommended to use the latest media' - } - throw $_ - } - } - #Enable Windows Optional Features (e.g. .Net3, etc) - If ($OptionalFeatures) { - $Source = Join-Path (Split-Path $wimpath) "sxs" - Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source - } + #Enable Windows Optional Features (e.g. .Net3, etc) + If ($OptionalFeatures) { + $Source = Join-Path (Split-Path $wimpath) 'sxs' + Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source + } } else { #Use cached vhdx file - WriteLog "Using cached VHDX file to speed up build proces" + WriteLog 'Using cached VHDX file to speed up build proces' WriteLog "VHDX file is: $($cachedVHDXInfo.VhdxFileName)" Robocopy.exe $($VHDXCacheFolder) $($VMPath) $($cachedVHDXInfo.VhdxFileName) /E /COPY:DAT /R:5 /W:5 /J $VHDXPath = Join-Path $($VMPath) $($cachedVHDXInfo.VhdxFileName) $vhdxDisk = Get-VHD -Path $VHDXPath | Mount-VHD -Passthru | Get-Disk - $osPartition = $vhdxDisk | Get-Partition | Where-Object { $_.GptType -eq "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" } + $osPartition = $vhdxDisk | Get-Partition | Where-Object { $_.GptType -eq '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' } $osPartitionDriveLetter = $osPartition.DriveLetter - $WindowsPartition = $osPartitionDriveLetter + ":\" + $WindowsPartition = $osPartitionDriveLetter + ':\' } @@ -4439,9 +4440,11 @@ try { Dismount-DiskImage -ImagePath $ISOPath | Out-null WriteLog 'Done' } - else { - #Remove ESD file + #If $wimPath is an esd file, remove it + If ($wimPath -match '.esd') { + WriteLog "Deleting $wimPath file" Remove-Item -Path $wimPath -Force + WriteLog "$wimPath deleted" } @@ -4459,12 +4462,12 @@ try { } if ($AllowVHDXCaching -and !$cachedVHDXFileFound) { - WriteLog 'New cachabe VHDX created' + WriteLog 'New cached VHDX created' WriteLog 'Defragmenting Windows partition...' - Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority -Verbose + Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority WriteLog 'Performing slab consolidation on Windows partition...' - Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority -Verbose + Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority WriteLog 'Dismounting VHDX' Dismount-ScratchVhdx -VhdxPath $VHDXPath @@ -4737,20 +4740,30 @@ If ($CleanupAppsISO) { Writelog "Removing $AppsISO failed with error $_" throw $_ } -If ($CleanupDrivers){ +} +If ($CleanupDrivers) { try { If (Test-Path -Path $Driversfolder\$Make) { WriteLog "Removing $Driversfolder\$Make" Remove-Item -Path $Driversfolder\$Make -Force -Recurse - WriteLog "Removal complete" + WriteLog 'Removal complete' } - } - catch { + } catch { Writelog "Removing $Driversfolder\$Make failed with error $_" throw $_ } - } +if ($AllowVHDXCaching) { + try { + If (Test-Path -Path $KBPath) { + WriteLog "Removing $KBPath" + Remove-Item -Path $KBPath -Recurse -Force -ErrorAction SilentlyContinue + WriteLog 'Removal complete' + } + } catch { + Writelog "Removing $KBPath failed with error $_" + throw $_ + } } #Clean up dirty.txt file Remove-Item -Path .\dirty.txt -Force | out-null From 802a225c3e1c5910625e08316a70b7126930cddc Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:36:00 -0800 Subject: [PATCH 33/50] Revert "2410.1 Allow vhdx caching" --- FFUDevelopment/BuildFFUVM.ps1 | 323 +++++++++------------------------- 1 file changed, 86 insertions(+), 237 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 1148ae2..056ead9 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -318,7 +318,6 @@ param( [bool]$UpdateLatestMSRT, [bool]$UpdateEdge, [bool]$UpdateOneDrive, - [bool]$AllowVHDXCaching, [bool]$CopyPPKG, [bool]$CopyUnattend, [bool]$CopyAutopilot, @@ -349,23 +348,6 @@ param( ) $version = '2410.1' -#Class definition for vhdx cache -class VhdxCacheUpdateItem { - [string]$Name - VhdxCacheUpdateItem([string]$Name) { - $this.Name = $Name - } -} - -class VhdxCacheItem { - [string]$VhdxFileName = "" - [string]$WindowsSKU = "" - [string]$WindowsRelease = "" - [string]$WindowsVersion = "" - [string]$OptionalFeatures = "" - [VhdxCacheUpdateItem[]]$IncludedUpdates = @() -} - #Check if Hyper-V feature is installed (requires only checks the module) $osInfo = Get-WmiObject -Class Win32_OperatingSystem $isServer = $osInfo.Caption -match 'server' @@ -408,7 +390,6 @@ if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" } if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" } if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" } if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" } -if (-not $VHDXCacheFolder) { $VHDXCacheFolder = "$FFUDevelopmentPath\VHDXCache" } if (-not $installationType) { $installationType = if ($WindowsRelease.ToString().Length -eq 2) { 'Client' } else { 'Server' } } if ($installationType -eq 'Server'){ #Map $WindowsRelease to $WindowsVersion for Windows Server @@ -2947,12 +2928,11 @@ function Optimize-FFUCaptureDrive { ) try { WriteLog 'Mounting VHDX for volume optimization' - $mountedDisk = Mount-VHD -Path $VhdxPath -Passthru | Get-Disk - $osPartition = $mountedDisk | Get-Partition | Where-Object { $_.GptType -eq "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" } + Mount-VHD -Path $VhdxPath WriteLog 'Defragmenting Windows partition...' - Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority + Optimize-Volume -DriveLetter W -Defrag -NormalPriority WriteLog 'Performing slab consolidation on Windows partition...' - Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority + Optimize-Volume -DriveLetter W -SlabConsolidate -NormalPriority WriteLog 'Dismounting VHDX' Dismount-ScratchVhdx -VhdxPath $VhdxPath WriteLog 'Mounting VHDX as read-only for optimization' @@ -3900,17 +3880,6 @@ if (($WindowsArch -eq 'ARM64') -and ($UpdateLatestMSRT -eq $true)) { $UpdateLatestMSRT = $false WriteLog 'Windows Malicious Software Removal Tool is not available for the ARM64 architecture.' } -#If downloading ESD from MCT, hardcode WindowsVersion to 22H2 for Windows 10 and 24H2 for Windows 11 -#MCT media only provides 22H2 and 24H2 media -#This prevents issues with VHDX Caching unecessarily and with searching for CUs -if ($ISOPath -eq '') { - if ($WindowsRelease -eq '10') { - $WindowsVersion = '22H2' - } - if ($WindowsRelease -eq '11') { - $WindowsVersion = '24H2' - } -} ###END PARAMETER VALIDATION @@ -4186,6 +4155,34 @@ if ($InstallApps) { #Create VHDX try { + if ($ISOPath) { + $wimPath = Get-WimFromISO + } + else { + $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType + } + #If index not specified by user, try and find based on WindowsSKU + if (-not($index) -and ($WindowsSKU)) { + $index = Get-Index -WindowsImagePath $wimPath -WindowsSKU $WindowsSKU + } + + $vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes + + $systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk + + New-MSRPartition -VhdxDisk $vhdxDisk + + $osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $index + $osPartitionDriveLetter = $osPartition[1].DriveLetter + $WindowsPartition = $osPartitionDriveLetter + ":\" + + #$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition + $recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition + + WriteLog "All necessary partitions created." + + Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] + #Update latest Cumulative Update if both $UpdateLatestCU is $true and $UpdatePreviewCU is $false #Changed to use MU Catalog instead of using Get-LatestWindowsKB #The Windows release info page is updated later than the MU Catalog @@ -4219,7 +4216,7 @@ try { } #Update Latest Preview Cumlative Update for Client OS only - #will take Precendence over $UpdateLatestCU if both were set to $true + #will take Precendence over $UpdateLastestCU if both were set to $true if ($UpdatePreviewCU -and $installationType -eq 'Client') { Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" @@ -4270,164 +4267,57 @@ try { # $KBFilePath = Save-KB -Name $Name -Path $KBPath # WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath" # } - - #Search for cached VHDX and skip VHDX creation if there's a cached version - if ($AllowVHDXCaching) { - WriteLog 'AllowVHDXCaching is true, checking for cached VHDX file' - if (Test-Path -Path $VHDXCacheFolder) { - WriteLog "Found $VHDXCacheFolder" - $vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending) - WriteLog "Found $($vhdxJsons.Count) cached VHDX files" - $downloadedKBs = @(Get-ChildItem -File -Path $KBPath) - #$jsonDeserializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new() - - foreach ($vhdxJson in $vhdxJsons) { - try { - WriteLog "Processing $($vhdxJson.FullName)" - #$vhdxCacheItem = $jsonDeserializer.Deserialize((Get-Content -Path $vhdxJson.FullName -Raw), [VhdxCacheItem]) - $vhdxCacheItem = Get-Content -Path $vhdxJson.FullName -Raw | ConvertFrom-Json - - if ((($vhdxCacheItem.WindowsSKU -ne $WindowsSKU) -or - ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsSKU) -xor [string]::IsNullOrEmpty($WindowsSKU)))) { - WriteLog 'WindowsSKU mismatch, continuing' - continue - } - - if ((($vhdxCacheItem.WindowsRelease -ne $WindowsRelease) -or - ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsRelease) -xor [string]::IsNullOrEmpty($WindowsRelease)))) { - WriteLog 'WindowsRelease mismatch, continuing' - continue - } - - if ((($vhdxCacheItem.WindowsVersion -ne $WindowsVersion) -or - ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsVersion) -xor [string]::IsNullOrEmpty($WindowsVersion)))) { - Writelog 'WindowsVersion mismatch, continuing' - continue - } - - if ((($vhdxCacheItem.OptionalFeatures -ne $OptionalFeatures) -or - ([string]::IsNullOrEmpty($vhdxCacheItem.OptionalFeatures) -xor [string]::IsNullOrEmpty($OptionalFeatures)))) { - WriteLog 'OptionalFeatures mismatch, continuing' - continue - } - - if ((Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name).Length -gt 0) { - (Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name) - $downloadedKBs.Name - $vhdxCacheItem.IncludedUpdates.Name - WriteLog 'IncludedUpdates mismatch, continuing' - continue - } - - WriteLog "Found cached VHDX file $vhdxCacheFolder\$($vhdxCacheItem.VhdxFileName) with matching parameters and included updates" - $cachedVHDXFileFound = $true - $cachedVHDXInfo = $vhdxCacheItem - break - } catch { - WriteLog "Reading $vhdxJson Failed with error $_" - } + + + #Add Windows packages + if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { + try { + WriteLog "Adding KBs to $WindowsPartition" + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' + # If WindowsRelease is 2016, we need to add the SSU first + if ($WindowsRelease -eq 2016) { + WriteLog "WindowsRelease is 2016, adding SSU first" + WriteLog "Adding SSU to $WindowsPartition" + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null + # Commenting out -preventpending as it causes an issue with the SSU being applied + # Seems to be because of the registry being mounted per dism.log + Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null + WriteLog "SSU added to $WindowsPartition" + WriteLog "Removing $SSUFilePath" + Remove-Item -Path $SSUFilePath -Force | Out-Null + WriteLog 'SSU removed' + WriteLog "Adding CU to $WindowsPartition" } + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null + Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null + WriteLog "KBs added to $WindowsPartition" + WriteLog "Removing $KBPath" + Remove-Item -Path $KBPath -Recurse -Force | Out-Null + WriteLog "Clean Up the WinSxS Folder" + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' + Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null + WriteLog "Clean Up the WinSxS Folder completed" } + catch { + Write-Host "Adding KB to VHDX failed with error $_" + WriteLog "Adding KB to VHDX failed with error $_" + if ($_.Exception.HResult -eq -2146498525){ + Write-Host 'Missing latest Servicing Stack Update' + Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + Write-Host 'Recommended to use the latest media' + WriteLog 'Missing latest Servicing Stack Update' + WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + WriteLog 'Recommended to use the latest media' + } + throw $_ + } } - - if (-Not $cachedVHDXFileFound) { - if ($ISOPath) { - $wimPath = Get-WimFromISO - } else { - $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType - } - #If index not specified by user, try and find based on WindowsSKU - if (-not($index) -and ($WindowsSKU)) { - $index = Get-Index -WindowsImagePath $wimPath -WindowsSKU $WindowsSKU - } - $vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes - - $systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk - - New-MSRPartition -VhdxDisk $vhdxDisk - - $osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $index - $osPartitionDriveLetter = $osPartition[1].DriveLetter - $WindowsPartition = $osPartitionDriveLetter + ':\' - - #$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition - $recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition - - WriteLog 'All necessary partitions created.' - - Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] - - #Add Windows packages - if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { - try { - WriteLog "Adding KBs to $WindowsPartition" - WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - # If WindowsRelease is 2016, we need to add the SSU first - if ($WindowsRelease -eq 2016) { - WriteLog 'WindowsRelease is 2016, adding SSU first' - WriteLog "Adding SSU to $WindowsPartition" - # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null - # Commenting out -preventpending as it causes an issue with the SSU being applied - # Seems to be because of the registry being mounted per dism.log - Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null - WriteLog "SSU added to $WindowsPartition" - WriteLog "Removing $SSUFilePath" - Remove-Item -Path $SSUFilePath -Force | Out-Null - WriteLog 'SSU removed' - WriteLog "Adding CU to $WindowsPartition" - } - # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null - Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null - WriteLog "KBs added to $WindowsPartition" - if ($AllowVHDXCaching) { - $cachedVHDXInfo = [VhdxCacheItem]::new() - $includedUpdates = Get-ChildItem -Path $KBPath -File - - foreach ($includedUpdate in $includedUpdates) { - $cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name)) - } - } - WriteLog "Removing $KBPath" - Remove-Item -Path $KBPath -Recurse -Force | Out-Null - WriteLog 'Clean Up the WinSxS Folder' - WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null - WriteLog 'Clean Up the WinSxS Folder completed' - } catch { - Write-Host "Adding KB to VHDX failed with error $_" - WriteLog "Adding KB to VHDX failed with error $_" - if ($_.Exception.HResult -eq -2146498525) { - Write-Host 'Missing latest Servicing Stack Update' - Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' - Write-Host 'Recommended to use the latest media' - WriteLog 'Missing latest Servicing Stack Update' - WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' - WriteLog 'Recommended to use the latest media' - } - throw $_ - } - } - - #Enable Windows Optional Features (e.g. .Net3, etc) - If ($OptionalFeatures) { - $Source = Join-Path (Split-Path $wimpath) 'sxs' - Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source - } - - } else { - #Use cached vhdx file - WriteLog 'Using cached VHDX file to speed up build proces' - WriteLog "VHDX file is: $($cachedVHDXInfo.VhdxFileName)" - - Robocopy.exe $($VHDXCacheFolder) $($VMPath) $($cachedVHDXInfo.VhdxFileName) /E /COPY:DAT /R:5 /W:5 /J - $VHDXPath = Join-Path $($VMPath) $($cachedVHDXInfo.VhdxFileName) - - $vhdxDisk = Get-VHD -Path $VHDXPath | Mount-VHD -Passthru | Get-Disk - $osPartition = $vhdxDisk | Get-Partition | Where-Object { $_.GptType -eq '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' } - $osPartitionDriveLetter = $osPartition.DriveLetter - $WindowsPartition = $osPartitionDriveLetter + ':\' + #Enable Windows Optional Features (e.g. .Net3, etc) + If ($OptionalFeatures) { + $Source = Join-Path (Split-Path $wimpath) "sxs" + Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source } #Set Product key @@ -4440,18 +4330,16 @@ try { Dismount-DiskImage -ImagePath $ISOPath | Out-null WriteLog 'Done' } - #If $wimPath is an esd file, remove it - If ($wimPath -match '.esd') { - WriteLog "Deleting $wimPath file" + else { + #Remove ESD file Remove-Item -Path $wimPath -Force - WriteLog "$wimPath deleted" } If ($InstallApps) { #Copy Unattend file so VM Boots into Audit Mode WriteLog 'Copying unattend file to boot to audit mode' - New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory -Force | Out-Null + New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory | Out-Null if($WindowsArch -eq 'x64'){ Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_x64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null } @@ -4459,35 +4347,6 @@ try { Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_arm64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null } WriteLog 'Copy completed' - } - - if ($AllowVHDXCaching -and !$cachedVHDXFileFound) { - WriteLog 'New cached VHDX created' - - WriteLog 'Defragmenting Windows partition...' - Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority - WriteLog 'Performing slab consolidation on Windows partition...' - Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority - WriteLog 'Dismounting VHDX' - Dismount-ScratchVhdx -VhdxPath $VHDXPath - - WriteLog 'Copying to cache dir' - - #Assuming there are now name collisons - Robocopy.exe $($VMPath) $($VHDXCacheFolder) $("$VMName.vhdx") /E /COPY:DAT /R:5 /W:5 /J - - #Only create new instance if not created during patching - if ($null -eq $cachedVHDXInfo) { - $cachedVHDXInfo = [VhdxCacheItem]::new() - } - $cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx") - $cachedVHDXInfo.WindowsSKU = $WindowsSKU - $cachedVHDXInfo.WindowsRelease = $WindowsRelease - $cachedVHDXInfo.WindowsVersion = $WindowsVersion - $cachedVHDXInfo.OptionalFeatures = $OptionalFeatures - - $cachedVHDXInfo | ConvertTo-Json | Out-File -FilePath ("{0}\{1}_config.json" -f $($VHDXCacheFolder), $VMName) - } else { Dismount-ScratchVhdx -VhdxPath $VHDXPath } } @@ -4740,30 +4599,20 @@ If ($CleanupAppsISO) { Writelog "Removing $AppsISO failed with error $_" throw $_ } -} -If ($CleanupDrivers) { +If ($CleanupDrivers){ try { If (Test-Path -Path $Driversfolder\$Make) { WriteLog "Removing $Driversfolder\$Make" Remove-Item -Path $Driversfolder\$Make -Force -Recurse - WriteLog 'Removal complete' + WriteLog "Removal complete" } - } catch { + } + catch { Writelog "Removing $Driversfolder\$Make failed with error $_" throw $_ } + } -if ($AllowVHDXCaching) { - try { - If (Test-Path -Path $KBPath) { - WriteLog "Removing $KBPath" - Remove-Item -Path $KBPath -Recurse -Force -ErrorAction SilentlyContinue - WriteLog 'Removal complete' - } - } catch { - Writelog "Removing $KBPath failed with error $_" - throw $_ - } } #Clean up dirty.txt file Remove-Item -Path .\dirty.txt -Force | out-null From 39a9bc9022e992c3583e479b3f5019b407b71471 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Thu, 28 Nov 2024 17:40:22 -0800 Subject: [PATCH 34/50] Revert "Revert "2410.1 Allow vhdx caching"" --- FFUDevelopment/BuildFFUVM.ps1 | 323 +++++++++++++++++++++++++--------- 1 file changed, 237 insertions(+), 86 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 056ead9..1148ae2 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -318,6 +318,7 @@ param( [bool]$UpdateLatestMSRT, [bool]$UpdateEdge, [bool]$UpdateOneDrive, + [bool]$AllowVHDXCaching, [bool]$CopyPPKG, [bool]$CopyUnattend, [bool]$CopyAutopilot, @@ -348,6 +349,23 @@ param( ) $version = '2410.1' +#Class definition for vhdx cache +class VhdxCacheUpdateItem { + [string]$Name + VhdxCacheUpdateItem([string]$Name) { + $this.Name = $Name + } +} + +class VhdxCacheItem { + [string]$VhdxFileName = "" + [string]$WindowsSKU = "" + [string]$WindowsRelease = "" + [string]$WindowsVersion = "" + [string]$OptionalFeatures = "" + [VhdxCacheUpdateItem[]]$IncludedUpdates = @() +} + #Check if Hyper-V feature is installed (requires only checks the module) $osInfo = Get-WmiObject -Class Win32_OperatingSystem $isServer = $osInfo.Caption -match 'server' @@ -390,6 +408,7 @@ if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" } if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" } if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" } if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" } +if (-not $VHDXCacheFolder) { $VHDXCacheFolder = "$FFUDevelopmentPath\VHDXCache" } if (-not $installationType) { $installationType = if ($WindowsRelease.ToString().Length -eq 2) { 'Client' } else { 'Server' } } if ($installationType -eq 'Server'){ #Map $WindowsRelease to $WindowsVersion for Windows Server @@ -2928,11 +2947,12 @@ function Optimize-FFUCaptureDrive { ) try { WriteLog 'Mounting VHDX for volume optimization' - Mount-VHD -Path $VhdxPath + $mountedDisk = Mount-VHD -Path $VhdxPath -Passthru | Get-Disk + $osPartition = $mountedDisk | Get-Partition | Where-Object { $_.GptType -eq "{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}" } WriteLog 'Defragmenting Windows partition...' - Optimize-Volume -DriveLetter W -Defrag -NormalPriority + Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority WriteLog 'Performing slab consolidation on Windows partition...' - Optimize-Volume -DriveLetter W -SlabConsolidate -NormalPriority + Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority WriteLog 'Dismounting VHDX' Dismount-ScratchVhdx -VhdxPath $VhdxPath WriteLog 'Mounting VHDX as read-only for optimization' @@ -3880,6 +3900,17 @@ if (($WindowsArch -eq 'ARM64') -and ($UpdateLatestMSRT -eq $true)) { $UpdateLatestMSRT = $false WriteLog 'Windows Malicious Software Removal Tool is not available for the ARM64 architecture.' } +#If downloading ESD from MCT, hardcode WindowsVersion to 22H2 for Windows 10 and 24H2 for Windows 11 +#MCT media only provides 22H2 and 24H2 media +#This prevents issues with VHDX Caching unecessarily and with searching for CUs +if ($ISOPath -eq '') { + if ($WindowsRelease -eq '10') { + $WindowsVersion = '22H2' + } + if ($WindowsRelease -eq '11') { + $WindowsVersion = '24H2' + } +} ###END PARAMETER VALIDATION @@ -4155,34 +4186,6 @@ if ($InstallApps) { #Create VHDX try { - if ($ISOPath) { - $wimPath = Get-WimFromISO - } - else { - $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType - } - #If index not specified by user, try and find based on WindowsSKU - if (-not($index) -and ($WindowsSKU)) { - $index = Get-Index -WindowsImagePath $wimPath -WindowsSKU $WindowsSKU - } - - $vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes - - $systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk - - New-MSRPartition -VhdxDisk $vhdxDisk - - $osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $index - $osPartitionDriveLetter = $osPartition[1].DriveLetter - $WindowsPartition = $osPartitionDriveLetter + ":\" - - #$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition - $recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition - - WriteLog "All necessary partitions created." - - Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] - #Update latest Cumulative Update if both $UpdateLatestCU is $true and $UpdatePreviewCU is $false #Changed to use MU Catalog instead of using Get-LatestWindowsKB #The Windows release info page is updated later than the MU Catalog @@ -4216,7 +4219,7 @@ try { } #Update Latest Preview Cumlative Update for Client OS only - #will take Precendence over $UpdateLastestCU if both were set to $true + #will take Precendence over $UpdateLatestCU if both were set to $true if ($UpdatePreviewCU -and $installationType -eq 'Client') { Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU" $Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" @@ -4267,57 +4270,164 @@ try { # $KBFilePath = Save-KB -Name $Name -Path $KBPath # WriteLog "Latest Security Platform Update saved to $KBPath\$KBFilePath" # } - - - #Add Windows packages - if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { - try { - WriteLog "Adding KBs to $WindowsPartition" - WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - # If WindowsRelease is 2016, we need to add the SSU first - if ($WindowsRelease -eq 2016) { - WriteLog "WindowsRelease is 2016, adding SSU first" - WriteLog "Adding SSU to $WindowsPartition" - # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null - # Commenting out -preventpending as it causes an issue with the SSU being applied - # Seems to be because of the registry being mounted per dism.log - Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null - WriteLog "SSU added to $WindowsPartition" - WriteLog "Removing $SSUFilePath" - Remove-Item -Path $SSUFilePath -Force | Out-Null - WriteLog 'SSU removed' - WriteLog "Adding CU to $WindowsPartition" + + #Search for cached VHDX and skip VHDX creation if there's a cached version + if ($AllowVHDXCaching) { + WriteLog 'AllowVHDXCaching is true, checking for cached VHDX file' + if (Test-Path -Path $VHDXCacheFolder) { + WriteLog "Found $VHDXCacheFolder" + $vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending) + WriteLog "Found $($vhdxJsons.Count) cached VHDX files" + $downloadedKBs = @(Get-ChildItem -File -Path $KBPath) + #$jsonDeserializer = [System.Web.Script.Serialization.JavaScriptSerializer]::new() + + foreach ($vhdxJson in $vhdxJsons) { + try { + WriteLog "Processing $($vhdxJson.FullName)" + #$vhdxCacheItem = $jsonDeserializer.Deserialize((Get-Content -Path $vhdxJson.FullName -Raw), [VhdxCacheItem]) + $vhdxCacheItem = Get-Content -Path $vhdxJson.FullName -Raw | ConvertFrom-Json + + if ((($vhdxCacheItem.WindowsSKU -ne $WindowsSKU) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsSKU) -xor [string]::IsNullOrEmpty($WindowsSKU)))) { + WriteLog 'WindowsSKU mismatch, continuing' + continue + } + + if ((($vhdxCacheItem.WindowsRelease -ne $WindowsRelease) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsRelease) -xor [string]::IsNullOrEmpty($WindowsRelease)))) { + WriteLog 'WindowsRelease mismatch, continuing' + continue + } + + if ((($vhdxCacheItem.WindowsVersion -ne $WindowsVersion) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.WindowsVersion) -xor [string]::IsNullOrEmpty($WindowsVersion)))) { + Writelog 'WindowsVersion mismatch, continuing' + continue + } + + if ((($vhdxCacheItem.OptionalFeatures -ne $OptionalFeatures) -or + ([string]::IsNullOrEmpty($vhdxCacheItem.OptionalFeatures) -xor [string]::IsNullOrEmpty($OptionalFeatures)))) { + WriteLog 'OptionalFeatures mismatch, continuing' + continue + } + + if ((Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name).Length -gt 0) { + (Compare-Object -ReferenceObject $downloadedKBs -DifferenceObject $vhdxCacheItem.IncludedUpdates -Property Name) + $downloadedKBs.Name + $vhdxCacheItem.IncludedUpdates.Name + WriteLog 'IncludedUpdates mismatch, continuing' + continue + } + + WriteLog "Found cached VHDX file $vhdxCacheFolder\$($vhdxCacheItem.VhdxFileName) with matching parameters and included updates" + $cachedVHDXFileFound = $true + $cachedVHDXInfo = $vhdxCacheItem + break + } catch { + WriteLog "Reading $vhdxJson Failed with error $_" + } } - # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null - Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null - WriteLog "KBs added to $WindowsPartition" - WriteLog "Removing $KBPath" - Remove-Item -Path $KBPath -Recurse -Force | Out-Null - WriteLog "Clean Up the WinSxS Folder" - WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' - Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null - WriteLog "Clean Up the WinSxS Folder completed" } - catch { - Write-Host "Adding KB to VHDX failed with error $_" - WriteLog "Adding KB to VHDX failed with error $_" - if ($_.Exception.HResult -eq -2146498525){ - Write-Host 'Missing latest Servicing Stack Update' - Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' - Write-Host 'Recommended to use the latest media' - WriteLog 'Missing latest Servicing Stack Update' - WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' - WriteLog 'Recommended to use the latest media' - } - throw $_ - } } + + if (-Not $cachedVHDXFileFound) { + if ($ISOPath) { + $wimPath = Get-WimFromISO + } else { + $wimPath = Get-WindowsESD -WindowsRelease $WindowsRelease -WindowsArch $WindowsArch -WindowsLang $WindowsLang -MediaType $mediaType + } + #If index not specified by user, try and find based on WindowsSKU + if (-not($index) -and ($WindowsSKU)) { + $index = Get-Index -WindowsImagePath $wimPath -WindowsSKU $WindowsSKU + } + $vhdxDisk = New-ScratchVhdx -VhdxPath $VHDXPath -SizeBytes $disksize -LogicalSectorSizeBytes $LogicalSectorSizeBytes + + $systemPartitionDriveLetter = New-SystemPartition -VhdxDisk $vhdxDisk + + New-MSRPartition -VhdxDisk $vhdxDisk + + $osPartition = New-OSPartition -VhdxDisk $vhdxDisk -OSPartitionSize $OSPartitionSize -WimPath $WimPath -WimIndex $index + $osPartitionDriveLetter = $osPartition[1].DriveLetter + $WindowsPartition = $osPartitionDriveLetter + ':\' + + #$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition + $recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DataPartition $dataPartition + + WriteLog 'All necessary partitions created.' + + Add-BootFiles -OsPartitionDriveLetter $osPartitionDriveLetter -SystemPartitionDriveLetter $systemPartitionDriveLetter[1] + + #Add Windows packages + if ($UpdateLatestCU -or $UpdateLatestNet -or $UpdatePreviewCU ) { + try { + WriteLog "Adding KBs to $WindowsPartition" + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' + # If WindowsRelease is 2016, we need to add the SSU first + if ($WindowsRelease -eq 2016) { + WriteLog 'WindowsRelease is 2016, adding SSU first' + WriteLog "Adding SSU to $WindowsPartition" + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null + # Commenting out -preventpending as it causes an issue with the SSU being applied + # Seems to be because of the registry being mounted per dism.log + Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath | Out-Null + WriteLog "SSU added to $WindowsPartition" + WriteLog "Removing $SSUFilePath" + Remove-Item -Path $SSUFilePath -Force | Out-Null + WriteLog 'SSU removed' + WriteLog "Adding CU to $WindowsPartition" + } + # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null + Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null + WriteLog "KBs added to $WindowsPartition" + if ($AllowVHDXCaching) { + $cachedVHDXInfo = [VhdxCacheItem]::new() + $includedUpdates = Get-ChildItem -Path $KBPath -File + + foreach ($includedUpdate in $includedUpdates) { + $cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name)) + } + } + WriteLog "Removing $KBPath" + Remove-Item -Path $KBPath -Recurse -Force | Out-Null + WriteLog 'Clean Up the WinSxS Folder' + WriteLog 'This can take 10+ minutes depending on how old the media is and the size of the KB. Please be patient' + Dism /Image:$WindowsPartition /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null + WriteLog 'Clean Up the WinSxS Folder completed' + } catch { + Write-Host "Adding KB to VHDX failed with error $_" + WriteLog "Adding KB to VHDX failed with error $_" + if ($_.Exception.HResult -eq -2146498525) { + Write-Host 'Missing latest Servicing Stack Update' + Write-Host 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + Write-Host 'Recommended to use the latest media' + WriteLog 'Missing latest Servicing Stack Update' + WriteLog 'Media likely older than 2023-09 for Windows Server 2022 (KB5030216), or 2021-08 for Windows Server 2019 (KB5005112)' + WriteLog 'Recommended to use the latest media' + } + throw $_ + } + } + + #Enable Windows Optional Features (e.g. .Net3, etc) + If ($OptionalFeatures) { + $Source = Join-Path (Split-Path $wimpath) 'sxs' + Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source + } + + } else { + #Use cached vhdx file + WriteLog 'Using cached VHDX file to speed up build proces' + WriteLog "VHDX file is: $($cachedVHDXInfo.VhdxFileName)" + + Robocopy.exe $($VHDXCacheFolder) $($VMPath) $($cachedVHDXInfo.VhdxFileName) /E /COPY:DAT /R:5 /W:5 /J + $VHDXPath = Join-Path $($VMPath) $($cachedVHDXInfo.VhdxFileName) + + $vhdxDisk = Get-VHD -Path $VHDXPath | Mount-VHD -Passthru | Get-Disk + $osPartition = $vhdxDisk | Get-Partition | Where-Object { $_.GptType -eq '{ebd0a0a2-b9e5-4433-87c0-68b6b72699c7}' } + $osPartitionDriveLetter = $osPartition.DriveLetter + $WindowsPartition = $osPartitionDriveLetter + ':\' - #Enable Windows Optional Features (e.g. .Net3, etc) - If ($OptionalFeatures) { - $Source = Join-Path (Split-Path $wimpath) "sxs" - Enable-WindowsFeaturesByName -FeatureNames $OptionalFeatures -Source $Source } #Set Product key @@ -4330,16 +4440,18 @@ try { Dismount-DiskImage -ImagePath $ISOPath | Out-null WriteLog 'Done' } - else { - #Remove ESD file + #If $wimPath is an esd file, remove it + If ($wimPath -match '.esd') { + WriteLog "Deleting $wimPath file" Remove-Item -Path $wimPath -Force + WriteLog "$wimPath deleted" } If ($InstallApps) { #Copy Unattend file so VM Boots into Audit Mode WriteLog 'Copying unattend file to boot to audit mode' - New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory | Out-Null + New-Item -Path "$($osPartitionDriveLetter):\Windows\Panther\unattend" -ItemType Directory -Force | Out-Null if($WindowsArch -eq 'x64'){ Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_x64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null } @@ -4347,6 +4459,35 @@ try { Copy-Item -Path "$FFUDevelopmentPath\BuildFFUUnattend\unattend_arm64.xml" -Destination "$($osPartitionDriveLetter):\Windows\Panther\Unattend\Unattend.xml" -Force | Out-Null } WriteLog 'Copy completed' + } + + if ($AllowVHDXCaching -and !$cachedVHDXFileFound) { + WriteLog 'New cached VHDX created' + + WriteLog 'Defragmenting Windows partition...' + Optimize-Volume -DriveLetter $osPartition.DriveLetter -Defrag -NormalPriority + WriteLog 'Performing slab consolidation on Windows partition...' + Optimize-Volume -DriveLetter $osPartition.DriveLetter -SlabConsolidate -NormalPriority + WriteLog 'Dismounting VHDX' + Dismount-ScratchVhdx -VhdxPath $VHDXPath + + WriteLog 'Copying to cache dir' + + #Assuming there are now name collisons + Robocopy.exe $($VMPath) $($VHDXCacheFolder) $("$VMName.vhdx") /E /COPY:DAT /R:5 /W:5 /J + + #Only create new instance if not created during patching + if ($null -eq $cachedVHDXInfo) { + $cachedVHDXInfo = [VhdxCacheItem]::new() + } + $cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx") + $cachedVHDXInfo.WindowsSKU = $WindowsSKU + $cachedVHDXInfo.WindowsRelease = $WindowsRelease + $cachedVHDXInfo.WindowsVersion = $WindowsVersion + $cachedVHDXInfo.OptionalFeatures = $OptionalFeatures + + $cachedVHDXInfo | ConvertTo-Json | Out-File -FilePath ("{0}\{1}_config.json" -f $($VHDXCacheFolder), $VMName) + } else { Dismount-ScratchVhdx -VhdxPath $VHDXPath } } @@ -4599,20 +4740,30 @@ If ($CleanupAppsISO) { Writelog "Removing $AppsISO failed with error $_" throw $_ } -If ($CleanupDrivers){ +} +If ($CleanupDrivers) { try { If (Test-Path -Path $Driversfolder\$Make) { WriteLog "Removing $Driversfolder\$Make" Remove-Item -Path $Driversfolder\$Make -Force -Recurse - WriteLog "Removal complete" + WriteLog 'Removal complete' } - } - catch { + } catch { Writelog "Removing $Driversfolder\$Make failed with error $_" throw $_ } - } +if ($AllowVHDXCaching) { + try { + If (Test-Path -Path $KBPath) { + WriteLog "Removing $KBPath" + Remove-Item -Path $KBPath -Recurse -Force -ErrorAction SilentlyContinue + WriteLog 'Removal complete' + } + } catch { + Writelog "Removing $KBPath failed with error $_" + throw $_ + } } #Clean up dirty.txt file Remove-Item -Path .\dirty.txt -Force | out-null From ed6a5fc7f14404aeeaf02590fca91c3efaeed5e3 Mon Sep 17 00:00:00 2001 From: HedgeComp <94635857+HedgeComp@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:59:01 -0400 Subject: [PATCH 35/50] Create SampleUnattend_64.xml Sample for Unattend File. --- FFUDevelopment/unattend/SampleUnattend_64.xml | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 FFUDevelopment/unattend/SampleUnattend_64.xml diff --git a/FFUDevelopment/unattend/SampleUnattend_64.xml b/FFUDevelopment/unattend/SampleUnattend_64.xml new file mode 100644 index 0000000..0a85e64 --- /dev/null +++ b/FFUDevelopment/unattend/SampleUnattend_64.xml @@ -0,0 +1,35 @@ + + + + + + MYCOMPUTER + Eastern Standard Time + + + + + + 1 + cmd.exe /c date 09-07-2024 + Set system date to a specific date + + + + + + + 0409:00000409 + en-US + en-US + en-US + + + + 3 + true + false + + + + From 1c103b2db738d24a6a7f22a743e5d7f8810564a3 Mon Sep 17 00:00:00 2001 From: HedgeComp <94635857+HedgeComp@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:59:18 -0400 Subject: [PATCH 36/50] Rename SampleUnattend_64.xml to SampleUnattend_x64.xml --- .../unattend/{SampleUnattend_64.xml => SampleUnattend_x64.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename FFUDevelopment/unattend/{SampleUnattend_64.xml => SampleUnattend_x64.xml} (100%) diff --git a/FFUDevelopment/unattend/SampleUnattend_64.xml b/FFUDevelopment/unattend/SampleUnattend_x64.xml similarity index 100% rename from FFUDevelopment/unattend/SampleUnattend_64.xml rename to FFUDevelopment/unattend/SampleUnattend_x64.xml From 9287464eb8bb3e06d370a47d21ea040950d64bcc Mon Sep 17 00:00:00 2001 From: HedgeComp <94635857+HedgeComp@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:59:44 -0400 Subject: [PATCH 37/50] Update unattend_x64.xml --- FFUDevelopment/unattend/unattend_x64.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/unattend/unattend_x64.xml b/FFUDevelopment/unattend/unattend_x64.xml index 9ce5316..4023587 100644 --- a/FFUDevelopment/unattend/unattend_x64.xml +++ b/FFUDevelopment/unattend/unattend_x64.xml @@ -1,8 +1,10 @@ - + + MyComputer + - \ No newline at end of file + From c26034a89cbed30b20e4ec46cef459f30849baa0 Mon Sep 17 00:00:00 2001 From: HedgeComp <94635857+HedgeComp@users.noreply.github.com> Date: Tue, 10 Sep 2024 15:00:07 -0400 Subject: [PATCH 38/50] Update unattend_arm64.xml --- FFUDevelopment/unattend/unattend_arm64.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/unattend/unattend_arm64.xml b/FFUDevelopment/unattend/unattend_arm64.xml index 98b1e7f..ae61894 100644 --- a/FFUDevelopment/unattend/unattend_arm64.xml +++ b/FFUDevelopment/unattend/unattend_arm64.xml @@ -1,8 +1,10 @@ - + + MyComputer + - \ No newline at end of file + From add11b003774009336ad6c573bba93c2d412173e Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Tue, 1 Oct 2024 04:07:11 +0200 Subject: [PATCH 39/50] Update BuildFFUVM.ps1 - Add $CustomFFUNameTemplate and the required code into BuildFFUVM.ps1 --- FFUDevelopment/BuildFFUVM.ps1 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 1148ae2..420d0ab 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -172,6 +172,9 @@ Model of the device to download drivers. This is required if Make is set. .PARAMETER AppsScriptVariables When passed a hashtable, the script will alter the $FFUDevelopmentPath\Apps\InstallAppsandSysprep.cmd file to set variables with the hashtable keys as variable names and the hashtable values their content. +.PARAMETER CustomFFUNameTemplate +Sets a custom FFU output name with placeholders. Allowed placeholders are: {Name}, {DisplayVersion}, {SKU}, {BuildDate}, {yyyy}, {MM}, {dd}, {H}, {hh}, {mm}, {tt} + .EXAMPLE Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates. .\BuildFFUVM.ps1 -WindowsSKU 'Pro' -Installapps $true -InstallOffice $true -InstallDrivers $true -VMSwitchName 'Name of your VM Switch in Hyper-V' -VMHostIPAddress 'Your IP Address' -CreateCaptureMedia $true -CreateDeploymentMedia $true -BuildUSBDrive $true -UpdateLatestCU $true -UpdateLatestNet $true -UpdateLatestDefender $true -UpdateEdge $true -UpdateOneDrive $true -verbose @@ -237,8 +240,9 @@ param( [string]$VMLocation, [string]$FFUPrefix = '_FFU', [string]$FFUCaptureLocation, - [String]$ShareName = "FFUCaptureShare", + [string]$ShareName = "FFUCaptureShare", [string]$Username = "ffu_user", + [string]$CustomFFUNameTemplate, [Parameter(Mandatory = $false)] [string]$VMHostIPAddress, [bool]$CreateCaptureMedia = $true, @@ -2807,6 +2811,11 @@ Function Set-CaptureFFU { $ScriptContent = Get-Content -Path $CaptureFFUScriptPath $UpdatedContent = $ScriptContent -replace '(net use).*', ("$SharePath") WriteLog 'Updating share command in CaptureFFU.ps1 script with new share information' + $UpdatedContent = $UpdatedContent -replace '^\$CustomFFUNameTemplate \= .*#Custom naming', "#Custom naming placeholder" + if (![string]::IsNullOrEmpty($CustomFFUNameTemplate)) { + $UpdatedContent = $UpdatedContent -replace '#Custom naming placeholder', ("`$CustomFFUNameTemplate = '$CustomFFUNameTemplate' #Custom naming") + WriteLog 'Updating share command in CaptureFFU.ps1 script with new ffu name template information' + } Set-Content -Path $CaptureFFUScriptPath -Value $UpdatedContent WriteLog 'Update complete' } From a59210c559032457ae33aac857b1d6488f2f92b1 Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Tue, 1 Oct 2024 04:09:34 +0200 Subject: [PATCH 40/50] Update CaptureFFU.ps1 - Add $CustomFFUNameTemplate support in Update CaptureFFU.ps1 --- .../WinPECaptureFFUFiles/CaptureFFU.ps1 | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index 1a8fdd7..17ff85f 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -1,5 +1,6 @@ #Modify the net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727 net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727 +#Custom naming placeholder $AssignDriveLetter = 'x:\AssignDriveLetter.txt' Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null @@ -59,16 +60,31 @@ else { #If Office is installed, modify the file name of the FFU #$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null $Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue -if($Office){ +if ($Office) { $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu" - $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" - - -} -else{ +} else { $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Apps`_$BuildDate.ffu" - $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" - +} +$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" + +if (![string]::IsNullOrEmpty($CustomFFUNameTemplate)) { + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{Name}", $Name + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{DisplayVersion}", $DisplayVersion + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{SKU}", $SKU + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{BuildDate}", $BuildDate + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{yyyy}", (Get-Date -UFormat "%Y") + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{MM}", (Get-Date -UFormat "%m") + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{dd}", (Get-Date -UFormat "%d") + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{HH}", (Get-Date -UFormat "%H") + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{hh}", (Get-Date -UFormat "%I") + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{mm}", (Get-Date -UFormat "%M") + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{tt}", (Get-Date -UFormat "%p") + + if (!$CustomFFUNameTemplate.EndsWith(".ffu")) { + $CustomFFUNameTemplate += ".ffu" + } + + $dismArgs = "/capture-ffu /imagefile=W:\$CustomFFUNameTemplate /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" } #Unload Registry From db0fbfaaf48360b74721ee8ef8c6a637136f1e90 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:08:30 -0800 Subject: [PATCH 41/50] Refactor CaptureFFU.ps1 to use $WindowsVersion and $WindowsRelease for improved clarity and consistency in FFU naming --- .../WinPECaptureFFUFiles/CaptureFFU.ps1 | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index 17ff85f..986a417 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -13,7 +13,7 @@ reg load "HKLM\FFU" $Software $SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID' [int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild' if ($CurrentBuild -notin 14393, 17763) { - $DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' + $WindowsVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion' } $InstallationType = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'InstallationType' $BuildDate = Get-Date -uformat %b%Y @@ -38,60 +38,58 @@ $SKU = switch ($SKU) { if ($InstallationType -eq "Client") { if ($CurrentBuild -ge 22000) { - $Name = 'Win11' + $WindowsRelease = 'Win11' } else { - $Name = 'Win10' + $WindowsRelease = 'Win10' } } else { - $Name = switch ($CurrentBuild) { + $WindowsRelease = switch ($CurrentBuild) { 26100 { '2025' } 20348 { '2022' } 17763 { '2019' } 14393 { '2016' } - Default { $DisplayVersion } + Default { $WindowsVersion } } if ($InstallationType -eq "Server Core") { $SKU += "_Core" } } -#If Office is installed, modify the file name of the FFU -#$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null -$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue -if ($Office) { - $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Office`_$BuildDate.ffu" -} else { - $ffuFilePath = "W:\$Name`_$DisplayVersion`_$SKU`_Apps`_$BuildDate.ffu" -} -$dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" - -if (![string]::IsNullOrEmpty($CustomFFUNameTemplate)) { - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{Name}", $Name - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{DisplayVersion}", $DisplayVersion - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{SKU}", $SKU - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{BuildDate}", $BuildDate - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{yyyy}", (Get-Date -UFormat "%Y") - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{MM}", (Get-Date -UFormat "%m") - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{dd}", (Get-Date -UFormat "%d") - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{HH}", (Get-Date -UFormat "%H") - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{hh}", (Get-Date -UFormat "%I") - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{mm}", (Get-Date -UFormat "%M") - $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace "{tt}", (Get-Date -UFormat "%p") - - if (!$CustomFFUNameTemplate.EndsWith(".ffu")) { - $CustomFFUNameTemplate += ".ffu" +if ($CustomFFUNameTemplate) { + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsRelease}', $WindowsRelease + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsVersion}', $WindowsVersion + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{SKU}', $SKU + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{BuildDate}', $BuildDate + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{yyyy}', (Get-Date -UFormat '%Y') + $CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{MM}', (Get-Date -UFormat '%m') + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{dd}', (Get-Date -UFormat '%d') + $CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{HH}', (Get-Date -UFormat '%H') + $CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{hh}', (Get-Date -UFormat '%I') + $CustomFFUNameTemplate = $CustomFFUNameTemplate -creplace '{mm}', (Get-Date -UFormat '%M') + $CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{tt}', (Get-Date -UFormat '%p') + if($CustomFFUNameTemplate -notlike '*.ffu') { + $CustomFFUNameTemplate += '.ffu' } - - $dismArgs = "/capture-ffu /imagefile=W:\$CustomFFUNameTemplate /capturedrive=\\.\PhysicalDrive0 /name:$Name$DisplayVersion$SKU /Compress:Default" + $dismArgs = "/capture-ffu /imagefile=W:\$CustomFFUNameTemplate /capturedrive=\\.\PhysicalDrive0 /name:$WindowsRelease$WindowsVersion$SKU /Compress:Default" +} else { + #If Office is installed, modify the file name of the FFU + #$Office = Get-childitem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue | Out-Null + $Office = Get-ChildItem -Path 'M:\Program Files\Microsoft Office' -ErrorAction SilentlyContinue + if ($Office) { + $ffuFilePath = "W:\$WindowsRelease`_$WindowsVersion`_$SKU`_Office`_$BuildDate.ffu" + } else { + $ffuFilePath = "W:\$WindowsRelease`_$WindowsVersion`_$SKU`_Apps`_$BuildDate.ffu" + } + $dismArgs = "/capture-ffu /imagefile=$ffuFilePath /capturedrive=\\.\PhysicalDrive0 /name:$WindowsRelease$WindowsVersion$SKU /Compress:Default" } #Unload Registry Set-Location X:\ Remove-Variable SKU if ($CurrentBuild -notin 14393, 17763) { - Remove-Variable DisplayVersion + Remove-Variable WindowsVersion } Remove-Variable CurrentBuild Remove-Variable Office @@ -102,5 +100,4 @@ Start-sleep 60 Start-Process -FilePath dism.exe -ArgumentList $dismArgs -Wait -PassThru -ErrorAction Stop | Out-Null #Copy DISM log to Host xcopy X:\Windows\logs\dism\dism.log W:\ /Y | Out-Null - wpeutil Shutdown From 6abc6f9d1a960f290fb0b14a042f8bddbccf2f07 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:34:58 -0800 Subject: [PATCH 42/50] Fixed an error with removing the office variable when capturing the FFU --- FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index 986a417..b51ba5a 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -88,11 +88,13 @@ if ($CustomFFUNameTemplate) { #Unload Registry Set-Location X:\ Remove-Variable SKU +Remove-Variable CurrentBuild if ($CurrentBuild -notin 14393, 17763) { Remove-Variable WindowsVersion } -Remove-Variable CurrentBuild -Remove-Variable Office +if($Office) { + Remove-Variable Office +} reg unload "HKLM\FFU" #This prevents Critical Process Died errors you can have during deployment of the FFU - may not happen during capture from WinPE, but adding here to be consistent with VHDX capture Write-Host "Sleeping for 60 seconds to allow registry to unload prior to capture" From a2c2b690264fb784edd7c04429faff635b2faa37 Mon Sep 17 00:00:00 2001 From: Mike Kelly <167896478+MKellyCBSD@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:09:00 -0500 Subject: [PATCH 43/50] Update USBImagingToolCreator.ps1 --- FFUDevelopment/USBImagingToolCreator.ps1 | 317 ++++++++++++----------- 1 file changed, 162 insertions(+), 155 deletions(-) diff --git a/FFUDevelopment/USBImagingToolCreator.ps1 b/FFUDevelopment/USBImagingToolCreator.ps1 index c5c28ab..e5afece 100644 --- a/FFUDevelopment/USBImagingToolCreator.ps1 +++ b/FFUDevelopment/USBImagingToolCreator.ps1 @@ -1,20 +1,35 @@ [CmdletBinding()] param( [Parameter(Mandatory = $True, Position = 0)] - [io.fileinfo] $DeployISOPath, + $DeployISOPath, [Switch]$DisableAutoPlay ) +$Host.UI.RawUI.WindowTitle = 'Imaging Tool USB Creator' +if($DeployISOPath){ +$DevelopmentPath = $DeployISOPath | Split-Path +$ImagesPath = "$DevelopmentPath\FFU" function WriteLog($LogText) { - $LogFileName = '\Script.log' - $LogFile = $DevelopmentPath + $LogFilename +$LogFileName = '\Script.log' +$LogFile = $DevelopmentPath + $LogFilename Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue Write-Verbose $LogText } -function Get-USBDrive { - $USBDrives = (Get-WmiObject -Class Win32_DiskDrive -Filter "MediaType='Removable Media'") - If ($USBDrives -and ($null -eq $USBDrives.count)) { +function Write-ProgressLog { + param( + [string]$Activity, + [string]$Status + ) + Write-Progress -Activity $Activity -Status $Status -PercentComplete (($currentStep / $totalSteps) * 100) + WriteLog $Status + $script:currentStep++ + + } +Function Get-RemovableDrive { +writelog "Get information for all removable drives" +$USBDrives = Get-WmiObject Win32_DiskDrive | Where-Object {$_.MediaType -eq "Removable media"} +If($USBDrives -and ($null -eq $USBDrives.count)) { $USBDrivesCount = 1 } else { $USBDrivesCount = $USBDrives.Count @@ -24,31 +39,33 @@ function Get-USBDrive { if ($null -eq $USBDrives) { WriteLog "No removable USB drive found. Exiting" Write-Error "No removable USB drive found. Exiting" + Pause exit 1 } - return $USBDrives, $USBDrivesCount -} - -function Build-DeploymentUSB { - param( - [Array]$Drives - ) - writelog "Checking if ffu files are present in the ffu folder" - $Images = Get-ChildItem -Path $FFUPath -Filter "*.ffu" -File -Recurse - writelog "Checking if drivers are present in the drivers folder" - $Drivers = Get-ChildItem -Path $DriversPath -Recurse - $DrivesCount = $Drives.Count - Writelog "Creating partitions..." - foreach ($USBDrive in $Drives) { - $DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") - $Model = $USBDrive.model - $ScriptBlock = { + return $USBDrives, $USBDrivesCount + } + +Function Build-DeploymentUSB{ + param( + [Array]$Drives + ) + writelog "Creating list of FFU image files" + $Images = Get-ChildItem -Path $ImagesPath -Filter "*.ffu" -File -Recurse + writelog "Checking if drivers are present in the drivers folder" + $Drivers = Get-ChildItem -Path $DriversPath -Recurse + $DrivesCount = $Drives.Count + Write-ProgressLog "Create Imaging Tool" "Creating partitions..." + writelog "Create job to partition each usb drive" + foreach ($USBDrive in $Drives) { + $DriveNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") + $Model = $USBDrive.model + $ScriptBlock = { param($DriveNumber) Clear-Disk -Number $DriveNumber -RemoveData -RemoveOEM -Confirm:$false $Disk = Get-Disk -Number $DriveNumber $PartitionStyle = $Disk.PartitionStyle - if ($PartitionStyle -ne 'MBR') { - $Disk | Set-Disk -PartitionStyle MBR + if($PartitionStyle -ne 'MBR'){ + $Disk | Set-Disk -PartitionStyle MBR } $BootPartition = New-Partition -DiskNumber $DriveNumber -Size 2GB -IsActive -AssignDriveLetter $DeployPartition = New-Partition -DiskNumber $DriveNumber -UseMaximumSize -AssignDriveLetter @@ -60,178 +77,168 @@ function Build-DeploymentUSB { } writelog "Wait for partitioning jobs to complete" Get-Job | Wait-Job | Out-Null - if ($DrivesCount -gt 1) { - writelog "Get file system information for all drives" - $Partitions = Get-Partition | Get-Volume + if($DrivesCount -gt 1){ + writelog "Get file system information for all drives" + $Partitions = Get-Partition | Get-Volume } else { - writelog "Get file system information for drive number $DiskNumber" - $Partitions = Get-Partition -DiskNumber $DriveNumber | Get-Volume + writelog "Get file system information for drive number $DiskNumber" + $Partitions = Get-Partition -DiskNumber $DriveNumber | Get-Volume } - writelog "Get drive letter for all volumes labeled:BOOT" - $BootDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "BOOT" }).DriveLetter - writelog "Get drive letter for all volumes labeled:Deploy" - $DeployDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "Deploy" }).DriveLetter - writelog "Mount Deployment .iso image" - $ISOMountPoint = (Mount-DiskImage -ImagePath "$DeployISOPath" -PassThru | Get-Volume).DriveLetter + ":\" - writelog "Copying boot files to all drives labeled BOOT concurrently" - foreach ($Drive in $BootDrives) { - $Destination = $Drive + ":\" - $jobScriptBlock = { - param ( - [string]$SFolder, - [string]$DFolder - ) - Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J - } - WriteLog "Start job to copy all boot files to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ISOMountPoint, $Destination | Out-Null +writelog "Get drive letter for all volumes labeled:BOOT" +$BootDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "BOOT"}).DriveLetter +writelog "Get drive letter for all volumes labeled:Deploy" +$DeployDrives = ($Partitions | Where-Object { $_.FileSystemLabel -eq "Deploy"}).DriveLetter +writelog "Mount Deployment .iso image" +$ISOMountPoint = (Mount-DiskImage -ImagePath "$DeployISOPath" -PassThru | Get-Volume).DriveLetter + ":\" +writelog "Copying boot files to all drives labeled BOOT concurrently" +foreach ($Drive in $BootDrives) { +$Destination = $Drive + ":\" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J + } + WriteLog "Start job to copy all boot files to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ISOMountPoint, $Destination | Out-Null +} +if($Images){ +writelog "Copying FFU image files to all drives labeled deploy concurrently" +foreach ($Drive in $DeployDrives) { +$Destination = $Drive + ":\" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J } - if ($Images) { - writelog "Copying FFU image files to all drives labeled deploy concurrently" - foreach ($Drive in $DeployDrives) { - $Destination = $Drive + ":\" - $jobScriptBlock = { - param ( - [string]$SFolder, - [string]$DFolder - ) - Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J - } - WriteLog "Start job to copy all FFU files to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $FFUPath, $Destination | Out-Null + WriteLog "Start job to copy all FFU files to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $ImagesPath, $Destination | Out-Null + } +} +if(!($Images)){ + foreach ($Drive in $DeployDrives) { + WriteLog "Create images directory" + $drivepath = $Drive + ":\" + New-Item -Path "$drivepath" -Name Images -ItemType Directory -Force -Confirm: $false | Out-Null } +} +if($Drivers){ +writelog "Copying driver files to all drives labeled deploy concurrently" +foreach ($Drive in $DeployDrives) { +$Destination = $Drive + ":\Drivers" + $jobScriptBlock = { + param ( + [string]$SFolder, + [string]$DFolder + ) + New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null + Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J } - if ($Drivers) { - writelog "Copying driver files to all drives labeled deploy concurrently" - foreach ($Drive in $DeployDrives) { - $Destination = $Drive + ":\Drivers" - $jobScriptBlock = { - param ( - [string]$SFolder, - [string]$DFolder - ) - New-Item -Path $DFolder -ItemType Directory -Force -Confirm: $false | Out-Null - Robocopy $SFolder $DFolder /E /COPYALL /R:5 /W:5 /J - } - WriteLog "Start job to copy all drivers to $Destination" - Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $DriversPath, $Destination | Out-Null + WriteLog "Start job to copy all drivers to $Destination" + Start-Job -ScriptBlock $jobScriptBlock -ArgumentList $DriversPath, $Destination | Out-Null +} +} +if(!($Drivers)){ + foreach ($Drive in $DeployDrives) { + WriteLog "Create drivers directory" + $drivepath = $Drive + ":\" + New-Item -Path "$drivepath" -Name Drivers -ItemType Directory -Force -Confirm: $false | Out-Null } - } - if (!($Drivers)) { - foreach ($Drive in $DeployDrives) { - WriteLog "Create drivers directory" - $drivepath = $Drive + ":\" - New-Item -Path "$drivepath" -Name Drivers -ItemType Directory -Force -Confirm: $false | Out-Null - } - } - if ($DrivesCount -gt 1) { - Writelog "Building $DrivesCount drives concurrently...Please be patient..." - } else { - Writelog "Building the imaging tool on $model...Please be patient..." - } - Get-Job | Wait-Job | Out-Null +} +if($DrivesCount -gt 1){ +Write-ProgressLog "Create Imaging Tool" "Building $DrivesCount drives concurrently...Please be patient..." +} else { +Write-ProgressLog "Create Imaging Tool" "Building the imaging tool on $model...Please be patient..." +} +Get-Job | Wait-Job | Out-Null - Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null - Writelog "Drive creation jobs completed..." +Dismount-DiskImage -ImagePath $DeployISOPath | Out-Null +Write-ProgressLog "Create Imaging Tool" "Drive creation jobs completed..." } -function New-DeploymentUSB { +Function New-DeploymentUSB { param( [Array]$Drives, [int]$Count, [String]$FFUPath = "$DevelopmentPath\FFU", [String]$DriversPath = "$DevelopmentPath\Drivers" - + ) - + $Drivelist = @() writelog "Creating a USB drive selection list" - for ($i = 0; $i -le $Count - 1; $i++) { + for($i=0;$i -le $Count -1;$i++){ $DriveModel = $Drives[$i].Model - $DriveSize = [math]::round($Drives[$i].size / 1GB, 2) + $DriveSize = [math]::round($Drives[$i].size/1GB, 2) $DiskNumber = $Drives[$i].DeviceID.Replace("\\.\PHYSICALDRIVE", "") - $Properties = [ordered]@{Number = $i + 1 ; DriveNumber = $DiskNumber ; DriveModel = $driveModel ; 'Size (GB)' = $DriveSize } - + $Properties = [ordered]@{Number = $i + 1 ; DriveNumber = $DiskNumber ; DriveModel = $driveModel ; 'Size (GB)' = $DriveSize} + $Drivelist += New-Object PSObject -Property $Properties - } - if ($Count -gt 1) { - $Last = $Count + 1 + } + if($Count -gt 1){ + $Last = $Count+1 $Drivelist += New-Object -TypeName PSObject -Property @{ Number = "$last"; DriveModel = "Select this option to use all ($count) inserted USB Drives" } - } - $Drivelist | Format-Table -AutoSize -Property Number, DriveModel , 'Size (GB)' - do { + } + $Drivelist | Format-Table -AutoSize -Property Number, DriveModel , 'Size (GB)' + do { try { $var = $true $DriveSelected = Read-Host 'Enter the drive number to apply the .iso to' - $DriveSelected = ($DriveSelected -as [int]) - 1 - if ($Last) { - writelog "All drives selected" + $DriveSelected = ($DriveSelected -as [int]) -1 + writelog "Drive $DriveSelected selected" } - else { - writelog "Drive $DriveSelected selected" - } - } + catch { Write-Host 'Input was not in correct format. Please enter a valid FFU number' $var = $false } - } until (($DriveSelected -le $Count - 1 -or $last) -and $var) - - $DisableAutoPlayCurrentSetting = (Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name DisableAutoplay).DisableAutoplay - if ($DisableAutoPlay -and $DisableAutoPlayCurrentSetting -ne 1) { - writelog "Disable autoPlay current setting is $DisableAutoPlayCurrentSetting" - WriteLog "Setting the registry key to disable autoplay for all drives" - Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD + } until (($DriveSelected -le $Count -1 -or $last) -and $var) + if($DisableAutoPlay){ + WriteLog "Setting the registry key to disable autoplay for all drives" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 1 -Type DWORD } WriteLog "Closing all MMC windows to prevent drive lock errors" Stop-Process -Name mmc -ErrorAction SilentlyContinue WriteLog "Closing all Diskpart windows to prevent drive lock errors" Stop-Process -Name diskpart -ErrorAction SilentlyContinue $Selection = $Drivelist[$DriveSelected].Number - - if ($Selection -eq $last) { - Read-Host -Prompt "ALL DRIVES SELECTED! WILL ERASE ALL CURRENTLY CONNECTED USB DRIVES!! Press ENTER to continue" - Build-DeploymentUSB -Drives $Drives + $totalSteps = 5 + if($Selection -eq $last){ + Read-Host -Prompt "ALL DRIVES SELECTED! WILL ERASE ALL CURRENTLY CONNECTED USB DRIVES!! Press ENTER to continue" + Build-DeploymentUSB -Drives $Drives } else { - Read-Host -Prompt "Drive number $Selection was selected. Press ENTER to continue" - Build-DeploymentUSB -Drives $Drives[$DriveSelected] + Read-Host -Prompt "Drive number $Selection was selected. Press ENTER to continue" + Build-DeploymentUSB -Drives $Drives[$DriveSelected] } WriteLog "Setting the registry key to re-enable autoplay for all drives" - if ($DisableAutoPlay) { - Writelog "Setting disable autoplay setting back to $DisableAutoPlayCurrentSetting" - Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value $DisableAutoPlayCurrentSetting -Type DWORD + if($DisableAutoPlay){ + Write-ProgressLog "Create Imaging Tool" "Enabling Autoplay" + Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\AutoplayHandlers" -Name "DisableAutoplay" -Value 0 -Type DWORD } - Writelog "Completed!" + Write-ProgressLog "Create Imaging Tool" "Completed!" +} +#Get USB Drive and create log file +if(Test-Path "$DevelopmentPath\Script.log"){ +Remove-Item -Path "$DevelopmentPath\Script.log" -Force -Confirm:$false +New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out-Null } +WriteLog 'Begin Logging' +WriteLog 'Getting USB drive information and usb drive count' +$USBDrives,$USBDrivesCount = Get-RemovableDrive +WriteLog 'Setting first step for percentage progress bar' +$currentStep = 1 +New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount -$Host.UI.RawUI.WindowTitle = 'USB Imaging Tool Creator' +read-host -Prompt "USB drive creation complete. Press ENTER to exit" -# Check if path is relative -if (-not [System.IO.Path]::IsPathRooted($DeployISOPath)) { - # Path is relative, Build full path - [io.fileinfo] $DeployISOPath = (Resolve-Path $DeployISOPath).Path - Write-Verbose "Path to ISO was relative. Attempting to resolve at: $($DeployISOPath.FullName)" -} - -if ($DeployISOPath.Exists) { - $DevelopmentPath = $DeployISOPath.Directory.FullName - - #Get USB Drive and create log file - if (Test-Path "$DevelopmentPath\Script.log") { - Remove-Item -Path "$DevelopmentPath\Script.log" -Force -Confirm:$false - New-item -Path $DevelopmentPath -Name 'Script.log' -ItemType "file" -Force | Out-Null - } - - WriteLog 'Begin Logging' - WriteLog 'Getting USB drive information and usb drive count' - - $USBDrives, $USBDrivesCount = Get-USBDrive - - New-DeploymentUSB -Drives $USBDrives -Count $USBDrivesCount - - Read-Host -Prompt "USB drive creation complete. Press ENTER to exit" - Exit +Exit } else { - Write-Error "Unable to locate ISO file at: $($DeployISOPath.FullName)" +Write-Host "No .ISO file selected..." +read-host "Press ENTER to Exit..." +Exit } From 97954f59c355840be931e71d2a8d66a4f5c585b6 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:55:40 -0800 Subject: [PATCH 44/50] - Added Windows 2025/24H2 mapping --- FFUDevelopment/BuildFFUVM.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 58c5cc4..960dc98 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -420,6 +420,7 @@ if ($installationType -eq 'Server'){ 2016 { $WindowsVersion = '1607' } 2019 { $WindowsVersion = '1809' } 2022 { $WindowsVersion = '21H2' } + 2025 { $WindowsVersion = '24H2' } } } From d7a697d68d52d2ff05c7202dd687ae6b9a08e672 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:59:02 -0800 Subject: [PATCH 45/50] Misc Fixes - Added Get-Childprocesses function to return child processes of parent process - Added a new -Wait boolean parameter to Invoke-Process function. This is to control whether Invoke-Process should wait in order to track stdout and stderr output. This is needed for processes that may hang, waiting for user input and there isn't a way to bypass (some Intel drivers provided by Dell leave dialog windows even when running silently) -Invoke-Process now returns process information (returns $cmd). This allows for process tracking when calling the function. - Since Invoke-Process now returns the process information, also needed to add Out-Null to the majority of the Invoke-Process references to prevent Invoke-Process from writing to the terminal - Refactored a lot of the Get-DellDrivers function due to inconsistencies with how driver extraction behaves between client and server devices. For client, /s /e seemed to work fine, but for server it would only extract the driver installer content and other dell related files, rather than the driver files themselves. We have since switched to using /s /drivers= which will extract the driver content. Not all drivers support /drivers= and may output some information to the terminal that looks like help documentation. If a driver doesn't support /drivers, the script falls back to using /s /e to do the extraction. If this doesn't work for you, you can always provide your own drivers that you manually download from Dell's website. - Updated Malicious Software Removal Tool (MSRT) code to handle Windows Server --- FFUDevelopment/BuildFFUVM.ps1 | 186 ++++++++++++++++++++++++---------- 1 file changed, 132 insertions(+), 54 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 960dc98..1e42dd5 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -475,6 +475,16 @@ function LogVariableValues { WriteLog 'End logging variables' } +function Get-ChildProcesses($parentId) { + $result = @() + $children = Get-CimInstance Win32_Process -Filter "ParentProcessId = $parentId" + foreach ($child in $children) { + $result += $child + $result += Get-ChildProcesses $child.ProcessId + } + return $result +} + function Invoke-Process { [CmdletBinding(SupportsShouldProcess)] param @@ -485,7 +495,11 @@ function Invoke-Process { [Parameter()] [ValidateNotNullOrEmpty()] - [string]$ArgumentList + [string]$ArgumentList, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [bool]$Wait = $true ) $ErrorActionPreference = 'Stop' @@ -499,7 +513,7 @@ function Invoke-Process { ArgumentList = $ArgumentList RedirectStandardError = $stdErrTempFile RedirectStandardOutput = $stdOutTempFile - Wait = $true; + Wait = $($Wait); PassThru = $true; NoNewWindow = $true; } @@ -507,7 +521,7 @@ function Invoke-Process { $cmd = Start-Process @startProcessParams $cmdOutput = Get-Content -Path $stdOutTempFile -Raw $cmdError = Get-Content -Path $stdErrTempFile -Raw - if ($cmd.ExitCode -ne 0) { + if ($cmd.ExitCode -ne 0 -and $wait -eq $true) { if ($cmdError) { throw $cmdError.Trim() } @@ -525,15 +539,14 @@ function Invoke-Process { catch { #$PSCmdlet.ThrowTerminatingError($_) WriteLog $_ - Write-Host "Script failed - $Logfile for more info" + # Write-Host "Script failed - $Logfile for more info" throw $_ } finally { Remove-Item -Path $stdOutTempFile, $stdErrTempFile -Force -ErrorAction Ignore - } - + return $cmd } function Test-Url { @@ -734,7 +747,7 @@ function Get-MicrosoftDrivers { # Extract the MSI file using an administrative install WriteLog "Extracting MSI file to $modelPath" $arguments = "/a `"$($filePath)`" /qn TARGETDIR=`"$($modelPath)`"" - Invoke-Process -FilePath "msiexec.exe" -ArgumentList $arguments + Invoke-Process -FilePath "msiexec.exe" -ArgumentList $arguments | Out-Null WriteLog "Extraction complete" } elseif ($fileExtension -eq ".zip") { # Extract the ZIP file @@ -790,7 +803,7 @@ function Get-HPDrivers { Start-BitsTransferWithRetry -Source $PlatformListUrl -Destination $PlatformListCab WriteLog "Download complete" WriteLog "Expanding $PlatformListCab to $PlatformListXml" - Invoke-Process -FilePath expand.exe -ArgumentList "$PlatformListCab $PlatformListXml" + Invoke-Process -FilePath expand.exe -ArgumentList "$PlatformListCab $PlatformListXml" | Out-Null WriteLog "Expansion complete" # Parse the PlatformList.xml to find the SystemID based on the ProductName @@ -920,7 +933,7 @@ function Get-HPDrivers { Writelog "Downloading HP Driver cab from $DriverCabUrl to $DriverCabFile" Start-BitsTransferWithRetry -Source $DriverCabUrl -Destination $DriverCabFile WriteLog "Expanding HP Driver cab to $DriverXmlFile" - Invoke-Process -FilePath expand.exe -ArgumentList "$DriverCabFile $DriverXmlFile" + Invoke-Process -FilePath expand.exe -ArgumentList "$DriverCabFile $DriverXmlFile" | Out-Null # Parse the extracted XML file to download individual drivers [xml]$DriverXmlContent = Get-Content -Path $DriverXmlFile @@ -971,7 +984,7 @@ function Get-HPDrivers { # Extract the driver $arguments = "/s /e /f `"$extractFolder`"" WriteLog "Extracting driver" - Invoke-Process -FilePath $DriverFilePath -ArgumentList $arguments + Invoke-Process -FilePath $DriverFilePath -ArgumentList $arguments | Out-Null WriteLog "Driver extracted to: $extractFolder" # Delete the .exe driver file after extraction @@ -1181,7 +1194,7 @@ function Get-LenovoDrivers { # Extract the driver # Start-Process -FilePath $driverFilePath -ArgumentList $modifiedExtractCommand -Wait -NoNewWindow WriteLog "Extracting driver: $driverFilePath to $extractFolder" - Invoke-Process -FilePath $driverFilePath -ArgumentList $modifiedExtractCommand + Invoke-Process -FilePath $driverFilePath -ArgumentList $modifiedExtractCommand | Out-Null WriteLog "Driver extracted" # Delete the .exe driver file after extraction @@ -1212,6 +1225,17 @@ function Get-DellDrivers { [int]$WindowsRelease ) + if (-not (Test-Path -Path $DriversFolder)) { + WriteLog "Creating Drivers folder: $DriversFolder" + New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null + WriteLog "Drivers folder created" + } + + $DriversFolder = "$DriversFolder\$Make" + WriteLog "Creating Dell Drivers folder: $DriversFolder" + New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null + WriteLog "Dell Drivers folder created" + #CatalogPC.cab is the catalog for Windows client PCs, Catalog.cab is the catalog for Windows Server if ($WindowsRelease -le 11) { $catalogUrl = "http://downloads.dell.com/catalog/CatalogPC.cab" @@ -1231,23 +1255,12 @@ function Get-DellDrivers { exit } - if (-not (Test-Path -Path $DriversFolder)) { - WriteLog "Creating Drivers folder: $DriversFolder" - New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null - WriteLog "Drivers folder created" - } - - $DriversFolder = "$DriversFolder\$Make" - WriteLog "Creating Dell Drivers folder: $DriversFolder" - New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null - WriteLog "Dell Drivers folder created" - WriteLog "Downloading Dell Catalog cab file: $catalogUrl to $DellCabFile" Start-BitsTransferWithRetry -Source $catalogUrl -Destination $DellCabFile WriteLog "Dell Catalog cab file downloaded" WriteLog "Extracting Dell Catalog cab file to $DellCatalogXML" - Invoke-Process -FilePath Expand.exe -ArgumentList "$DellCabFile $DellCatalogXML" + Invoke-Process -FilePath Expand.exe -ArgumentList "$DellCabFile $DellCatalogXML" | Out-Null WriteLog "Dell Catalog cab file extracted" $xmlContent = [xml](Get-Content -Path $DellCatalogXML) @@ -1268,6 +1281,8 @@ function Get-DellDrivers { $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W19") } } elseif ($WindowsRelease -eq 2022) { $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") } + } elseif ($WindowsRelease -eq 2025) { + $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W25") } } else { $validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") } } @@ -1277,9 +1292,10 @@ function Get-DellDrivers { $downloadUrl = $baseLocation + $driverPath $driverFileName = [System.IO.Path]::GetFileName($driverPath) $name = $component.Name.Display.'#cdata-section' - $name = $name -replace '[\\\/\:\*\?\"\<\>\|]', '_' + $name = $name -replace '[\\\/\:\*\?\"\<\>\| ]', '_' $name = $name -replace '[\,]', '-' $category = $component.Category.Display.'#cdata-section' + $category = $category -replace '[\\\/\:\*\?\"\<\>\| ]', '_' $version = [version]$component.vendorVersion $namePrefix = ($name -split '-')[0] @@ -1327,7 +1343,7 @@ function Get-DellDrivers { $driverFilePath = Join-Path -Path $downloadFolder -ChildPath $driver.DriverFileName if (Test-Path -Path $driverFilePath) { - Write-Output "Driver already downloaded: $driverFilePath skipping" + WriteLog "Driver already downloaded: $driverFilePath skipping" continue } @@ -1349,13 +1365,66 @@ function Get-DellDrivers { $extractFolder = $downloadFolder + "\" + $driver.DriverFileName.TrimEnd($driver.DriverFileName[-4..-1]) - WriteLog "Creating extraction folder: $extractFolder" - New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null - WriteLog "Extraction folder created" + # WriteLog "Creating extraction folder: $extractFolder" + # New-Item -Path $extractFolder -ItemType Directory -Force | Out-Null + # WriteLog "Extraction folder created" - $arguments = "/s /e=`"$extractFolder`"" - WriteLog "Extracting driver: $driverFilePath to $extractFolder" - Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments + # $arguments = "/s /e=`"$extractFolder`"" + $arguments = "/s /drivers=`"$extractFolder`"" + WriteLog "Extracting driver: $driverFilePath $arguments" + try { + #If Category is Chipset, must add -wait $false to the Invoke-Process command line to prevent the script from hanging on the Intel chipset driver which leaves a Window open + if ($driver.Category -eq "Chipset") { + $process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait $false + + #Wait 5 seconds to allow for the extraction process to finish + Start-Sleep -Seconds 5 + + $childProcesses = Get-ChildProcesses $process.Id + + # Find and stop the last created child process + if ($childProcesses) { + $latestProcess = $childProcesses | Sort-Object CreationDate -Descending | Select-Object -First 1 + Stop-Process -Id $latestProcess.ProcessId -Force + # Sleep 1 second to let process finish exiting so its installer can be removed + Start-Sleep -Seconds 1 + } + #If Category is Network and $isServer is $false, must add -wait $false to the Invoke-Process command line to prevent the script from hanging on the Intel network driver which leaves a Window open + } elseif ($driver.Category -eq "Network" -and $isServer -eq $false) { + + $process = Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait $false + + #Sometimes the network drivers will extract on client OS, wait 5 seconds and check if the process is still running + Start-Sleep -Seconds 5 + if ($process.HasExited -eq $false) { + $childProcesses = Get-ChildProcesses $process.Id + + # Find and stop the last created child process + if ($childProcesses) { + $latestProcess = $childProcesses | Sort-Object CreationDate -Descending | Select-Object -First 1 + Stop-Process -Id $latestProcess.ProcessId -Force + #Move on to the next driver and skip this one - it won't extract on a client OS even with /s /e switches + continue + } + } + } else { + Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null + } + # If $extractFolder is empty, try alternative extraction method + if (!(Get-ChildItem -Path $extractFolder -Recurse | Where-Object { -not $_.PSIsContainer })) { + WriteLog 'Extraction with /drivers= switch failed. Removing folder and retrying with /s /e switches' + Remove-Item -Path $extractFolder -Force -Recurse -ErrorAction SilentlyContinue + $arguments = "/s /e=`"$extractFolder`"" + WriteLog "Extracting driver: $driverFilePath $arguments" + Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null + } + } + catch { + WriteLog 'Extraction with /drivers= switch failed. Retrying with /s /e switches' + $arguments = "/s /e=`"$extractFolder`"" + WriteLog "Extracting driver: $driverFilePath $arguments" + Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments | Out-Null + } WriteLog "Driver extracted" WriteLog "Deleting driver file: $driverFilePath" @@ -1455,7 +1524,7 @@ function Install-ADK { WriteLog "$ADKOption downloaded to $installerLocation" WriteLog "Installing $ADKOption with $feature enabled" - Invoke-Process $installerLocation "/quiet /installpath ""%ProgramFiles(x86)%\Windows Kits\10"" /features $feature" + Invoke-Process $installerLocation "/quiet /installpath ""%ProgramFiles(x86)%\Windows Kits\10"" /features $feature" | Out-Null WriteLog "$ADKOption installation completed." WriteLog "Removing $installer from $installerLocation" @@ -1512,7 +1581,7 @@ function Uninstall-ADK { $adkBundleCachePath = $adkRegKey.GetValue("BundleCachePath") WriteLog "Uninstalling $ADKOption..." - Invoke-Process $adkBundleCachePath "/uninstall /quiet" + Invoke-Process $adkBundleCachePath "/uninstall /quiet" | Out-Null WriteLog "$ADKOption uninstalled successfully." } catch { @@ -1615,7 +1684,7 @@ function Get-ADK { if ($adkBundleCachePath) { WriteLog "Installing Windows Deployment Tools..." $adkInstallPath = $adkPath.TrimEnd('\') - Invoke-Process $adkBundleCachePath "/quiet /installpath ""$adkInstallPath"" /features OptionId.DeploymentTools" + Invoke-Process $adkBundleCachePath "/quiet /installpath ""$adkInstallPath"" /features OptionId.DeploymentTools" | Out-Null WriteLog "Windows Deployment Tools installed successfully." } else { @@ -1668,7 +1737,7 @@ function Get-WindowsESD { # Extract XML from cab file WriteLog "Extracting Products XML from cab" $xmlFilePath = Join-Path $PSScriptRoot "products.xml" - Invoke-Process Expand "-F:*.xml $cabFilePath $xmlFilePath" + Invoke-Process Expand "-F:*.xml $cabFilePath $xmlFilePath" | Out-Null WriteLog "Products XML extracted" # Load XML content @@ -1729,7 +1798,7 @@ function Get-Office { # Extract ODT WriteLog "Extracting ODT to $OfficePath" - Invoke-Process $ODTInstallFile "/extract:$OfficePath /quiet" + Invoke-Process $ODTInstallFile "/extract:$OfficePath /quiet" | Out-Null # Run setup.exe with config.xml and modify xml file to download to $OfficePath $ConfigXml = "$OfficePath\DownloadFFU.xml" @@ -1737,7 +1806,7 @@ function Get-Office { $xmlContent.Configuration.Add.SourcePath = $OfficePath $xmlContent.Save($ConfigXml) WriteLog "Downloading M365 Apps/Office to $OfficePath" - Invoke-Process $OfficePath\setup.exe "/download $ConfigXml" + Invoke-Process $OfficePath\setup.exe "/download $ConfigXml" | Out-Null WriteLog "Cleaning up ODT default config files and checking InstallAppsandSysprep.cmd file for proper command line" #Clean up default configuration files @@ -1796,7 +1865,7 @@ function Install-WinGet { # } # $wingetVersion = & winget.exe --version # WriteLog "Installed version of WinGet: $wingetVersion" -# if ($wingetVersion -match 'v?(\d+\.\d+\.\d+)' -and [version]$matches[1] -lt $minVersion) { +# if ($wingetVersion -match 'v?(\d+\.\d+.\d+)' -and [version]$matches[1] -lt $minVersion) { # WriteLog "The installed version of WinGet $($matches[1]) does not support downloading MSStore apps. Downloading the latest version of WinGet..." # Install-WinGet -Architecture $WindowsArch # } @@ -2415,7 +2484,7 @@ function New-AppsISO { #Create Apps ISO file $OSCDIMG = "$adkpath`Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe" #Start-Process -FilePath $OSCDIMG -ArgumentList "-n -m -d $Appspath $AppsISO" -wait - Invoke-Process $OSCDIMG "-n -m -d $Appspath $AppsISO" + Invoke-Process $OSCDIMG "-n -m -d $Appspath $AppsISO" | Out-Null #Remove the Office Download and ODT if ($InstallOffice) { @@ -2707,7 +2776,7 @@ function Add-BootFiles { ) WriteLog "Adding boot files for `"$($OsPartitionDriveLetter):\Windows`" to System partition `"$($SystemPartitionDriveLetter):`"..." - Invoke-Process bcdboot "$($OsPartitionDriveLetter):\Windows /S $($SystemPartitionDriveLetter): /F $FirmwareType" + Invoke-Process bcdboot "$($OsPartitionDriveLetter):\Windows /S $($SystemPartitionDriveLetter): /F $FirmwareType" | Out-Null WriteLog "Done." } @@ -2848,7 +2917,7 @@ function New-PEMedia { elseif($WindowsArch -eq 'arm64') { & cmd /c """$DandIEnv"" && copype arm64 $WinPEFFUPath" | Out-Null } - #Invoke-Process cmd "/c ""$DandIEnv"" && copype amd64 $WinPEFFUPath" + #Invoke-Process cmd "/c ""$DandIEnv"" && copype amd64 $WinPEFFUPath" | Out-Null WriteLog 'Files copied successfully' WriteLog 'Mounting WinPE media to add WinPE optional components' @@ -2944,7 +3013,7 @@ function New-PEMedia { } } - Invoke-Process $OSCDIMG $OSCDIMGArgs + Invoke-Process $OSCDIMG $OSCDIMGArgs | Out-Null WriteLog "ISO created successfully" WriteLog "Cleaning up $WinPEFFUPath" Remove-Item -Path "$WinPEFFUPath" -Recurse -Force @@ -3025,8 +3094,8 @@ function New-FFU { WriteLog "FFU file name: $FFUFileName" $FFUFile = "$FFUCaptureLocation\$FFUFileName" #Capture the FFU - Invoke-Process cmd "/c ""$DandIEnv"" && dism /Capture-FFU /ImageFile:$FFUFile /CaptureDrive:\\.\PhysicalDrive$($vhdxDisk.DiskNumber) /Name:$($winverinfo.Name)$($winverinfo.DisplayVersion)$($winverinfo.SKU) /Compress:Default" - # Invoke-Process cmd "/c dism /Capture-FFU /ImageFile:$FFUFile /CaptureDrive:\\.\PhysicalDrive$($vhdxDisk.DiskNumber) /Name:$($winverinfo.Name)$($winverinfo.DisplayVersion)$($winverinfo.SKU) /Compress:Default" + Invoke-Process cmd "/c ""$DandIEnv"" && dism /Capture-FFU /ImageFile:$FFUFile /CaptureDrive:\\.\PhysicalDrive$($vhdxDisk.DiskNumber) /Name:$($winverinfo.Name)$($winverinfo.DisplayVersion)$($winverinfo.SKU) /Compress:Default" | Out-Null + # Invoke-Process cmd "/c dism /Capture-FFU /ImageFile:$FFUFile /CaptureDrive:\\.\PhysicalDrive$($vhdxDisk.DiskNumber) /Name:$($winverinfo.Name)$($winverinfo.DisplayVersion)$($winverinfo.SKU) /Compress:Default" | Out-Null WriteLog 'FFU Capture complete' Dismount-ScratchVhdx -VhdxPath $VHDXPath } @@ -3063,8 +3132,8 @@ function New-FFU { if ($Optimize -eq $true) { WriteLog 'Optimizing FFU - This will take a few minutes, please be patient' #Need to use ADK version of DISM to address bug in DISM - perhaps Windows 11 24H2 will fix this - Invoke-Process cmd "/c ""$DandIEnv"" && dism /optimize-ffu /imagefile:$FFUFile" - #Invoke-Process cmd "/c dism /optimize-ffu /imagefile:$FFUFile" + Invoke-Process cmd "/c ""$DandIEnv"" && dism /optimize-ffu /imagefile:$FFUFile" | Out-Null + #Invoke-Process cmd "/c dism /optimize-ffu /imagefile:$FFUFile" | Out-Null WriteLog 'Optimizing FFU complete' } @@ -3125,7 +3194,7 @@ function Remove-FFUVM { } #Remove unused mountpoints WriteLog 'Remove unused mountpoints' - Invoke-Process cmd "/c mountvol /r" + Invoke-Process cmd "/c mountvol /r" | Out-Null WriteLog 'Removal complete' } Function Remove-FFUUserShare { @@ -3145,7 +3214,7 @@ Function Get-WindowsVersionInfo { #Load Registry Hive $Software = "$osPartitionDriveLetter`:\Windows\System32\config\software" WriteLog "Loading Software registry hive" - Invoke-Process reg "load HKLM\FFU $Software" + Invoke-Process reg "load HKLM\FFU $Software" | Out-Null #Find Windows version values $SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID' @@ -3191,7 +3260,7 @@ Function Get-WindowsVersionInfo { } WriteLog "Unloading registry" - Invoke-Process reg "unload HKLM\FFU" + Invoke-Process reg "unload HKLM\FFU" | Out-Null #This prevents Critical Process Died errors you can have during deployment of the FFU. Capturing from very fast disks (NVME) can cause the capture to happen faster than Windows is ready for. WriteLog 'Sleep 60 seconds to allow registry to completely unload' Start-sleep 60 @@ -3579,7 +3648,7 @@ function Get-FFUEnvironment { # Remove unused mountpoints WriteLog 'Remove unused mountpoints' - Invoke-Process cmd "/c mountvol /r" + Invoke-Process cmd "/c mountvol /r" | Out-Null WriteLog 'Removal complete' # Check for content in the VM folder and delete any folders that start with _FFU- @@ -3621,8 +3690,8 @@ function Get-FFUEnvironment { #Clean up registry if (Test-Path -Path 'HKLM:\FFU') { - Writelog 'Found HKLM:\FFU, removing it' - Invoke-Process reg "unload HKLM\FFU" + Writelog 'Found HKLM:\FFU, removing it' + Invoke-Process reg "unload HKLM\FFU" | Out-Null } #Remove FFU User and Share @@ -4071,7 +4140,16 @@ if ($InstallApps) { if ($UpdateLatestMSRT) { WriteLog "`$UpdateLatestMSRT is set to true." if ($WindowsArch -eq 'x64') { - $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows $WindowsRelease""" + if ($installationType -eq 'client') { + $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows $WindowsRelease""" + } + #Windows Server 2025 isn't listed as a product in the Microsoft Update Catalog, so we'll use the 2019 version + elseif ($installationType -eq 'server' -and $WindowsRelease -eq '24H2') { + $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows Server 2019""" + } + else { + $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows Server $WindowsRelease""" + } } if ($WindowsArch -eq 'x86') { $Name = """Windows Malicious Software Removal Tool""" + " " + """Windows $WindowsRelease""" @@ -4143,7 +4221,7 @@ if ($InstallApps) { $EdgeMSIFileName = "MicrosoftEdgeEnterprise$WindowsArch.msi" $EdgeFullFilePath = "$EdgePath\$EdgeMSIFileName" WriteLog "Expanding $EdgeCABFilePath" - Invoke-Process Expand "$EdgeCABFilePath -F:*.msi $EdgeFullFilePath" + Invoke-Process Expand "$EdgeCABFilePath -F:*.msi $EdgeFullFilePath" | Out-Null WriteLog "Expansion complete" #Remove Edge CAB file From 10624787fe6b98416659691c47a67aaf17fc4437 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:39:49 -0800 Subject: [PATCH 46/50] Fix ODT - Refactored the Get-ODTURL function to fix recent download issues. Also added some better error handling - Moved the odtsetup.exe download to the FFUDevelopment folder and will clean it up after office has downloaded --- FFUDevelopment/BuildFFUVM.ps1 | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 1e42dd5..961e4ba 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -1775,21 +1775,36 @@ function Get-WindowsESD { function Get-ODTURL { + try { + [String]$ODTPage = Invoke-WebRequest 'https://www.microsoft.com/en-us/download/details.aspx?id=49117' -Headers $Headers -UserAgent $UserAgent -ErrorAction Stop - # [String]$MSWebPage = Invoke-RestMethod 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=49117' - [String]$MSWebPage = Invoke-RestMethod 'https://www.microsoft.com/en-us/download/confirmation.aspx?id=49117' -Headers $Headers -UserAgent $UserAgent - - $MSWebPage | ForEach-Object { - if ($_ -match 'url=(https://.*officedeploymenttool.*\.exe)') { - $matches[1] + # Extract JSON data from the webpage + if ($ODTPage -match '' + $scriptPattern = '