mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-14 02:09:35 -06:00
+516
-116
@@ -15,6 +15,9 @@ When set to $true, will allow the use of media identified as External Hard Disk
|
|||||||
.PARAMETER AllowVHDXCaching
|
.PARAMETER AllowVHDXCaching
|
||||||
When set to $true, will cache the VHDX file to the $FFUDevelopmentPath\VHDXCache folder and create a config json file that will keep track of the Windows build information, the updates installed, and the logical sector byte size information. Default is $false.
|
When set to $true, will cache the VHDX file to the $FFUDevelopmentPath\VHDXCache folder and create a config json file that will keep track of the Windows build information, the updates installed, and the logical sector byte size information. Default is $false.
|
||||||
|
|
||||||
|
.PARAMETER AppListPath
|
||||||
|
Path to a JSON file containing a list of applications to install using WinGet. Default is $FFUDevelopmentPath\Apps\AppList.json.
|
||||||
|
|
||||||
.PARAMETER AppsScriptVariables
|
.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.
|
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.
|
||||||
|
|
||||||
@@ -117,6 +120,9 @@ When set to $true, will optimize the FFU file. Default is $true.
|
|||||||
.PARAMETER OptionalFeatures
|
.PARAMETER OptionalFeatures
|
||||||
Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP).
|
Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP).
|
||||||
|
|
||||||
|
.PARAMETER PEDriversFolder
|
||||||
|
Path to the folder containing drivers to be injected into the WinPE deployment media. Default is $FFUDevelopmentPath\PEDrivers.
|
||||||
|
|
||||||
.PARAMETER Processors
|
.PARAMETER Processors
|
||||||
Number of virtual processors for the virtual machine. Recommended to use at least 4.
|
Number of virtual processors for the virtual machine. Recommended to use at least 4.
|
||||||
|
|
||||||
@@ -132,6 +138,9 @@ When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU fol
|
|||||||
.PARAMETER ShareName
|
.PARAMETER ShareName
|
||||||
Name of the shared folder for FFU capture. The default is FFUCaptureShare. This share will be created with rights for the user account. When finished, the share will be removed.
|
Name of the shared folder for FFU capture. The default is FFUCaptureShare. This share will be created with rights for the user account. When finished, the share will be removed.
|
||||||
|
|
||||||
|
.PARAMETER UpdateADK
|
||||||
|
When set to $true, the script will check for and install the latest Windows ADK and WinPE add-on if they are not already installed or up-to-date. Default is $true.
|
||||||
|
|
||||||
.PARAMETER UpdateEdge
|
.PARAMETER UpdateEdge
|
||||||
When set to $true, will download and install the latest Microsoft Edge for Windows 10/11. Default is $false.
|
When set to $true, will download and install the latest Microsoft Edge for Windows 10/11. Default is $false.
|
||||||
|
|
||||||
@@ -141,12 +150,12 @@ When set to $true, will download and install the latest cumulative update for Wi
|
|||||||
.PARAMETER UpdatePreviewCU
|
.PARAMETER UpdatePreviewCU
|
||||||
When set to $true, will download and install the latest Preview cumulative update for Windows 10/11. Default is $false.
|
When set to $true, will download and install the latest Preview cumulative update for Windows 10/11. Default is $false.
|
||||||
|
|
||||||
.PARAMETER UpdateLatestNet
|
|
||||||
When set to $true, will download and install the latest .NET Framework for Windows 10/11. Default is $false.
|
|
||||||
|
|
||||||
.PARAMETER UpdateLatestDefender
|
.PARAMETER UpdateLatestDefender
|
||||||
When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false.
|
When set to $true, will download and install the latest Windows Defender definitions and Defender platform update. Default is $false.
|
||||||
|
|
||||||
|
.PARAMETER UpdateLatestMicrocode
|
||||||
|
When set to $true, will download and install the latest microcode updates for applicable Windows releases (e.g., Windows Server 2016/2019, Windows 10 LTSC 2016/2019) into the FFU. Default is $false.
|
||||||
|
|
||||||
.PARAMETER UpdateLatestMSRT
|
.PARAMETER UpdateLatestMSRT
|
||||||
When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false.
|
When set to $true, will download and install the latest Windows Malicious Software Removal Tool. Default is $false.
|
||||||
|
|
||||||
@@ -224,13 +233,36 @@ param(
|
|||||||
[Parameter(Mandatory = $false, Position = 0)]
|
[Parameter(Mandatory = $false, Position = 0)]
|
||||||
[ValidateScript({ Test-Path $_ })]
|
[ValidateScript({ Test-Path $_ })]
|
||||||
[string]$ISOPath,
|
[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 2016 LTSB',
|
||||||
|
'Enterprise N 2016 LTSB',
|
||||||
|
'Enterprise LTSC',
|
||||||
|
'Enterprise N LTSC',
|
||||||
|
'IoT Enterprise LTSC',
|
||||||
|
'IoT Enterprise N LTSC',
|
||||||
|
'Standard',
|
||||||
|
'Standard (Desktop Experience)',
|
||||||
|
'Datacenter',
|
||||||
|
'Datacenter (Desktop Experience)'
|
||||||
|
)]
|
||||||
[string]$WindowsSKU = 'Pro',
|
[string]$WindowsSKU = 'Pro',
|
||||||
[ValidateScript({ Test-Path $_ })]
|
[ValidateScript({ Test-Path $_ })]
|
||||||
[string]$FFUDevelopmentPath = $PSScriptRoot,
|
[string]$FFUDevelopmentPath = $PSScriptRoot,
|
||||||
|
|
||||||
[bool]$InstallApps,
|
[bool]$InstallApps,
|
||||||
|
[string]$AppListPath,
|
||||||
[hashtable]$AppsScriptVariables,
|
[hashtable]$AppsScriptVariables,
|
||||||
[bool]$InstallOffice,
|
[bool]$InstallOffice,
|
||||||
[ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')]
|
[ValidateSet('Microsoft', 'Dell', 'HP', 'Lenovo')]
|
||||||
@@ -284,7 +316,7 @@ param(
|
|||||||
[string]$ProductKey,
|
[string]$ProductKey,
|
||||||
[bool]$BuildUSBDrive,
|
[bool]$BuildUSBDrive,
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[ValidateSet(10, 11, 2016, 2019, 2022, 2025)]
|
[ValidateSet(10, 11, 2016, 2019, 2021, 2022, 2024, 2025)]
|
||||||
[int]$WindowsRelease = 11,
|
[int]$WindowsRelease = 11,
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[string]$WindowsVersion = '24h2',
|
[string]$WindowsVersion = '24h2',
|
||||||
@@ -321,6 +353,7 @@ param(
|
|||||||
[bool]$RemoveFFU,
|
[bool]$RemoveFFU,
|
||||||
[bool]$UpdateLatestCU,
|
[bool]$UpdateLatestCU,
|
||||||
[bool]$UpdatePreviewCU,
|
[bool]$UpdatePreviewCU,
|
||||||
|
[bool]$UpdateLatestMicrocode,
|
||||||
[bool]$UpdateLatestNet,
|
[bool]$UpdateLatestNet,
|
||||||
[bool]$UpdateLatestDefender,
|
[bool]$UpdateLatestDefender,
|
||||||
[bool]$UpdateLatestMSRT,
|
[bool]$UpdateLatestMSRT,
|
||||||
@@ -359,9 +392,10 @@ param(
|
|||||||
[ValidateScript({ $_ -eq $null -or (Test-Path $_) })]
|
[ValidateScript({ $_ -eq $null -or (Test-Path $_) })]
|
||||||
[string]$ConfigFile,
|
[string]$ConfigFile,
|
||||||
[Parameter(Mandatory = $false)]
|
[Parameter(Mandatory = $false)]
|
||||||
[string]$ExportConfigFile
|
[string]$ExportConfigFile,
|
||||||
|
[bool]$UpdateADK = $true
|
||||||
)
|
)
|
||||||
$version = '2412.3'
|
$version = '2505.1'
|
||||||
|
|
||||||
# If a config file is specified and it exists, load it
|
# If a config file is specified and it exists, load it
|
||||||
if ($ConfigFile -and (Test-Path -Path $ConfigFile)) {
|
if ($ConfigFile -and (Test-Path -Path $ConfigFile)) {
|
||||||
@@ -383,12 +417,12 @@ if ($ConfigFile -and (Test-Path -Path $ConfigFile)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# If this is the Headers parameter, convert PSCustomObject to hashtable
|
# If this is the Headers parameter, convert PSCustomObject to hashtable
|
||||||
if ($key -eq 'Headers' -and $value -is [System.Management.Automation.PSCustomObject]) {
|
if ((($key -eq 'Headers') -or ($key -eq 'AppsScriptVariables')) -and ($value -is [System.Management.Automation.PSCustomObject])) {
|
||||||
$headers = [hashtable]::new()
|
$hashtableValue = [hashtable]::new()
|
||||||
foreach ($prop in $value.psobject.Properties) {
|
foreach ($prop in $value.psobject.Properties) {
|
||||||
$headers[$prop.Name] = $prop.Value
|
$hashtableValue[$prop.Name] = $prop.Value
|
||||||
}
|
}
|
||||||
$value = $headers
|
$value = $hashtableValue
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if this key matches a parameter in the script
|
# Check if this key matches a parameter in the script
|
||||||
@@ -400,6 +434,53 @@ if ($ConfigFile -and (Test-Path -Path $ConfigFile)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Validate that the selected Windows SKU is compatible with the chosen Windows release and ensure an ISO is provided for unsupported releases
|
||||||
|
$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 2016 LTSB',
|
||||||
|
'Enterprise N 2016 LTSB',
|
||||||
|
'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 definition for vhdx cache
|
||||||
class VhdxCacheUpdateItem {
|
class VhdxCacheUpdateItem {
|
||||||
[string]$Name
|
[string]$Name
|
||||||
@@ -451,6 +532,7 @@ if (-not $VHDXPath) { $VHDXPath = "$VMPath\$VMName.vhdx" }
|
|||||||
if (-not $FFUCaptureLocation) { $FFUCaptureLocation = "$FFUDevelopmentPath\FFU" }
|
if (-not $FFUCaptureLocation) { $FFUCaptureLocation = "$FFUDevelopmentPath\FFU" }
|
||||||
if (-not $LogFile) { $LogFile = "$FFUDevelopmentPath\FFUDevelopment.log" }
|
if (-not $LogFile) { $LogFile = "$FFUDevelopmentPath\FFUDevelopment.log" }
|
||||||
if (-not $KBPath) { $KBPath = "$FFUDevelopmentPath\KB" }
|
if (-not $KBPath) { $KBPath = "$FFUDevelopmentPath\KB" }
|
||||||
|
if (-not $MicrocodePath) { $MicrocodePath = "$KBPath\Microcode" }
|
||||||
if (-not $DefenderPath) { $DefenderPath = "$AppsPath\Defender" }
|
if (-not $DefenderPath) { $DefenderPath = "$AppsPath\Defender" }
|
||||||
if (-not $MSRTPath) { $MSRTPath = "$AppsPath\MSRT" }
|
if (-not $MSRTPath) { $MSRTPath = "$AppsPath\MSRT" }
|
||||||
if (-not $OneDrivePath) { $OneDrivePath = "$AppsPath\OneDrive" }
|
if (-not $OneDrivePath) { $OneDrivePath = "$AppsPath\OneDrive" }
|
||||||
@@ -461,7 +543,7 @@ if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" }
|
|||||||
if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" }
|
if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" }
|
||||||
if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" }
|
if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" }
|
||||||
if (-not $VHDXCacheFolder) { $VHDXCacheFolder = "$FFUDevelopmentPath\VHDXCache" }
|
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'){
|
if ($installationType -eq 'Server'){
|
||||||
#Map $WindowsRelease to $WindowsVersion for Windows Server
|
#Map $WindowsRelease to $WindowsVersion for Windows Server
|
||||||
switch ($WindowsRelease) {
|
switch ($WindowsRelease) {
|
||||||
@@ -471,6 +553,17 @@ if ($installationType -eq 'Server'){
|
|||||||
2025 { $WindowsVersion = '24H2' }
|
2025 { $WindowsVersion = '24H2' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (-not $AppListPath) { $AppListPath = "$AppsPath\AppList.json" }
|
||||||
|
|
||||||
|
if ($WindowsSKU -like "*LTS*") {
|
||||||
|
switch ($WindowsRelease) {
|
||||||
|
2016 { $WindowsVersion = '1607' }
|
||||||
|
2019 { $WindowsVersion = '1809' }
|
||||||
|
2021 { $WindowsVersion = '21H2' }
|
||||||
|
2024 { $WindowsVersion = '24H2' }
|
||||||
|
}
|
||||||
|
$isLTSC = $true
|
||||||
|
}
|
||||||
|
|
||||||
#FUNCTIONS
|
#FUNCTIONS
|
||||||
function WriteLog($LogText) {
|
function WriteLog($LogText) {
|
||||||
@@ -1732,6 +1825,8 @@ function Confirm-ADKVersionIsLatest {
|
|||||||
|
|
||||||
function Get-ADK {
|
function Get-ADK {
|
||||||
# Check if latest ADK and WinPE add-on are installed
|
# Check if latest ADK and WinPE add-on are installed
|
||||||
|
if ($UpdateADK) {
|
||||||
|
WriteLog "Checking if latest ADK and WinPE add-on are installed"
|
||||||
$latestADKInstalled = Confirm-ADKVersionIsLatest -ADKOption "Windows ADK"
|
$latestADKInstalled = Confirm-ADKVersionIsLatest -ADKOption "Windows ADK"
|
||||||
$latestWinPEInstalled = Confirm-ADKVersionIsLatest -ADKOption "WinPE add-on"
|
$latestWinPEInstalled = Confirm-ADKVersionIsLatest -ADKOption "WinPE add-on"
|
||||||
|
|
||||||
@@ -1745,6 +1840,7 @@ function Get-ADK {
|
|||||||
Uninstall-ADK -ADKOption "WinPE add-on"
|
Uninstall-ADK -ADKOption "WinPE add-on"
|
||||||
Install-ADK -ADKOption "WinPE add-on"
|
Install-ADK -ADKOption "WinPE add-on"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Define registry path
|
# Define registry path
|
||||||
$adkPathKey = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots"
|
$adkPathKey = "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows Kits\Installed Roots"
|
||||||
@@ -1811,7 +1907,7 @@ function Get-WindowsESD {
|
|||||||
elseif ($WindowsRelease -eq 11) {
|
elseif ($WindowsRelease -eq 11) {
|
||||||
'https://go.microsoft.com/fwlink/?LinkId=2156292'
|
'https://go.microsoft.com/fwlink/?LinkId=2156292'
|
||||||
} else {
|
} 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
|
# Download cab file
|
||||||
@@ -2429,23 +2525,30 @@ function Get-KBLink {
|
|||||||
$VerbosePreference = 'SilentlyContinue'
|
$VerbosePreference = 'SilentlyContinue'
|
||||||
$results = Invoke-WebRequest -Uri "http://www.catalog.update.microsoft.com/Search.aspx?q=$Name" -Headers $Headers -UserAgent $UserAgent
|
$results = Invoke-WebRequest -Uri "http://www.catalog.update.microsoft.com/Search.aspx?q=$Name" -Headers $Headers -UserAgent $UserAgent
|
||||||
$VerbosePreference = $OriginalVerbosePreference
|
$VerbosePreference = $OriginalVerbosePreference
|
||||||
|
|
||||||
|
# Extract the first KB article ID from the HTML content and store it globally
|
||||||
|
# Edge and Defender do not have KB article IDs
|
||||||
|
if ($Name -notmatch 'Defender|Edge') {
|
||||||
|
if ($results.Content -match '>\s*([^\(<]+)\(KB(\d+)\)\s*<') {
|
||||||
|
$kbArticleID = "KB$($matches[2])"
|
||||||
|
$global:LastKBArticleID = $kbArticleID
|
||||||
|
WriteLog "Found KB article ID: $kbArticleID"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "No KB article ID found in search results."
|
||||||
|
$global:LastKBArticleID = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$kbids = $results.InputFields |
|
$kbids = $results.InputFields |
|
||||||
Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } |
|
Where-Object { $_.type -eq 'Button' -and $_.Value -eq 'Download' } |
|
||||||
Select-Object -ExpandProperty ID
|
Select-Object -ExpandProperty ID
|
||||||
|
|
||||||
# Write-Verbose -Message "$kbids"
|
|
||||||
|
|
||||||
if (-not $kbids) {
|
if (-not $kbids) {
|
||||||
Write-Warning -Message "No results found for $Name"
|
Write-Warning -Message "No results found for $Name"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
# $guids = $results.Links |
|
|
||||||
# Where-Object ID -match '_link' |
|
|
||||||
# Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } |
|
|
||||||
# ForEach-Object { $_.id.replace('_link', '') } |
|
|
||||||
# Where-Object { $_ -in $kbids }
|
|
||||||
|
|
||||||
$guids = $results.Links |
|
$guids = $results.Links |
|
||||||
Where-Object ID -match '_link' |
|
Where-Object ID -match '_link' |
|
||||||
Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } |
|
Where-Object { $_.OuterHTML -match ( "(?=.*" + ( $Filter -join ")(?=.*" ) + ")" ) } |
|
||||||
@@ -2522,41 +2625,63 @@ function Save-KB {
|
|||||||
[string[]]$Name,
|
[string[]]$Name,
|
||||||
[string]$Path
|
[string]$Path
|
||||||
)
|
)
|
||||||
|
|
||||||
if ($WindowsArch -eq 'x64') {
|
|
||||||
[array]$WindowsArch = @("x64", "amd64")
|
|
||||||
}
|
|
||||||
foreach ($kb in $name) {
|
foreach ($kb in $name) {
|
||||||
$links = Get-KBLink -Name $kb
|
$links = Get-KBLink -Name $kb
|
||||||
foreach ($link in $links) {
|
foreach ($link in $links) {
|
||||||
if (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) {
|
# if (!($link -match 'x64' -or $link -match 'amd64' -or $link -match 'x86' -or $link -match 'arm64')) {
|
||||||
WriteLog "No architecture found in $link, skipping"
|
# WriteLog "No architecture found in $link, skipping"
|
||||||
continue
|
# continue
|
||||||
}
|
# }
|
||||||
|
|
||||||
if ($link -match 'x64' -or $link -match 'amd64') {
|
if ($link -match 'x64' -or $link -match 'amd64') {
|
||||||
if($WindowsArch -is [array]) {
|
if ($WindowsArch -eq 'x64') {
|
||||||
if ($link -match $WindowsArch[0] -or $link -match $WindowsArch[1]) {
|
Writelog "Downloading $link for $WindowsArch to $Path"
|
||||||
Writelog "Downloading $Link for $WindowsArch to $Path"
|
|
||||||
Start-BitsTransferWithRetry -Source $link -Destination $Path
|
Start-BitsTransferWithRetry -Source $link -Destination $Path
|
||||||
$fileName = ($link -split '/')[-1]
|
$fileName = ($link -split '/')[-1]
|
||||||
Writelog "Returning $fileName"
|
Writelog "Returning $fileName"
|
||||||
#With Windows 11 24H2 and Checkpoint CUs, there are multiple files that are downloaded
|
|
||||||
# break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if ($link -match 'arm64') {
|
elseif ($link -match 'arm64') {
|
||||||
if ($WindowsArch -eq 'arm64') {
|
if ($WindowsArch -eq 'arm64') {
|
||||||
Writelog "Downloading $Link for $WindowsArch to $Path"
|
Writelog "Downloading $Link for $WindowsArch to $Path"
|
||||||
Start-BitsTransferWithRetry -Source $link -Destination $Path
|
Start-BitsTransferWithRetry -Source $link -Destination $Path
|
||||||
$fileName = ($link -split '/')[-1]
|
$fileName = ($link -split '/')[-1]
|
||||||
Writelog "Returning $fileName"
|
Writelog "Returning $fileName"
|
||||||
#With Windows 11 24H2 and Checkpoint CUs, there are multiple files that are downloaded
|
|
||||||
# break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
elseif ($link -match 'x86') {
|
||||||
|
if ($WindowsArch -eq 'x86') {
|
||||||
|
Writelog "Downloading $link for $WindowsArch to $Path"
|
||||||
|
Start-BitsTransferWithRetry -Source $link -Destination $Path
|
||||||
|
$fileName = ($link -split '/')[-1]
|
||||||
|
Writelog "Returning $fileName"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "No architecture found in $link"
|
||||||
|
|
||||||
|
#If no architecture is found, download the file and run it through Get-PEArchitecture to determine the architecture
|
||||||
|
Writelog "Downloading $link to $Path and analyzing file for architecture"
|
||||||
|
Start-BitsTransferWithRetry -Source $link -Destination $Path
|
||||||
|
|
||||||
|
#Take the file and run it through Get-PEArchitecture to determine the architecture
|
||||||
|
$fileName = ($link -split '/')[-1]
|
||||||
|
$filePath = Join-Path -Path $Path -ChildPath $fileName
|
||||||
|
$arch = Get-PEArchitecture -FilePath $filePath
|
||||||
|
Writelog "$fileName is $arch"
|
||||||
|
#If the architecture matches $WindowsArch, keep the file, otherwise delete it
|
||||||
|
if ($arch -eq $WindowsArch) {
|
||||||
|
Writelog "Architecture for $fileName matches $WindowsArch, keeping file"
|
||||||
|
return $fileName
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Writelog "Deleting $fileName, architecture does not match"
|
||||||
|
Remove-Item -Path $filePath -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $fileName
|
return $fileName
|
||||||
@@ -2621,6 +2746,10 @@ function Get-WimIndex {
|
|||||||
'Pro_Edu_N' { 9 }
|
'Pro_Edu_N' { 9 }
|
||||||
'Pro_WKS' { 10 }
|
'Pro_WKS' { 10 }
|
||||||
'Pro_WKS_N' { 11 }
|
'Pro_WKS_N' { 11 }
|
||||||
|
'Enterprise' { 3 }
|
||||||
|
'Enterprise N' { 4 }
|
||||||
|
'Enterprise LTSC' { 1 }
|
||||||
|
'Enterprise N LTSC' { 2 }
|
||||||
Default { 6 }
|
Default { 6 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3133,36 +3262,43 @@ function Get-ShortenedWindowsSKU {
|
|||||||
)
|
)
|
||||||
$shortenedWindowsSKU = switch ($WindowsSKU) {
|
$shortenedWindowsSKU = switch ($WindowsSKU) {
|
||||||
'Core' { 'Home' }
|
'Core' { 'Home' }
|
||||||
'CoreN' { 'Home_N' }
|
|
||||||
'CoreSingleLanguage' { 'Home_SL' }
|
|
||||||
'Education' { 'Edu' }
|
|
||||||
'EducationN' { 'Edu_N' }
|
|
||||||
'Professional' { 'Pro' }
|
|
||||||
'ProfessionalN' { 'Pro_N' }
|
|
||||||
'ProfessionalEducation' { 'Pro_Edu' }
|
|
||||||
'ProfessionalEducationN' { 'Pro_Edu_N' }
|
|
||||||
'ProfessionalWorkstation' { 'Pro_WKS' }
|
|
||||||
'ProfessionalWorkstationN' { 'Pro_WKS_N' }
|
|
||||||
'Enterprise' { 'Ent' }
|
|
||||||
'EnterpriseN' { 'Ent_N' }
|
|
||||||
'ServerStandard' { 'Srv_Std' }
|
|
||||||
'ServerDatacenter' { 'Srv_Dtc' }
|
|
||||||
'Home' { 'Home' }
|
'Home' { 'Home' }
|
||||||
|
'CoreN' { 'Home_N' }
|
||||||
'Home N' { 'Home_N' }
|
'Home N' { 'Home_N' }
|
||||||
|
'CoreSingleLanguage' { 'Home_SL' }
|
||||||
'Home Single Language' { 'Home_SL' }
|
'Home Single Language' { 'Home_SL' }
|
||||||
'Education' { 'Edu' }
|
'Education' { 'Edu' }
|
||||||
'EducationN' { 'Edu_N' }
|
'EducationN' { 'Edu_N' }
|
||||||
|
'Education N' { 'Edu_N' }
|
||||||
'Professional' { 'Pro' }
|
'Professional' { 'Pro' }
|
||||||
|
'Pro' { 'Pro' }
|
||||||
|
'ProfessionalN' { 'Pro_N' }
|
||||||
'Pro N' { 'Pro_N' }
|
'Pro N' { 'Pro_N' }
|
||||||
|
'ProfessionalEducation' { 'Pro_Edu' }
|
||||||
'Pro Education' { 'Pro_Edu' }
|
'Pro Education' { 'Pro_Edu' }
|
||||||
|
'ProfessionalEducationN' { 'Pro_Edu_N' }
|
||||||
'Pro Education N' { 'Pro_Edu_N' }
|
'Pro Education N' { 'Pro_Edu_N' }
|
||||||
|
'ProfessionalWorkstation' { 'Pro_WKS' }
|
||||||
'Pro for Workstations' { 'Pro_WKS' }
|
'Pro for Workstations' { 'Pro_WKS' }
|
||||||
|
'ProfessionalWorkstationN' { 'Pro_WKS_N' }
|
||||||
'Pro N for Workstations' { 'Pro_WKS_N' }
|
'Pro N for Workstations' { 'Pro_WKS_N' }
|
||||||
'Enterprise' { 'Ent' }
|
'Enterprise' { 'Ent' }
|
||||||
'EnterpriseN' { 'Ent_N' }
|
'EnterpriseN' { 'Ent_N' }
|
||||||
|
'Enterprise N' { 'Ent_N' }
|
||||||
|
'Enterprise N LTSC' { 'Ent_N_LTSC' }
|
||||||
|
'EnterpriseS' { 'Ent_LTSC' }
|
||||||
|
'EnterpriseSN' { 'Ent_N_LTSC' }
|
||||||
|
'Enterprise LTSC' { 'Ent_LTSC' }
|
||||||
|
'Enterprise 2016 LTSB' { 'Ent_LTSC' }
|
||||||
|
'Enterprise N 2016 LTSB' { 'Ent_N_LTSC' }
|
||||||
|
'IoT Enterprise LTSC' { 'IoT_Ent_LTSC' }
|
||||||
|
'IoTEnterpriseS' { 'IoT_Ent_LTSC' }
|
||||||
|
'IoT Enterprise N LTSC' { 'IoT_Ent_N_LTSC' }
|
||||||
|
'ServerStandard' { 'Srv_Std' }
|
||||||
'Standard' { 'Srv_Std' }
|
'Standard' { 'Srv_Std' }
|
||||||
'Standard (Desktop Experience)' { 'Srv_Std_DE' }
|
'ServerDatacenter' { 'Srv_Dtc' }
|
||||||
'Datacenter' { 'Srv_Dtc' }
|
'Datacenter' { 'Srv_Dtc' }
|
||||||
|
'Standard (Desktop Experience)' { 'Srv_Std_DE' }
|
||||||
'Datacenter (Desktop Experience)' { 'Srv_Dtc_DE' }
|
'Datacenter (Desktop Experience)' { 'Srv_Dtc_DE' }
|
||||||
}
|
}
|
||||||
return $shortenedWindowsSKU
|
return $shortenedWindowsSKU
|
||||||
@@ -3170,6 +3306,13 @@ function Get-ShortenedWindowsSKU {
|
|||||||
}
|
}
|
||||||
function New-FFUFileName {
|
function New-FFUFileName {
|
||||||
|
|
||||||
|
# $Winverinfo.name will be either Win10 or Win11 for client OSes
|
||||||
|
# Since WindowsRelease now includes dates, it breaks default name template in the config file
|
||||||
|
# This should keep in line with the naming that's done via VM Captures
|
||||||
|
if ($installationType -eq 'Client' -and $winverinfo) {
|
||||||
|
$WindowsRelease = $winverinfo.name
|
||||||
|
}
|
||||||
|
|
||||||
$BuildDate = Get-Date -uformat %b%Y
|
$BuildDate = Get-Date -uformat %b%Y
|
||||||
# Replace '{WindowsRelease}' with the Windows release (e.g., 10, 11, 2016, 2019, 2022, 2025)
|
# Replace '{WindowsRelease}' with the Windows release (e.g., 10, 11, 2016, 2019, 2022, 2025)
|
||||||
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsRelease}', $WindowsRelease
|
$CustomFFUNameTemplate = $CustomFFUNameTemplate -replace '{WindowsRelease}', $WindowsRelease
|
||||||
@@ -3410,9 +3553,27 @@ Function Get-WindowsVersionInfo {
|
|||||||
$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion'
|
$DisplayVersion = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'DisplayVersion'
|
||||||
WriteLog "Windows Version: $DisplayVersion"
|
WriteLog "Windows Version: $DisplayVersion"
|
||||||
}
|
}
|
||||||
|
# For Windows 10 LTSC 2019, set DisplayVersion to 2019
|
||||||
|
if ($CurrentBuild -eq 17763 -and $InstallationType -eq "Client") {
|
||||||
|
$DisplayVersion = '2019'
|
||||||
|
}
|
||||||
|
|
||||||
$BuildDate = Get-Date -uformat %b%Y
|
$BuildDate = Get-Date -uformat %b%Y
|
||||||
|
|
||||||
|
# $SKU = switch ($SKU) {
|
||||||
|
# Core { 'Home' }
|
||||||
|
# Professional { 'Pro' }
|
||||||
|
# ProfessionalEducation { 'Pro_Edu' }
|
||||||
|
# Enterprise { 'Ent' }
|
||||||
|
# EnterpriseS { 'Ent_LTSC' }
|
||||||
|
# IoTEnterpriseS { 'IoT_Ent_LTSC' }
|
||||||
|
# Education { 'Edu' }
|
||||||
|
# ProfessionalWorkstation { 'Pro_Wks' }
|
||||||
|
# ServerStandard { 'Srv_Std' }
|
||||||
|
# ServerDatacenter { 'Srv_Dtc' }
|
||||||
|
# }
|
||||||
|
# WriteLog "Windows SKU Modified to: $SKU"
|
||||||
|
|
||||||
# $WindowsSKU = switch ($WindowsSKU) {
|
# $WindowsSKU = switch ($WindowsSKU) {
|
||||||
# Core { 'Home' }
|
# Core { 'Home' }
|
||||||
# Professional { 'Pro' }
|
# Professional { 'Pro' }
|
||||||
@@ -4010,6 +4171,37 @@ function Export-ConfigFile{
|
|||||||
# Convert to JSON and save
|
# Convert to JSON and save
|
||||||
$orderedParams | ConvertTo-Json | Out-File $ExportConfigFile -Force
|
$orderedParams | ConvertTo-Json | Out-File $ExportConfigFile -Force
|
||||||
}
|
}
|
||||||
|
function Get-PEArchitecture {
|
||||||
|
param(
|
||||||
|
[string]$FilePath
|
||||||
|
)
|
||||||
|
|
||||||
|
# Read the entire file as bytes.
|
||||||
|
$bytes = [System.IO.File]::ReadAllBytes($FilePath)
|
||||||
|
|
||||||
|
# Check for the 'MZ' signature.
|
||||||
|
if ($bytes[0] -ne 0x4D -or $bytes[1] -ne 0x5A) {
|
||||||
|
throw "The file is not a valid PE file."
|
||||||
|
}
|
||||||
|
|
||||||
|
# The PE header offset is stored at offset 0x3C.
|
||||||
|
$peHeaderOffset = [System.BitConverter]::ToInt32($bytes, 0x3C)
|
||||||
|
|
||||||
|
# Verify the PE signature "PE\0\0".
|
||||||
|
if ($bytes[$peHeaderOffset] -ne 0x50 -or $bytes[$peHeaderOffset + 1] -ne 0x45) {
|
||||||
|
throw "Invalid PE header."
|
||||||
|
}
|
||||||
|
|
||||||
|
# The Machine field is located immediately after the PE signature.
|
||||||
|
$machine = [System.BitConverter]::ToUInt16($bytes, $peHeaderOffset + 4)
|
||||||
|
|
||||||
|
switch ($machine) {
|
||||||
|
0x014c { return "x86" }
|
||||||
|
0x8664 { return "x64" }
|
||||||
|
0xAA64 { return "ARM64" }
|
||||||
|
default { return ("Unknown architecture: 0x{0:X}" -f $machine) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
###END FUNCTIONS
|
###END FUNCTIONS
|
||||||
|
|
||||||
@@ -4307,9 +4499,9 @@ if ($InstallApps) {
|
|||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
WriteLog "$AppsPath\InstallAppsandSysprep.cmd found"
|
WriteLog "$AppsPath\InstallAppsandSysprep.cmd found"
|
||||||
If (Test-Path -Path "$AppsPath\AppList.json"){
|
If (Test-Path -Path $AppListPath){
|
||||||
WriteLog "$AppsPath\AppList.json found, checking for winget apps to install"
|
WriteLog "$AppListPath found, checking for winget apps to install"
|
||||||
Get-Apps -AppList "$AppsPath\AppList.json"
|
Get-Apps -AppList "$AppListPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $InstallOffice) {
|
if (-not $InstallOffice) {
|
||||||
@@ -4372,40 +4564,46 @@ if ($InstallApps) {
|
|||||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
||||||
WriteLog "Update complete"
|
WriteLog "Update complete"
|
||||||
|
|
||||||
###### 9/4/2024 - Windows Security Platform update is no longer available from Update Catalog. Will change to using
|
###### 5/20/2025 - Security Platform URLs are not available for download, will go back to using the Microsoft Update Catalog in UI build
|
||||||
###### https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3
|
###### https://support.microsoft.com/en-us/topic/windows-security-update-a6ac7d2e-b1bf-44c0-a028-41720a242da3
|
||||||
|
|
||||||
#Download Windows Security Platform Update
|
#Download Windows Security Platform Update
|
||||||
WriteLog "Downloading Windows Security Platform Update"
|
# WriteLog "Downloading Windows Security Platform Update"
|
||||||
if ($WindowsArch -eq 'x64') {
|
# if ($WindowsArch -eq 'x64') {
|
||||||
$securityPlatformURL = 'https://definitionupdates.microsoft.com/download/DefinitionUpdates/windowssecurity/10.0.27703.1006/x64/securityhealthsetup.exe'
|
# $securityPlatformURL = 'https://definitionupdates.microsoft.com/download/DefinitionUpdates/windowssecurity/10.0.27703.1006/x64/securityhealthsetup.exe'
|
||||||
}
|
# }
|
||||||
if ($WindowsArch -eq 'ARM64') {
|
# if ($WindowsArch -eq 'ARM64') {
|
||||||
$securityPlatformURL = 'https://definitionupdates.microsoft.com/download/DefinitionUpdates/windowssecurity/10.0.27703.1006/arm64/securityhealthsetup.exe'
|
# $securityPlatformURL = 'https://definitionupdates.microsoft.com/download/DefinitionUpdates/windowssecurity/10.0.27703.1006/arm64/securityhealthsetup.exe'
|
||||||
}
|
# }
|
||||||
try {
|
# try {
|
||||||
WriteLog "Windows Security Platform Update URL is $securityPlatformURL"
|
# WriteLog "Windows Security Platform Update URL is $securityPlatformURL"
|
||||||
Start-BitsTransferWithRetry -Source $securityPlatformURL -Destination "$DefenderPath\securityhealthsetup.exe"
|
# Start-BitsTransferWithRetry -Source $securityPlatformURL -Destination "$DefenderPath\securityhealthsetup.exe"
|
||||||
WriteLog "Windows Security Platform Update downloaded to $DefenderPath\securityhealthsetup.exe"
|
# WriteLog "Windows Security Platform Update downloaded to $DefenderPath\securityhealthsetup.exe"
|
||||||
}
|
# }
|
||||||
catch {
|
# catch {
|
||||||
Write-Host "Downloading Windows Security Platform Update Failed"
|
# Write-Host "Downloading Windows Security Platform Update Failed"
|
||||||
WriteLog "Downloading Windows Security Platform Update Failed with error $_"
|
# WriteLog "Downloading Windows Security Platform Update Failed with error $_"
|
||||||
throw $_
|
# throw $_
|
||||||
}
|
# }
|
||||||
# Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Windows Security Platform Update
|
# # Modify InstallAppsandSysprep.cmd to add in $KBFilePath on the line after REM Install Windows Security Platform Update
|
||||||
WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Security Platform Update"
|
# WriteLog "Updating $AppsPath\InstallAppsandSysprep.cmd to include Windows Security Platform Update"
|
||||||
$CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
# $CmdContent = Get-Content -Path "$AppsPath\InstallAppsandSysprep.cmd"
|
||||||
$UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Security Platform Update)', ("REM Install Windows Security Platform Update`r`nd:\Defender\securityhealthsetup.exe")
|
# $UpdatedcmdContent = $CmdContent -replace '^(REM Install Windows Security Platform Update)', ("REM Install Windows Security Platform Update`r`nd:\Defender\securityhealthsetup.exe")
|
||||||
Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
# Set-Content -Path "$AppsPath\InstallAppsandSysprep.cmd" -Value $UpdatedcmdContent
|
||||||
WriteLog "Update complete"
|
# WriteLog "Update complete"
|
||||||
}
|
}
|
||||||
if ($UpdateLatestMSRT) {
|
if ($UpdateLatestMSRT) {
|
||||||
WriteLog "`$UpdateLatestMSRT is set to true."
|
WriteLog "`$UpdateLatestMSRT is set to true."
|
||||||
if ($WindowsArch -eq 'x64') {
|
if ($WindowsArch -eq 'x64') {
|
||||||
if ($installationType -eq 'client') {
|
if ($WindowsRelease -in 10, 11) {
|
||||||
$Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows $WindowsRelease"""
|
$Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows $WindowsRelease"""
|
||||||
}
|
}
|
||||||
|
elseif ($WindowsRelease -in 2016, 2019, 2021 -and $isLTSC) {
|
||||||
|
$Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows 10"""
|
||||||
|
}
|
||||||
|
elseif ($WindowsRelease -in 2024 -and $isLTSC) {
|
||||||
|
$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
|
#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') {
|
elseif ($installationType -eq 'server' -and $WindowsRelease -eq '24H2') {
|
||||||
$Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows Server 2019"""
|
$Name = """Windows Malicious Software Removal Tool x64""" + " " + """Windows Server 2019"""
|
||||||
@@ -4542,7 +4740,7 @@ try {
|
|||||||
#The Windows release info page is updated later than the MU Catalog
|
#The Windows release info page is updated later than the MU Catalog
|
||||||
if ($UpdateLatestCU -and -not $UpdatePreviewCU) {
|
if ($UpdateLatestCU -and -not $UpdatePreviewCU) {
|
||||||
Writelog "`$UpdateLatestCU is set to true, checking for latest CU"
|
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"""
|
$Name = """Cumulative update for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
|
||||||
}
|
}
|
||||||
if ($WindowsRelease -eq 2025) {
|
if ($WindowsRelease -eq 2025) {
|
||||||
@@ -4551,30 +4749,67 @@ try {
|
|||||||
if ($WindowsRelease -eq 2022) {
|
if ($WindowsRelease -eq 2022) {
|
||||||
$Name = """Cumulative Update for Microsoft server operating system, version 21h2 for $WindowsArch"""
|
$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"""
|
$Name = """Cumulative update for Windows Server $WindowsRelease for $WindowsArch"""
|
||||||
}
|
}
|
||||||
|
if ($WindowsRelease -in 2016, 2019, 2021 -and $isLTSC) {
|
||||||
|
$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 $isLTSC) {
|
||||||
|
$Name = """Cumulative update for Windows 11 Version $WindowsVersion for $WindowsArch"""
|
||||||
|
}
|
||||||
#Check if $KBPath exists, if not, create it
|
#Check if $KBPath exists, if not, create it
|
||||||
If (-not (Test-Path -Path $KBPath)) {
|
If (-not (Test-Path -Path $KBPath)) {
|
||||||
WriteLog "Creating $KBPath"
|
WriteLog "Creating $KBPath"
|
||||||
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
||||||
}
|
}
|
||||||
#Get latest Servicing Stack Update for Windows Server 2016
|
#Get latest Servicing Stack Update for Windows Server 2016
|
||||||
if ($WindowsRelease -eq 2016) {
|
if ($WindowsRelease -eq 2016 -and $installationType -eq "Server") {
|
||||||
$SSUName = """Servicing stack update for Windows Server $WindowsRelease for $WindowsArch"""
|
$SSUName = """Servicing stack update for Windows Server $WindowsRelease for $WindowsArch"""
|
||||||
WriteLog "Searching for $SSUName from Microsoft Update Catalog and saving to $KBPath"
|
WriteLog "Searching for $SSUName from Microsoft Update Catalog and saving to $KBPath"
|
||||||
$SSUFile = Save-KB -Name $SSUName -Path $KBPath
|
$SSUFile = Save-KB -Name $SSUName -Path $KBPath
|
||||||
$SSUFilePath = "$KBPath\$SSUFile"
|
$SSUFilePath = "$KBPath\$SSUFile"
|
||||||
WriteLog "Latest SSU saved to $SSUFilePath"
|
WriteLog "Latest SSU saved to $SSUFilePath"
|
||||||
}
|
}
|
||||||
|
if ($WindowsRelease -in 2016, 2019, 2021 -and $isLTSC) {
|
||||||
|
$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"
|
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath"
|
||||||
$KBFilePath = Save-KB -Name $Name -Path $KBPath
|
$CUFileName = Save-KB -Name $Name -Path $KBPath
|
||||||
WriteLog "Latest CU saved to $KBPath\$KBFilePath"
|
# Check if $CUFileName contains the string in $global:LastKBArticleID
|
||||||
|
# If it does not, look in $KBPath for the file that contains the string in $global:LastKBArticleID
|
||||||
|
# and set that as the $CUFileName
|
||||||
|
# This is because checkpoint CUs download indeterministically
|
||||||
|
WriteLog "Checking if $CUFileName contains $global:LastKBArticleID"
|
||||||
|
if ($CUFileName -notmatch $global:LastKBArticleID) {
|
||||||
|
WriteLog "$CUFileName does not contain $global:LastKBArticleID, searching for file that contains it"
|
||||||
|
$CUFileName = $null
|
||||||
|
# Get the file that contains the string in $global:LastKBArticleID
|
||||||
|
$CUFileName = (Get-ChildItem -Path $KBPath -Filter "*$global:LastKBArticleID*" | Select-Object -First 1).Name
|
||||||
|
if ($null -ne $CUFileName) {
|
||||||
|
WriteLog "Found $CUFileName"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "Could not find file that contains $global:LastKBArticleID"
|
||||||
|
throw "Could not find file that contains $global:LastKBArticleID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$CUPath = "$KBPath\$CUFileName"
|
||||||
|
WriteLog "Latest CU saved to $CUPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
#Update Latest Preview Cumlative Update for Client OS only
|
#Update Latest Preview Cumlative Update for Client OS only
|
||||||
#will take Precendence over $UpdateLatestCU if both were set to $true
|
#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"
|
Writelog "`$UpdatePreviewCU is set to true, checking for latest Preview CU"
|
||||||
$Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
|
$Name = """Cumulative update Preview for Windows $WindowsRelease Version $WindowsVersion for $WindowsArch"""
|
||||||
#Check if $KBPath exists, if not, create it
|
#Check if $KBPath exists, if not, create it
|
||||||
@@ -4583,36 +4818,174 @@ try {
|
|||||||
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
||||||
}
|
}
|
||||||
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath"
|
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath"
|
||||||
$KBFilePath = Save-KB -Name $Name -Path $KBPath
|
$CUPFileName = Save-KB -Name $Name -Path $KBPath
|
||||||
WriteLog "Latest Preview CU saved to $KBPath\$KBFilePath"
|
# Check if $CUPFileName contains the string in $global:LastKBArticleID
|
||||||
|
# If it does not, look in $KBPath for the file that contains the string in $global:LastKBArticleID
|
||||||
|
# and set that as the $CUPFileName
|
||||||
|
# This is because checkpoint CUs download indeterministically
|
||||||
|
WriteLog "Checking if $CUPFileName contains $global:LastKBArticleID"
|
||||||
|
if ($CUPFileName -notmatch $global:LastKBArticleID) {
|
||||||
|
WriteLog "$CUPFileName does not contain $global:LastKBArticleID, searching for file that contains it"
|
||||||
|
$CUPFileName = $null
|
||||||
|
# Get the file that contains the string in $global:LastKBArticleID
|
||||||
|
$CUPFileName = (Get-ChildItem -Path $KBPath -Filter "*$global:LastKBArticleID*" | Select-Object -First 1).Name
|
||||||
|
if ($null -ne $CUPFileName) {
|
||||||
|
WriteLog "Found $CUPFileName"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "Could not find file that contains $global:LastKBArticleID"
|
||||||
|
throw "Could not find file that contains $global:LastKBArticleID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$CUPPath = "$KBPath\$CUPFileName"
|
||||||
|
WriteLog "Latest CU Preview saved to $CUPPath"
|
||||||
}
|
}
|
||||||
|
|
||||||
#Update Latest .NET Framework
|
#Update Latest .NET Framework
|
||||||
if ($UpdateLatestNet) {
|
if ($UpdateLatestNet) {
|
||||||
Writelog "`$UpdateLatestNet is set to true, checking for latest .NET Framework"
|
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 -eq 2025) {
|
|
||||||
$Name = """Cumulative Update for .NET Framework"" ""3.5 and 4.8.1"" for Windows 11 24H2 x64 -preview"
|
|
||||||
}
|
|
||||||
if ($WindowsRelease -eq 2022) {
|
|
||||||
$Name = """Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1"" ""operating system version 21H2 for x64"""
|
|
||||||
}
|
|
||||||
if ($WindowsRelease -eq 2019) {
|
|
||||||
$Name = """Cumulative Update for .NET Framework 3.5, 4.7.2 and 4.8 for Windows Server 2019 for x64"""
|
|
||||||
}
|
|
||||||
if ($WindowsRelease -eq 2016) {
|
|
||||||
$Name = """Cumulative Update for .NET Framework 4.8 for Windows Server 2016 for x64"""
|
|
||||||
}
|
|
||||||
#Check if $KBPath exists, if not, create it
|
#Check if $KBPath exists, if not, create it
|
||||||
If (-not (Test-Path -Path $KBPath)) {
|
if (-not (Test-Path -Path $KBPath)) {
|
||||||
WriteLog "Creating $KBPath"
|
WriteLog "Creating $KBPath"
|
||||||
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
New-Item -Path $KBPath -ItemType Directory -Force | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
######
|
||||||
|
#LTSC#
|
||||||
|
######
|
||||||
|
|
||||||
|
# For Windows 10 LTSC editions (2016, 2019, 2021), download and save the latest Servicing Stack Update (SSU) and .NET Framework cumulative update(s)
|
||||||
|
if ($WindowsRelease -in 2016, 2019, 2021 -and $isLTSC) {
|
||||||
|
# SSU likely was downloaded via CU, but still needed here if .net is being updated, no need to download twice though
|
||||||
|
if ($null -eq $SSUFile) {
|
||||||
|
$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"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For Windows 10 LTSC editions (2016, 2019, 2021), download and save the latest .NET Framework cumulative update(s)
|
||||||
|
# to a dedicated NET subdirectory, as these editions may include multiple .NET updates that need to be installed together.
|
||||||
|
if ($WindowsRelease -in 2016) {
|
||||||
|
$name = """Cumulative Update for .NET Framework 4.8 for Windows 10 version $WindowsVersion for $WindowsArch"""
|
||||||
|
}
|
||||||
|
if ($WindowsRelease -eq 2019) {
|
||||||
|
$name = """Cumulative Update for .NET Framework 3.5, 4.7.2 and 4.8 for Windows 10 Version $WindowsVersion for $WindowsArch"""
|
||||||
|
}
|
||||||
|
if ($WindowsRelease -eq 2021){
|
||||||
|
$name = """Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1 for Windows 10 Version $WindowsVersion for $WindowsArch"""
|
||||||
|
}
|
||||||
|
|
||||||
|
$NETPath = Join-Path -Path $KBPath -ChildPath "NET"
|
||||||
|
if (-not (Test-Path -Path $NETPath)) {
|
||||||
|
WriteLog "Creating $NETPath"
|
||||||
|
New-Item -Path $NETPath -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $NETPath"
|
||||||
|
$NETFileName = Save-KB -Name $name -Path $NETPath
|
||||||
|
WriteLog "Latest .NET Framework cumulative update saved to $NETPath\$NETFileName"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For Windows 11 LTSC 2024, set the update name to search for the latest .NET Framework cumulative update in the Microsoft Update Catalog
|
||||||
|
if ($WindowsRelease -eq 2024 -and $isLTSC) {
|
||||||
|
$Name = "Cumulative update for .NET framework windows 11 $WindowsVersion $WindowsArch -preview"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For Windows 10 LTSC 2021, download and save the latest .NET Framework 4.8.1 feature pack to the NET subdirectory.
|
||||||
|
if ($WindowsRelease -eq 2021 -and $isLTSC) {
|
||||||
|
WriteLog "Checking for latest .NET Framework feature pack for Windows $WindowsRelease $WindowsSKU"
|
||||||
|
$NETFeatureName = """Microsoft .NET Framework 4.8.1 for Windows 10 Version 21H2 for x64"""
|
||||||
|
$NETFeaturePackFile = Save-KB -Name $NETFeatureName -Path $NETPath
|
||||||
|
WriteLog "Latest .NET Framework Feature pack saved to $NETPath\$NETFeaturePackFile"
|
||||||
|
}
|
||||||
|
# For Windows 10 LTSC 2016 and 2019, download and save the latest .NET Framework 4.8 feature pack to the NET subdirectory.
|
||||||
|
if ($WindowsRelease -in 2016, 2019 -and $isLTSC) {
|
||||||
|
WriteLog "Checking for latest .NET Framework feature pack for Windows $WindowsRelease $WindowsSKU"
|
||||||
|
$NETFeatureName = """Microsoft .NET Framework 4.8 for Windows 10 Version $WindowsVersion and Windows Server $WindowsRelease for x64"""
|
||||||
|
$NETFeaturePackFile = Save-KB -Name $NETFeatureName -Path $NETPath
|
||||||
|
WriteLog "Latest .NET Framework Feature pack saved to $NETPath\$NETFeaturePackFile"
|
||||||
|
}
|
||||||
|
|
||||||
|
########
|
||||||
|
#CLIENT#
|
||||||
|
########
|
||||||
|
|
||||||
|
# For Windows 10 and 11, set the update name to search for the latest .NET Framework cumulative update (excluding preview) in the Microsoft Update Catalog
|
||||||
|
if ($WindowsRelease -in 10, 11) {
|
||||||
|
$Name = "Cumulative update for .NET framework windows $WindowsRelease $WindowsVersion $WindowsArch -preview"
|
||||||
|
}
|
||||||
|
|
||||||
|
########
|
||||||
|
#SERVER#
|
||||||
|
########
|
||||||
|
|
||||||
|
# For Windows Server 2025, set the update name to search for the latest .NET Framework cumulative update (excluding preview) in the Microsoft Update Catalog
|
||||||
|
if ($WindowsRelease -eq 2025 -and $installationType -eq "Server") {
|
||||||
|
$Name = """Cumulative Update for .NET Framework"" ""3.5 and 4.8.1"" for Windows 11 24H2 x64 -preview"
|
||||||
|
}
|
||||||
|
|
||||||
|
# For Windows Server 2022, set the update name to search for the latest .NET Framework cumulative update (3.5, 4.8, and 4.8.1) for OS version 21H2 x64
|
||||||
|
if ($WindowsRelease -eq 2022 -and $installationType -eq "Server") {
|
||||||
|
$Name = """Cumulative Update for .NET Framework 3.5, 4.8 and 4.8.1"" ""operating system version 21H2 for x64"""
|
||||||
|
}
|
||||||
|
# For Windows Server 2019, set the update name to search for the latest .NET Framework cumulative update (3.5, 4.7.2, and 4.8) for x64
|
||||||
|
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"""
|
||||||
|
}
|
||||||
|
|
||||||
|
# For Windows Server 2016, set the update name to search for the latest .NET Framework 4.8 cumulative update for x64
|
||||||
|
if ($WindowsRelease -eq 2016 -and $installationType -eq "Server") {
|
||||||
|
$Name = """Cumulative Update for .NET Framework 4.8 for Windows Server 2016 for x64"""
|
||||||
|
}
|
||||||
|
|
||||||
|
# For all editions except Windows 10 LTSC (2016, 2019, 2021), search for the latest .NET Framework cumulative update in the Microsoft Update Catalog,
|
||||||
|
# download it to $KBPath, and verify the correct file was downloaded by matching the KB article ID. If not found, search for the file by KB article ID.
|
||||||
|
if (-not ($WindowsRelease -in 2016, 2019, 2021 -and $isLTSC)) {
|
||||||
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath"
|
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $KBPath"
|
||||||
$KBFilePath = Save-KB -Name $Name -Path $KBPath
|
$NETFileName = Save-KB -Name $Name -Path $KBPath
|
||||||
WriteLog "Latest .NET saved to $KBPath\$KBFilePath"
|
# Check if $NETFileName contains the string in $global:LastKBArticleID
|
||||||
|
# If it does not, look in $KBPath for the file that contains the string in $global:LastKBArticleID
|
||||||
|
# and set that as the $NETFileName
|
||||||
|
WriteLog "Checking if $NETFileName contains $global:LastKBArticleID"
|
||||||
|
if ($NETFileName -notmatch $global:LastKBArticleID) {
|
||||||
|
WriteLog "$NETFileName does not contain $global:LastKBArticleID, searching for file that contains it"
|
||||||
|
$NETFileName = $null
|
||||||
|
# Get the file that contains the string in $global:LastKBArticleID
|
||||||
|
$NETFileName = (Get-ChildItem -Path $KBPath -Filter "*$global:LastKBArticleID*" | Select-Object -First 1).Name
|
||||||
|
if ($null -ne $NETFileName) {
|
||||||
|
WriteLog "Found $NETFileName"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteLog "Could not find file that contains $global:LastKBArticleID"
|
||||||
|
throw "Could not find file that contains $global:LastKBArticleID"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$NETPath = "$KBPath\$NETFileName"
|
||||||
|
WriteLog "Latest .NET Framework saved to $NETPath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Update latest Microcode
|
||||||
|
if ($UpdateLatestMicrocode -and $WindowsRelease -in 2016, 2019) {
|
||||||
|
WriteLog "`$UpdateLatestMicrocode is set to true, checking for latest Microcode"
|
||||||
|
#Check if $MicrocodePath exists, if not, create it
|
||||||
|
If (-not (Test-Path -Path $MicrocodePath)) {
|
||||||
|
WriteLog "Creating $MicrocodePath"
|
||||||
|
New-Item -Path $MicrocodePath -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Windows 10 LTSC 2016 (1607) and Windows Server 2016
|
||||||
|
if($WindowsRelease -eq 2016){
|
||||||
|
$name = "KB4589210 $windowsArch"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Windows 10 LTSC 2019 (1809) and Windows Server 2019
|
||||||
|
if($WindowsRelease -eq 2019){
|
||||||
|
$name = "KB4589208 $windowsArch"
|
||||||
|
}
|
||||||
|
WriteLog "Searching for $name from Microsoft Update Catalog and saving to $MicrocodePath"
|
||||||
|
$MicrocodeFileName = Save-KB -Name $name -Path $MicrocodePath
|
||||||
|
WriteLog "Latest Microcode saved to $MicrocodePath\$MicrocodeFileName"
|
||||||
}
|
}
|
||||||
|
|
||||||
#Search for cached VHDX and skip VHDX creation if there's a cached version
|
#Search for cached VHDX and skip VHDX creation if there's a cached version
|
||||||
@@ -4623,7 +4996,7 @@ try {
|
|||||||
$vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending)
|
$vhdxJsons = @(Get-ChildItem -File -Path $VHDXCacheFolder -Filter '*_config.json' | Sort-Object -Property CreationTime -Descending)
|
||||||
WriteLog "Found $($vhdxJsons.Count) cached VHDX files"
|
WriteLog "Found $($vhdxJsons.Count) cached VHDX files"
|
||||||
if (Test-Path -Path $KBPath){
|
if (Test-Path -Path $KBPath){
|
||||||
$downloadedKBs = @(Get-ChildItem -File -Path $KBPath)
|
$downloadedKBs = @(Get-ChildItem -File -Path $KBPath -Recurse)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$downloadedKBs = @()
|
$downloadedKBs = @()
|
||||||
@@ -4719,7 +5092,7 @@ try {
|
|||||||
WriteLog "Adding KBs to $WindowsPartition"
|
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'
|
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 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 'WindowsRelease is 2016, adding SSU first'
|
||||||
WriteLog "Adding SSU to $WindowsPartition"
|
WriteLog "Adding SSU to $WindowsPartition"
|
||||||
# Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null
|
# Add-WindowsPackage -Path $WindowsPartition -PackagePath $SSUFilePath -PreventPending | Out-Null
|
||||||
@@ -4730,14 +5103,41 @@ try {
|
|||||||
WriteLog "Removing $SSUFilePath"
|
WriteLog "Removing $SSUFilePath"
|
||||||
Remove-Item -Path $SSUFilePath -Force | Out-Null
|
Remove-Item -Path $SSUFilePath -Force | Out-Null
|
||||||
WriteLog 'SSU removed'
|
WriteLog 'SSU removed'
|
||||||
WriteLog "Adding CU to $WindowsPartition"
|
|
||||||
}
|
}
|
||||||
# Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath -PreventPending | Out-Null
|
if ($WindowsRelease -in 2016, 2019, 2021 -and $isLTSC) {
|
||||||
Add-WindowsPackage -Path $WindowsPartition -PackagePath $KBPath | Out-Null
|
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'
|
||||||
|
}
|
||||||
|
# Break out CU and NET updates to be added separately to abide by Checkpoint Update recommendations
|
||||||
|
if ($UpdateLatestCU) {
|
||||||
|
WriteLog "Adding $CUPath to $WindowsPartition"
|
||||||
|
Add-WindowsPackage -Path $WindowsPartition -PackagePath $CUPath | Out-Null
|
||||||
|
WriteLog "$CUPath added to $WindowsPartition"
|
||||||
|
}
|
||||||
|
if ($UpdatePreviewCU) {
|
||||||
|
WriteLog "Adding $CUPPath to $WindowsPartition"
|
||||||
|
Add-WindowsPackage -Path $WindowsPartition -PackagePath $CUPPath | Out-Null
|
||||||
|
WriteLog "$CUPPath added to $WindowsPartition"
|
||||||
|
}
|
||||||
|
if ($UpdateLatestNet) {
|
||||||
|
WriteLog "Adding $NETPath to $WindowsPartition"
|
||||||
|
Add-WindowsPackage -Path $WindowsPartition -PackagePath $NETPath | Out-Null
|
||||||
|
WriteLog "$NETPath added to $WindowsPartition"
|
||||||
|
}
|
||||||
|
if ($UpdateLatestMicrocode -and $WindowsRelease -in 2016, 2019) {
|
||||||
|
WriteLog "Adding $MicrocodePath to $WindowsPartition"
|
||||||
|
Add-WindowsPackage -Path $WindowsPartition -PackagePath $MicrocodePath | Out-Null
|
||||||
|
WriteLog "$MicrocodePath added to $WindowsPartition"
|
||||||
|
}
|
||||||
WriteLog "KBs added to $WindowsPartition"
|
WriteLog "KBs added to $WindowsPartition"
|
||||||
if ($AllowVHDXCaching) {
|
if ($AllowVHDXCaching) {
|
||||||
$cachedVHDXInfo = [VhdxCacheItem]::new()
|
$cachedVHDXInfo = [VhdxCacheItem]::new()
|
||||||
$includedUpdates = Get-ChildItem -Path $KBPath -File
|
$includedUpdates = Get-ChildItem -Path $KBPath -File -Recurse
|
||||||
|
|
||||||
foreach ($includedUpdate in $includedUpdates) {
|
foreach ($includedUpdate in $includedUpdates) {
|
||||||
$cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name))
|
$cachedVHDXInfo.IncludedUpdates += ([VhdxCacheUpdateItem]::new($includedUpdate.Name))
|
||||||
@@ -4942,7 +5342,7 @@ try {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
#Shorten Windows SKU for use in FFU file name to remove spaces and long names
|
#Shorten Windows SKU for use in FFU file name to remove spaces and long names
|
||||||
WriteLog 'Shortening Windows SKU for FFU file name'
|
WriteLog "Shortening Windows SKU: $WindowsSKU for FFU file name"
|
||||||
$shortenedWindowsSKU = Get-ShortenedWindowsSKU -WindowsSKU $WindowsSKU
|
$shortenedWindowsSKU = Get-ShortenedWindowsSKU -WindowsSKU $WindowsSKU
|
||||||
WriteLog "Shortened Windows SKU: $shortenedWindowsSKU"
|
WriteLog "Shortened Windows SKU: $shortenedWindowsSKU"
|
||||||
#Create FFU file
|
#Create FFU file
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#Modify the net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727
|
#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
|
net use W: \\192.168.1.158\FFUCaptureShare /user:ffu_user ddb1f077-3eed-433c-b4d9-7b8cd54ce727
|
||||||
#Custom naming placeholder
|
#Custom naming placeholder
|
||||||
|
|
||||||
$AssignDriveLetter = 'x:\AssignDriveLetter.txt'
|
$AssignDriveLetter = 'x:\AssignDriveLetter.txt'
|
||||||
Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null
|
Start-Process -FilePath diskpart.exe -ArgumentList "/S $AssignDriveLetter" -Wait -ErrorAction Stop | Out-Null
|
||||||
#Load Registry Hive
|
#Load Registry Hive
|
||||||
@@ -12,26 +11,39 @@ reg load "HKLM\FFU" $Software
|
|||||||
|
|
||||||
$SKU = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'EditionID'
|
$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'
|
[int]$CurrentBuild = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'CurrentBuild'
|
||||||
if ($CurrentBuild -notin 14393, 17763) {
|
$InstallationType = Get-ItemPropertyValue -Path 'HKLM:\FFU\Microsoft\Windows NT\CurrentVersion\' -Name 'InstallationType'
|
||||||
|
if ($CurrentBuild -notin 14393, 17763 -and $InstallationType -ne "Server") {
|
||||||
$WindowsVersion = 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'
|
# For Windows 10 LTSB 2016, set WindowsVersion to 2016
|
||||||
|
if ($CurrentBuild -eq 14393 -and $InstallationType -eq "Client") {
|
||||||
|
$WindowsVersion = '2016'
|
||||||
|
}
|
||||||
|
# For Windows 10 LTSC 2019, set WindowsVersion to 2019
|
||||||
|
if ($CurrentBuild -eq 17763 -and $InstallationType -eq "Client") {
|
||||||
|
$WindowsVersion = '2019'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$BuildDate = Get-Date -uformat %b%Y
|
$BuildDate = Get-Date -uformat %b%Y
|
||||||
|
|
||||||
$SKU = switch ($SKU) {
|
$SKU = switch ($SKU) {
|
||||||
Core { 'Home' }
|
Core { 'Home' }
|
||||||
CoreN { 'HomeN' }
|
CoreN { 'Home_N' }
|
||||||
CoreSingleLanguage { 'HomeSL' }
|
CoreSingleLanguage { 'Home_SL' }
|
||||||
Professional { 'Pro' }
|
Professional { 'Pro' }
|
||||||
ProfessionalN { 'ProN' }
|
ProfessionalN { 'Pro_N' }
|
||||||
ProfessionalEducation { 'Pro_Edu' }
|
ProfessionalEducation { 'Pro_Edu' }
|
||||||
ProfessionalEducationN { 'Pro_EduN' }
|
ProfessionalEducationN { 'Pro_Edu_N' }
|
||||||
Enterprise { 'Ent' }
|
Enterprise { 'Ent' }
|
||||||
EnterpriseN { 'EntN' }
|
EnterpriseN { 'Ent_N' }
|
||||||
|
EnterpriseS { 'Ent_LTSC' }
|
||||||
|
EnterpriseSN { 'Ent_N_LTSC' }
|
||||||
|
IoTEnterpriseS { 'IoT_Ent_LTSC' }
|
||||||
Education { 'Edu' }
|
Education { 'Edu' }
|
||||||
EducationN { 'EduN' }
|
EducationN { 'Edu_N' }
|
||||||
ProfessionalWorkstation { 'Pro_Wks' }
|
ProfessionalWorkstation { 'Pro_Wks' }
|
||||||
ProfessionalWorkstationN { 'Pro_WksN' }
|
ProfessionalWorkstationN { 'Pro_Wks_N' }
|
||||||
ServerStandard { 'Srv_Std' }
|
ServerStandard { 'Srv_Std' }
|
||||||
ServerDatacenter { 'Srv_Dtc' }
|
ServerDatacenter { 'Srv_Dtc' }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ $LogFileName = 'ScriptLog.txt'
|
|||||||
$USBDrive = Get-USBDrive
|
$USBDrive = Get-USBDrive
|
||||||
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
|
New-item -Path $USBDrive -Name $LogFileName -ItemType "file" -Force | Out-Null
|
||||||
$LogFile = $USBDrive + $LogFilename
|
$LogFile = $USBDrive + $LogFilename
|
||||||
$version = '2412.3'
|
$version = '2505.1'
|
||||||
WriteLog 'Begin Logging'
|
WriteLog 'Begin Logging'
|
||||||
WriteLog "Script version: $version"
|
WriteLog "Script version: $version"
|
||||||
|
|
||||||
@@ -222,6 +222,7 @@ if (Test-Path -Path $PPKGFolder){
|
|||||||
$UnattendFolder = $USBDrive + "unattend\"
|
$UnattendFolder = $USBDrive + "unattend\"
|
||||||
$UnattendFilePath = $UnattendFolder + "unattend.xml"
|
$UnattendFilePath = $UnattendFolder + "unattend.xml"
|
||||||
$UnattendPrefixPath = $UnattendFolder + "prefixes.txt"
|
$UnattendPrefixPath = $UnattendFolder + "prefixes.txt"
|
||||||
|
$UnattendComputerNamePath = $UnattendFolder + "SerialComputerNames.csv"
|
||||||
If (Test-Path -Path $UnattendFilePath){
|
If (Test-Path -Path $UnattendFilePath){
|
||||||
$UnattendFile = Get-ChildItem -Path $UnattendFilePath
|
$UnattendFile = Get-ChildItem -Path $UnattendFilePath
|
||||||
If ($UnattendFile){
|
If ($UnattendFile){
|
||||||
@@ -234,6 +235,12 @@ If (Test-Path -Path $UnattendPrefixPath){
|
|||||||
$UnattendPrefix = $true
|
$UnattendPrefix = $true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
If (Test-Path -Path $UnattendComputerNamePath){
|
||||||
|
$UnattendComputerNameFile = Get-ChildItem -Path $UnattendComputerNamePath
|
||||||
|
If ($UnattendComputerNameFile){
|
||||||
|
$UnattendComputerName = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#Ask for device name if unattend exists
|
#Ask for device name if unattend exists
|
||||||
if ($Unattend -and $UnattendPrefix){
|
if ($Unattend -and $UnattendPrefix){
|
||||||
@@ -278,6 +285,24 @@ if ($Unattend -and $UnattendPrefix){
|
|||||||
$computername = Set-Computername($computername)
|
$computername = Set-Computername($computername)
|
||||||
Writelog "Computer name set to $computername"
|
Writelog "Computer name set to $computername"
|
||||||
}
|
}
|
||||||
|
elseif($Unattend -and $UnattendComputerName){
|
||||||
|
Writelog 'Unattend file found with SerialComputerNames.csv. Getting name for current computer.'
|
||||||
|
$SerialComputerNames = Import-Csv -Path $UnattendComputerNameFile.FullName -Delimiter ","
|
||||||
|
|
||||||
|
$SerialNumber = (Get-CimInstance -Class Win32_Bios).SerialNumber
|
||||||
|
$SCName = $SerialComputerNames | Where-Object { $_.SerialNumber -eq $SerialNumber }
|
||||||
|
|
||||||
|
If ($SCName) {
|
||||||
|
[string]$computername = $SCName.ComputerName
|
||||||
|
$computername = Set-Computername($computername)
|
||||||
|
Writelog "Computer name set to $computername"
|
||||||
|
} else {
|
||||||
|
Writelog 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.'
|
||||||
|
[string]$computername = ("FFU-" + (-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 11 | ForEach-Object { [char]$_ })))
|
||||||
|
$computername = Set-Computername($computername)
|
||||||
|
Writelog "Computer name set to $computername"
|
||||||
|
}
|
||||||
|
}
|
||||||
elseif($Unattend) {
|
elseif($Unattend) {
|
||||||
Writelog 'Unattend file found with no prefixes.txt, asking for name'
|
Writelog 'Unattend file found with no prefixes.txt, asking for name'
|
||||||
[string]$computername = Read-Host 'Enter device name'
|
[string]$computername = Read-Host 'Enter device name'
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user