From bc4a181182a5de9ecae4aef527aba80534b07694 Mon Sep 17 00:00:00 2001 From: Zehadi Alam <63765084+zehadialam@users.noreply.github.com> Date: Sat, 28 Dec 2024 20:01:01 -0500 Subject: [PATCH] Add support for LTSC versions of Windows --- FFUDevelopment/BuildFFUVM.ps1 | 115 +++++++++++++++--- .../WinPECaptureFFUFiles/CaptureFFU.ps1 | 4 +- 2 files changed, 104 insertions(+), 15 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index efff889..d5f862e 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -224,7 +224,7 @@ param( [Parameter(Mandatory = $false, Position = 0)] [ValidateScript({ Test-Path $_ })] [string]$ISOPath, - [ValidateSet('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)')] + [ValidateSet('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Enterprise LTSC', 'Enterprise N LTSC', 'IoT Enterprise LTSC', 'IoT Enterprise N LTSC', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)')] [string]$WindowsSKU = 'Pro', [ValidateScript({ Test-Path $_ })] [string]$FFUDevelopmentPath = $PSScriptRoot, @@ -284,7 +284,7 @@ param( [string]$ProductKey, [bool]$BuildUSBDrive, [Parameter(Mandatory = $false)] - [ValidateSet(10, 11, 2016, 2019, 2022, 2025)] + [ValidateSet(10, 11, 2016, 2019, 2021, 2022, 2024, 2025)] [int]$WindowsRelease = 11, [Parameter(Mandatory = $false)] [string]$WindowsVersion = '24h2', @@ -400,6 +400,26 @@ if ($ConfigFile -and (Test-Path -Path $ConfigFile)) { } } +$clientSKUs = @('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N') +$LTSCSKUs = @('Enterprise LTSC', 'Enterprise N LTSC', 'IoT Enterprise LTSC', 'IoT Enterprise N LTSC') +$ServerSKUs = @('Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)') +$releaseToSKUMapping = @{ + 10 = $clientSKUs + 11 = $clientSKUs + 2016 = $LTSCSKUs + $ServerSKUs + 2019 = $LTSCSKUs + $ServerSKUs + 2021 = $LTSCSKUs + 2022 = $ServerSKUs + 2024 = $LTSCSKUs + 2025 = $ServerSKUs +} +if ($releaseToSKUMapping.ContainsKey($WindowsRelease) -and $WindowsSKU -notin $releaseToSKUMapping[$WindowsRelease]) { + throw "Selected SKU is $WindowsSKU. Windows $WindowsRelease requires one of these SKUs: $($releaseToSKUMapping[$WindowsRelease] -join ', ')" +} +if ($WindowsRelease -notin 10, 11 -and -not $ISOPath) { + throw "Windows $WindowsRelease cannot automatically be downloaded. Please specify your own ISO using the -ISOPath parameter." +} + #Class definition for vhdx cache class VhdxCacheUpdateItem { [string]$Name @@ -461,7 +481,7 @@ 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 (-not $installationType) { $installationType = if ($WindowsSKU -like "Standard*" -or $WindowsSKU -like "Datacenter*") { 'Server' } else { 'Client' } } if ($installationType -eq 'Server'){ #Map $WindowsRelease to $WindowsVersion for Windows Server switch ($WindowsRelease) { @@ -472,6 +492,15 @@ if ($installationType -eq 'Server'){ } } +if ($WindowsSKU -like "*LTSC") { + switch ($WindowsRelease) { + 2016 { $WindowsVersion = '1607' } + 2019 { $WindowsVersion = '1809' } + 2021 { $WindowsVersion = '21H2' } + 2024 { $WindowsVersion = '24H2' } + } +} + #FUNCTIONS function WriteLog($LogText) { Add-Content -path $LogFile -value "$((Get-Date).ToString()) $LogText" -Force -ErrorAction SilentlyContinue @@ -1809,7 +1838,7 @@ function Get-WindowsESD { elseif ($WindowsRelease -eq 11) { 'https://go.microsoft.com/fwlink/?LinkId=2156292' } else { - throw "Downloading Windows Server is not supported. Please use the -ISOPath parameter to specify the path to the Windows Server ISO file." + throw "Downloading Windows $WindowsRelease is not supported. Please use the -ISOPath parameter to specify the path to the Windows $WindowsRelease ISO file." } # Download cab file @@ -2619,6 +2648,10 @@ function Get-WimIndex { 'Pro_Edu_N' { 9 } 'Pro_WKS' { 10 } 'Pro_WKS_N' { 11 } + 'Enterprise' { 3 } + 'Enterprise N' { 4 } + 'Enterprise LTSC' { 1 } + 'Enterprise N LTSC' { 2 } Default { 6 } } } @@ -3371,6 +3404,8 @@ Function Get-WindowsVersionInfo { Professional { 'Pro' } ProfessionalEducation { 'Pro_Edu' } Enterprise { 'Ent' } + EnterpriseS { 'Ent_LTSC' } + IoTEnterpriseS { 'IoT_Ent_LTSC' } Education { 'Edu' } ProfessionalWorkstation { 'Pro_Wks' } ServerStandard { 'Srv_Std' } @@ -4356,9 +4391,15 @@ if ($InstallApps) { if ($UpdateLatestMSRT) { WriteLog "`$UpdateLatestMSRT is set to true." if ($WindowsArch -eq 'x64') { - if ($installationType -eq 'client') { + if ($WindowsRelease -in 10, 11) { $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows $WindowsRelease""" } + elseif ($WindowsRelease -in 2016, 2019, 2021 -and $WindowsSKU -like "*LTSC") { + $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows 10""" + } + elseif ($WindowsRelease -in 2024 -and $WindowsSKU -like "*LTSC") { + $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows 11""" + } #Windows Server 2025 isn't listed as a product in the Microsoft Update Catalog, so we'll use the 2019 version elseif ($installationType -eq 'server' -and $WindowsRelease -eq '24H2') { $Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows Server 2019""" @@ -4495,7 +4536,7 @@ try { #The Windows release info page is updated later than the MU Catalog if ($UpdateLatestCU -and -not $UpdatePreviewCU) { Writelog "`$UpdateLatestCU is set to true, checking for latest CU" - if ($installationType -eq 'Client') { + if ($WindowsRelease -in 10, 11) { $Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch""" } if ($WindowsRelease -eq 2025) { @@ -4504,9 +4545,20 @@ try { if ($WindowsRelease -eq 2022) { $Name = """Cumulative Update for Microsoft server operating system, version 21h2 for $WindowsArch""" } - if ($WindowsRelease -in 2016, 2019) { + if ($WindowsRelease -in 2016, 2019 -and $installationType -eq "Server") { $Name = """Cumulative update for Windows Server $WindowsRelease for $WindowsArch""" } + if ($WindowsRelease -in 2016, 2019, 2021 -and $WindowsSKU -like "*LTSC") { + $today = Get-Date + $firstDayOfMonth = Get-Date -Year $today.Year -Month $today.Month -Day 1 + $secondTuesday = $firstDayOfMonth.AddDays(((2 - [int]$firstDayOfMonth.DayOfWeek + 7) % 7) + 7) + $updateDate = if ($today -gt $secondTuesday) { $today } else { $today.AddMonths(-1) } + # More precise search to prevent Dynamic cumulative update from being chosen. + $Name = """$($updateDate.ToString('yyyy-MM')) Cumulative update for Windows 10 Version $WindowsVersion for $WindowsArch""" + } + if ($WindowsRelease -eq 2024 -and $WindowsSKU -like "*LTSC") { + $Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch""" + } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" @@ -4520,6 +4572,13 @@ try { $SSUFilePath = "$KBPath\$SSUFile" WriteLog "Latest SSU saved to $SSUFilePath" } + if ($WindowsRelease -in 2016, 2019, 2021 -and $WindowsSKU -like "*LTSC") { + $SSUName = """Servicing Stack Update for Windows 10 Version $WindowsVersion for $WindowsArch""" + WriteLog "Searching for $SSUName from Microsoft Update Catalog and saving to $KBPath" + $SSUFile = Save-KB -Name $SSUName -Path $KBPath + $SSUFilePath = "$KBPath\$SSUFile" + WriteLog "Latest SSU saved to $SSUFilePath" + } WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath" $KBFilePath = Save-KB -Name $Name -Path $KBPath WriteLog "Latest CU saved to $KBPath\$KBFilePath" @@ -4527,7 +4586,7 @@ try { #Update Latest Preview Cumlative Update for Client OS only #will take Precendence over $UpdateLatestCU if both were set to $true - if ($UpdatePreviewCU -and $installationType -eq 'Client') { + if ($UpdatePreviewCU -and $installationType -eq 'Client' -and $WindowsSKU -notlike "*LTSC") { 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 @@ -4543,8 +4602,8 @@ try { #Update Latest .NET Framework if ($UpdateLatestNet) { Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework" - if ($installationType -eq 'Client') { - $Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" + if ($WindowsRelease -in 10, 11) { + $Name = "Cumulative update for .NET framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview" } if ($WindowsRelease -eq 2025) { $Name = """Cumulative Update for .NET Framework"" ""3.5 and 4.8.1"" for Windows 11 24H2 x64 -preview" @@ -4552,12 +4611,18 @@ try { if ($WindowsRelease -eq 2022) { $Name = """Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1"" ""operating system version 21H2 for x64""" } - if ($WindowsRelease -eq 2019) { + if ($WindowsRelease -eq 2019 -and $installationType -eq "Server") { $Name = """Cumulative Update for .NET Framework 3.5, 4.7.2 and 4.8 for Windows Server 2019 for x64""" } - if ($WindowsRelease -eq 2016) { + if ($WindowsRelease -eq 2016 -and $installationType -eq "Server") { $Name = """Cumulative Update for .NET Framework 4.8 for Windows Server 2016 for x64""" } + if ($WindowsRelease -in 2016, 2019, 2021 -and $WindowsSKU -like "*LTSC") { + $Name = "Cumulative update for .net framework windows 10 $WindowsVersion $WindowsArch" + } + if ($WindowsRelease -eq 2024) { + $Name = "Cumulative update for .NET framework windows 11 $WindowsVersion $WindowsArch" + } #Check if $KBPath exists, if not, create it If (-not (Test-Path -Path $KBPath)) { WriteLog "Creating $KBPath" @@ -4566,6 +4631,18 @@ try { 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" + if ($WindowsRelease -eq 2021) { + WriteLog "Checking for latest .NET Framework feature pack for Windows $WindowsRelease $WindowsSKU" + $Name = """Microsoft .NET Framework 4.8.1 for Windows 10 Version 21H2 for x64""" + $KBFilePath = Save-KB -Name $Name -Path $KBPath + WriteLog "Latest .NET Framework feature pack saved to $KBPath\$KBFilePath" + } + if ($WindowsRelease -in 2016, 2019) { + WriteLog "Checking for latest .NET Framework feature pack for Windows $WindowsRelease $WindowsSKU" + $Name = """Microsoft .NET Framework 4.8 for Windows 10 Version $WindowsVersion and Windows Server $WindowsRelease for x64""" + $KBFilePath = Save-KB -Name $Name -Path $KBPath + WriteLog "Latest .NET Framework feature pack saved to $KBPath\$KBFilePath" + } } #Search for cached VHDX and skip VHDX creation if there's a cached version @@ -4672,7 +4749,7 @@ 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) { + if ($WindowsRelease -eq 2016 -and $installationType -eq "Server") { WriteLog 'WindowsRelease is 2016, adding SSU first' WriteLog "Adding SSU to $WindowsPartition" # Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null @@ -4683,8 +4760,17 @@ try { WriteLog "Removing $SSUFilePath" Remove-Item -Path $SSUFilePath -Force | Out-Null WriteLog 'SSU removed' - WriteLog "Adding CU to $WindowsPartition" } + if ($WindowsRelease -in 2016, 2019, 2021 -and $WindowsSKU -like "*LTSC") { + WriteLog "WindowsRelease is $WindowsRelease and is $WindowsSKU, adding SSU first" + WriteLog "Adding SSU to $WindowsPartition" + 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 Cumulative Updates to $WindowsPartition" # Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null WriteLog "KBs added to $WindowsPartition" @@ -4701,6 +4787,7 @@ try { 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 + # Repair-WindowsImage -Path $WindowsPartition -StartComponentCleanup -ResetBase | Out-Null WriteLog 'Clean Up the WinSxS Folder completed' } catch { Write-Host "Adding KB to VHDX failed with error $_" diff --git a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 index b51ba5a..08be73a 100644 --- a/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 +++ b/FFUDevelopment/WinPECaptureFFUFiles/CaptureFFU.ps1 @@ -27,7 +27,9 @@ $SKU = switch ($SKU) { ProfessionalEducation { 'Pro_Edu' } ProfessionalEducationN { 'Pro_EduN' } Enterprise { 'Ent' } - EnterpriseN { 'EntN' } + EnterpriseN { 'EntN'} + EnterpriseS { 'Ent_LTSC' } + IoTEnterpriseS { 'IoT_Ent_LTSC' } Education { 'Edu' } EducationN { 'EduN' } ProfessionalWorkstation { 'Pro_Wks' }