mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
Merge pull request #84 from JonasKloseBW/2410.1
Add support for Windows Server in 2410.1 branch
This commit is contained in:
@@ -9,6 +9,8 @@ REM Install Windows Malicious Software Removal Tool
|
||||
REM Install OneDrive Per Machine
|
||||
REM Install Edge Stable
|
||||
REM Winget Win32 Apps
|
||||
REM START Batch variables placeholder
|
||||
REM END Batch variables placeholder
|
||||
REM Add additional apps below here
|
||||
REM Contoso App (Example)
|
||||
REM msiexec /i d:\Contoso\setup.msi /qn /norestart
|
||||
|
||||
+131
-14
@@ -169,6 +169,9 @@ Make of the device to download drivers. Accepted values are: 'Microsoft', 'Dell'
|
||||
.PARAMETER Model
|
||||
Model of the device to download drivers. This is required if Make is set.
|
||||
|
||||
.PARAMETER AppsScriptVariables
|
||||
When passed a hashtable, the script will alter the $FFUDevelopmentPath\Apps\InstallAppsandSysprep.cmd file to set variables with the hashtable keys as variable names and the hashtable values their content.
|
||||
|
||||
.EXAMPLE
|
||||
Command line for most people who want to download the latest Windows 11 Pro x64 media in English (US) with the latest Windows Cumulative Update, .NET Framework, Defender platform and definition updates, Edge, OneDrive, and Office/M365 Apps. It will also copy drivers to the FFU. This can take about 40 minutes to create the FFU due to the time it takes to download and install the updates.
|
||||
.\BuildFFUVM.ps1 -WindowsSKU 'Pro' -Installapps $true -InstallOffice $true -InstallDrivers $true -VMSwitchName 'Name of your VM Switch in Hyper-V' -VMHostIPAddress 'Your IP Address' -CreateCaptureMedia $true -CreateDeploymentMedia $true -BuildUSBDrive $true -UpdateLatestCU $true -UpdateLatestNet $true -UpdateLatestDefender $true -UpdateEdge $true -UpdateOneDrive $true -verbose
|
||||
@@ -204,11 +207,16 @@ param(
|
||||
[Parameter(Mandatory = $false, Position = 0)]
|
||||
[ValidateScript({ Test-Path $_ })]
|
||||
[string]$ISOPath,
|
||||
[ValidateSet('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N')]
|
||||
[ValidateScript({
|
||||
$allowedSKUs = @('Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)')
|
||||
if ($allowedSKUs -contains $_) { $true } else { throw "Invalid WindowsSKU value. Allowed values: $($allowedSKUs -join ', ')" }
|
||||
return $true
|
||||
})]
|
||||
[string]$WindowsSKU = 'Pro',
|
||||
[ValidateScript({ Test-Path $_ })]
|
||||
[string]$FFUDevelopmentPath = $PSScriptRoot,
|
||||
[bool]$InstallApps,
|
||||
[hashtable]$AppsScriptVariables,
|
||||
[bool]$InstallOffice,
|
||||
[ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')]
|
||||
[string]$Make,
|
||||
@@ -270,7 +278,7 @@ param(
|
||||
[string]$ProductKey,
|
||||
[bool]$BuildUSBDrive,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[ValidateSet(10, 11)]
|
||||
[ValidateSet(10, 11, 2016, 2019, 2022, 2025)]
|
||||
[int]$WindowsRelease = 11,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$WindowsVersion = '23h2',
|
||||
@@ -1169,10 +1177,21 @@ function Get-DellDrivers {
|
||||
[string]$Model,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateSet("x64", "x86", "ARM64")]
|
||||
[string]$WindowsArch
|
||||
[string]$WindowsArch,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$WindowsRelease
|
||||
)
|
||||
|
||||
if ($WindowsRelease -le 11) {
|
||||
$catalogUrl = "http://downloads.dell.com/catalog/CatalogPC.cab"
|
||||
$DellCabFile = "$DriversFolder\CatalogPC.cab"
|
||||
$DellCatalogXML = "$DriversFolder\CatalogPC.XML"
|
||||
} else {
|
||||
$catalogUrl = "https://downloads.dell.com/catalog/Catalog.cab"
|
||||
$DellCabFile = "$DriversFolder\Catalog.cab"
|
||||
$DellCatalogXML = "$DriversFolder\Catalog.xml"
|
||||
}
|
||||
|
||||
if (-not (Test-Url -Url $catalogUrl)) {
|
||||
WriteLog "Dell Catalog cab URL is not accessible: $catalogUrl Exiting"
|
||||
if ($VerbosePreference -ne 'Continue') {
|
||||
@@ -1192,12 +1211,10 @@ function Get-DellDrivers {
|
||||
New-Item -Path $DriversFolder -ItemType Directory -Force | Out-Null
|
||||
WriteLog "Dell Drivers folder created"
|
||||
|
||||
$DellCabFile = "$DriversFolder\CatalogPC.cab"
|
||||
WriteLog "Downloading Dell Catalog cab file: $catalogUrl to $DellCabFile"
|
||||
Start-BitsTransferWithRetry -Source $catalogUrl -Destination $DellCabFile
|
||||
WriteLog "Dell Catalog cab file downloaded"
|
||||
|
||||
$DellCatalogXML = "$DriversFolder\CatalogPC.XML"
|
||||
WriteLog "Extracting Dell Catalog cab file to $DellCatalogXML"
|
||||
Invoke-Process -FilePath Expand.exe -ArgumentList "$DellCabFile $DellCatalogXML"
|
||||
WriteLog "Dell Catalog cab file extracted"
|
||||
@@ -1211,7 +1228,19 @@ function Get-DellDrivers {
|
||||
$models = $component.SupportedSystems.Brand.Model
|
||||
foreach ($item in $models) {
|
||||
if ($item.Display.'#cdata-section' -match $Model) {
|
||||
|
||||
if ($WindowsRelease -le 11) {
|
||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { $_.osArch -eq $WindowsArch }
|
||||
} elseif ($WindowsRelease -eq 2016) {
|
||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W14") }
|
||||
} elseif ($WindowsRelease -eq 2019) {
|
||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W19") }
|
||||
} elseif ($WindowsRelease -eq 2022) {
|
||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") }
|
||||
} else {
|
||||
$validOS = $component.SupportedOperatingSystems.OperatingSystem | Where-Object { ($_.osArch -eq $WindowsArch) -and ($_.osCode -match "W22") }
|
||||
}
|
||||
|
||||
if ($validOS) {
|
||||
$driverPath = $component.path
|
||||
$downloadUrl = $baseLocation + $driverPath
|
||||
@@ -1590,8 +1619,10 @@ function Get-WindowsESD {
|
||||
$cabFileUrl = if ($WindowsRelease -eq 10) {
|
||||
'https://go.microsoft.com/fwlink/?LinkId=841361'
|
||||
}
|
||||
else {
|
||||
elseif ($WindowsRelease -eq 11) {
|
||||
'https://go.microsoft.com/fwlink/?LinkId=2156292'
|
||||
} else {
|
||||
throw "Can't download Windows Server. Please download the Windows setup media from your subscription homepage."
|
||||
}
|
||||
|
||||
# Download cab file
|
||||
@@ -2235,16 +2266,21 @@ function Get-KBLink {
|
||||
}
|
||||
function Get-LatestWindowsKB {
|
||||
param (
|
||||
[ValidateSet(10, 11)]
|
||||
[int]$WindowsRelease
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet(10, 11, 2016, 2019, 2022, 2025)]
|
||||
[int]$WindowsRelease,
|
||||
[Parameter(Mandatory)]
|
||||
[string]$WindowsVersion
|
||||
)
|
||||
|
||||
# Define the URL of the update history page based on the Windows release
|
||||
if ($WindowsRelease -eq 11) {
|
||||
$updateHistoryUrl = 'https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information'
|
||||
}
|
||||
else {
|
||||
elseif ($WindowsRelease -eq 10) {
|
||||
$updateHistoryUrl = 'https://learn.microsoft.com/en-us/windows/release-health/release-information'
|
||||
} else {
|
||||
$updateHistoryUrl = 'https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info'
|
||||
}
|
||||
|
||||
# Use Invoke-WebRequest to fetch the content of the page
|
||||
@@ -2254,7 +2290,11 @@ function Get-LatestWindowsKB {
|
||||
$VerbosePreference = $OriginalVerbosePreference
|
||||
|
||||
# Use a regular expression to find the KB article number
|
||||
$kbArticleRegex = 'KB\d+'
|
||||
if ($WindowsRelease -le 11) {
|
||||
$kbArticleRegex = "(?:Version $WindowsRelease \(OS build d+\)(?!(KB)).)*?KB\d+"
|
||||
} else {
|
||||
$kbArticleRegex = "(?:Windows Server $WindowsRelease \(OS build d+\)(?!(KB)).)*?KB\d+"
|
||||
}
|
||||
$kbArticle = [regex]::Match($response.Content, $kbArticleRegex).Value
|
||||
|
||||
return $kbArticle
|
||||
@@ -2375,9 +2415,13 @@ function Get-WimIndex {
|
||||
If ($ISOPath) {
|
||||
$wimindex = switch ($WindowsSKU) {
|
||||
'Home' { 1 }
|
||||
'Standard' { 1 }
|
||||
'Home_N' { 2 }
|
||||
'Standard (Desktop Experience)' { 1 }
|
||||
'Home_SL' { 3 }
|
||||
'Datacenter' { 3 }
|
||||
'EDU' { 4 }
|
||||
'Datacenter (Desktop Experience)' { 4 }
|
||||
'EDU_N' { 5 }
|
||||
'Pro' { 6 }
|
||||
'Pro_N' { 7 }
|
||||
@@ -2408,8 +2452,13 @@ function Get-Index {
|
||||
|
||||
# Get the ImageName of ImageIndex 1 if an ISO was specified, else use ImageIndex 4 - this is usually Home or Education SKU on ESD MCT media
|
||||
if($ISOPath){
|
||||
if ($WindowsSKU -notmatch "Standard|Datacenter") {
|
||||
$imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1
|
||||
$WindowsImage = $imageIndex.ImageName.Substring(0, 10)
|
||||
} else {
|
||||
$imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1
|
||||
$WindowsImage = $imageIndex.ImageName.Substring(0, 19)
|
||||
}
|
||||
}
|
||||
else{
|
||||
$imageIndex = $imageIndexes | Where-Object ImageIndex -eq 4
|
||||
@@ -2427,8 +2476,8 @@ function Get-Index {
|
||||
return $matchingImageIndex.ImageIndex
|
||||
}
|
||||
else {
|
||||
# Look for either the number 10 or 11 in the ImageName
|
||||
$relevantImageIndexes = $imageIndexes | Where-Object { ($_.ImageName -like "*10*") -or ($_.ImageName -like "*11*") }
|
||||
# Look for the numbers 10, 11, 2016, 2019, 2022+ in the ImageName
|
||||
$relevantImageIndexes = $imageIndexes | Where-Object { ($_.ImageName -match "(10|11|2016|2019|202\d)") }
|
||||
|
||||
while ($true) {
|
||||
# Present list of ImageNames to the end user if no matching ImageIndex is found
|
||||
@@ -2540,7 +2589,7 @@ function New-OSPartition {
|
||||
if ((Get-CimInstance Win32_OperatingSystem).Caption -match "Server") {
|
||||
WriteLog (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\")
|
||||
}
|
||||
if ($CompactOS) {
|
||||
elseif ($CompactOS) {
|
||||
WriteLog '$CompactOS is set to true, using -Compact switch to apply the WIM file to the OS partition.'
|
||||
WriteLog (Expand-WindowsImage -ImagePath $WimPath -Index $WimIndex -ApplyPath "$($osPartition.DriveLetter):\" -Compact)
|
||||
}
|
||||
@@ -3068,15 +3117,26 @@ Function Get-WindowsVersionInfo {
|
||||
Enterprise { 'Ent' }
|
||||
Education { 'Edu' }
|
||||
ProfessionalWorkstation { 'Pro_Wks' }
|
||||
ServerStandard { 'Srv_Std' }
|
||||
ServerDatacenter { 'Srv_Dtc' }
|
||||
}
|
||||
WriteLog "Windows SKU Modified to: $SKU"
|
||||
|
||||
if ($SKU -notmatch "Srv") {
|
||||
if ($CurrentBuild -ge 22000) {
|
||||
$Name = 'Win11'
|
||||
}
|
||||
else {
|
||||
$Name = 'Win10'
|
||||
}
|
||||
} else {
|
||||
$Name = switch ($CurrentBuild) {
|
||||
26100 { '2025' }
|
||||
20348 { '2022' }
|
||||
17763 { '2019' }
|
||||
Default { $DisplayVersion }
|
||||
}
|
||||
}
|
||||
|
||||
WriteLog "Unloading registry"
|
||||
Invoke-Process reg "unload HKLM\FFU"
|
||||
@@ -3748,8 +3808,20 @@ if (($VMHostIPAddress) -and ($VMSwitchName)){
|
||||
throw "IP address for -VMSwitchName $VMSwitchName not found. Please check the -VMSwitchName parameter and try again."
|
||||
}
|
||||
if ($VMSwitchIPAddress -ne $VMHostIPAddress) {
|
||||
try {
|
||||
# Bypass the check for systems that could have a Hyper-V NAT switch
|
||||
$null = Get-NetNat -ErrorAction Stop
|
||||
$NetNat = @(Get-NetNat -ErrorAction Stop);
|
||||
} catch {
|
||||
throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again."
|
||||
}
|
||||
if ($NetNat.Count -gt 0) {
|
||||
WriteLog "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress!"
|
||||
WriteLog "NAT setup detected, remember to configure NATing if the FFU image can't be captured to the network share on the host."
|
||||
} else {
|
||||
throw "IP address for -VMSwitchName $VMSwitchName is $VMSwitchIPAddress, which does not match the -VMHostIPAddress $VMHostIPAddress. Please check the -VMHostIPAddress parameter and try again."
|
||||
}
|
||||
}
|
||||
WriteLog '-VMSwitchName and -VMHostIPAddress validation complete'
|
||||
}
|
||||
|
||||
@@ -3818,7 +3890,7 @@ if (($make -and $model) -and ($installdrivers -or $copydrivers)) {
|
||||
if ($make -eq 'Dell'){
|
||||
WriteLog 'Getting Dell drivers'
|
||||
#Dell mixes Win10 and 11 drivers, hence no WindowsRelease parameter
|
||||
Get-DellDrivers -Model $Model -WindowsArch $WindowsArch
|
||||
Get-DellDrivers -Model $Model -WindowsArch $WindowsArch -WindowsRelease $WindowsRelease
|
||||
WriteLog 'Getting Dell drivers completed successfully'
|
||||
}
|
||||
}
|
||||
@@ -4016,6 +4088,29 @@ if ($InstallApps) {
|
||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
||||
WriteLog "Update complete"
|
||||
}
|
||||
|
||||
#Modify InstallAppsandSysprep.cmd to remove old script variables
|
||||
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||
$StartIndex = $CmdContent.IndexOf("REM START Batch variables placeholder")
|
||||
$EndIndex = $CmdContent.IndexOf("REM END Batch variables placeholder")
|
||||
if (($StartIndex + 1) -lt $EndIndex) {
|
||||
for ($i = ($StartIndex + 1); $i -lt $EndIndex; $i++) {
|
||||
$CmdContent[$i] = $null
|
||||
}
|
||||
}
|
||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $CmdContent
|
||||
|
||||
if ($AppsScriptVariables) {
|
||||
#Modify InstallAppsandSysprep.cmd to add the script variables
|
||||
$CmdContent = [System.Collections.ArrayList](Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd")
|
||||
$ScriptIndex = $CmdContent.IndexOf("REM START Batch variables placeholder") + 1
|
||||
foreach ($VariableKey in $AppsScriptVariables.Keys) {
|
||||
$CmdContent.Insert($ScriptIndex, ("set {0}={1}" -f $VariableKey, $AppsScriptVariables[$VariableKey]))
|
||||
$ScriptIndex++
|
||||
}
|
||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $CmdContent
|
||||
}
|
||||
|
||||
#Create Apps ISO
|
||||
WriteLog "Creating $AppsISO file"
|
||||
New-AppsISO
|
||||
@@ -4064,7 +4159,15 @@ try {
|
||||
#The Windows release info page is updated later than the MU Catalog
|
||||
if ($UpdateLatestCU -and -not $UpdatePreviewCU) {
|
||||
Writelog "`$UpdateLatestCU is set to true, checking for latest CU"
|
||||
if ($WindowsRelease -le 11) {
|
||||
$Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
|
||||
} elseif ($WindowsRelease -eq 2022) {
|
||||
$Name = """Cumulative Update for Microsoft server operating system, version $WindowsVersion for $WindowsArch"""
|
||||
} elseif ($WindowsRelease -lt 2022) {
|
||||
$Name = """Cumulative update for Windows 10 Version $WindowsVersion for $WindowsArch"""
|
||||
} else {
|
||||
$Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch"""
|
||||
}
|
||||
#Check if $KBPath exists, if not, create it
|
||||
If (-not (Test-Path -Path $KBPath)) {
|
||||
WriteLog "Creating $KBPath"
|
||||
@@ -4079,7 +4182,15 @@ try {
|
||||
#will take Precendence over $UpdateLastestCU if both were set to $true
|
||||
if ($UpdatePreviewCU) {
|
||||
Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU"
|
||||
if ($WindowsRelease -le 11) {
|
||||
$Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
|
||||
} elseif ($WindowsRelease -eq 2022) {
|
||||
$Name = """Cumulative Update Preview for Microsoft server operating system, version $WindowsVersion for $WindowsArch"""
|
||||
} elseif ($WindowsRelease -lt 2022) {
|
||||
$Name = """Cumulative update Preview for Windows 10 Version $WindowsVersion for $WindowsArch"""
|
||||
} else {
|
||||
$Name = """Cumulative update Preview for Windows 11 Version $WindowsVersion for $WindowsArch"""
|
||||
}
|
||||
#Check if $KBPath exists, if not, create it
|
||||
If (-not (Test-Path -Path $KBPath)) {
|
||||
WriteLog "Creating $KBPath"
|
||||
@@ -4093,7 +4204,13 @@ try {
|
||||
#Update Latest .NET Framework
|
||||
if ($UpdateLatestNet) {
|
||||
Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework"
|
||||
if ($WindowsRelease -le 11) {
|
||||
$Name = "Cumulative update for .net framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview"
|
||||
} elseif ($WindowsRelease -le 2022) {
|
||||
$Name = "Cumulative update for .net framework windows 10 $WindowsVersion for $WindowsArch -preview"
|
||||
} else {
|
||||
$Name = "Cumulative update for .net framework windows 11 $WindowsVersion for $WindowsArch -preview"
|
||||
}
|
||||
#Check if $KBPath exists, if not, create it
|
||||
If (-not (Test-Path -Path $KBPath)) {
|
||||
WriteLog "Creating $KBPath"
|
||||
|
||||
@@ -12,6 +12,7 @@ reg load "HKLM\FFU" $Software
|
||||
$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID'
|
||||
[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild'
|
||||
$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion'
|
||||
$InstallationType = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'InstallationType'
|
||||
$BuildDate = Get-Date -uformat %b%Y
|
||||
|
||||
$SKU = switch ($SKU) {
|
||||
@@ -28,13 +29,27 @@ $SKU = switch ($SKU) {
|
||||
EducationN { 'EduN'}
|
||||
ProfessionalWorkstation { 'Pro_Wks' }
|
||||
ProfessionalWorkstationN { 'Pro_WksN' }
|
||||
ServerStandard { 'Srv_Std' }
|
||||
ServerDatacenter { 'Srv_Dtc' }
|
||||
}
|
||||
|
||||
if($CurrentBuild -ge 22000){
|
||||
if ($InstallationType -eq "Client") {
|
||||
if ($CurrentBuild -ge 22000) {
|
||||
$Name = 'Win11'
|
||||
}
|
||||
else{
|
||||
}
|
||||
else {
|
||||
$Name = 'Win10'
|
||||
}
|
||||
} else {
|
||||
$Name = switch ($CurrentBuild) {
|
||||
26100 { '2025' }
|
||||
20348 { '2022' }
|
||||
17763 { '2019' }
|
||||
Default { $DisplayVersion }
|
||||
}
|
||||
if ($InstallationType -eq "Server Core") {
|
||||
$SKU += "_Core"
|
||||
}
|
||||
}
|
||||
|
||||
#If Office is installed, modify the file name of the FFU
|
||||
|
||||
Reference in New Issue
Block a user