From d45b6dc8dcc80e3ae165c1065a4effcf6d6bd39a Mon Sep 17 00:00:00 2001 From: JonasKloseBW Date: Mon, 30 Sep 2024 14:14:33 +0200 Subject: [PATCH 01/14] 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 02/14] 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 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 03/14] =?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 04/14] 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 05/14] 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 06/14] 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 07/14] 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 08/14] 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 09/14] 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 10/14] 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 11/14] 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 12/14] 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 13/14] 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 14/14] 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 }