使用动态更新更新 Windows 安装媒体
本文介绍如何在部署之前获取动态更新包并将其应用于现有 Windows 映像,并包含可用于自动执行此过程的Windows PowerShell脚本。
批量许可媒体适用于批量许可服务中心 (VLSC) 和其他相关渠道(如 Windows 更新 for Business、Windows Server Update Services (WSUS) 和 Visual Studio 订阅)中的每个 Windows 版本。 可以使用动态更新来确保 Windows 设备将最新的功能更新包作为就地升级的一部分,同时保留语言包和按需功能 (可能已安装的 FOD) 。 动态更新还无需在就地升级过程中安装单独的质量更新。
动态更新
无论从媒体还是从连接到Windows 更新) 的环境开始安装功能更新 (,动态更新都是第一步。 Windows 安装程序联系 Microsoft 终结点以提取动态更新包,然后将这些更新应用于操作系统安装媒体。 更新包包括以下类型的更新:
- 汇报 Setup.exe 安装程序用于功能更新的二进制文件或其他文件
- 用于 Windows 恢复环境的“安全操作系统” (SafeOS) 的汇报
- 汇报完成功能更新所需的服务堆栈有关详细信息,请参阅服务堆栈更新。
- 最新累积 (质量) 更新
- 汇报特定于动态更新的制造商已发布的适用驱动程序
动态更新通过重新获取语言包和按需功能包来保留它们。
设备必须能够连接到 Internet 才能获取动态汇报。 在某些环境中,它不是获取动态汇报的选项。 你仍可以通过获取动态更新包并将其应用到映像,然后再在设备上启动安装程序来执行基于媒体的功能更新。
获取动态更新包
可以从 Microsoft更新目录中获取动态更新包。 在该站点上,使用右上角的搜索栏查找特定版本的动态更新包。 各种动态更新包可能不会全部出现在单个搜索的结果中,因此可能需要使用不同的关键字进行搜索才能查找所有更新。 检查结果的各个部分,确保已确定所需的文件。 下表显示了在结果中搜索或查找的键值。
Windows 11版本 22H2 及更高版本的动态更新包
游戏 可以区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅当给定累积更新需要时才发布服务堆栈。以下游戏适用于 Windows 11 版本 22H2。 Windows 11,版本 23H2 和 24H2 具有类似的格式。
更新包 | Title |
---|---|
安全 OS 动态更新 | 适用于 Windows 11 版本 22H2 的 YYYY-MM 安全 OS 动态更新 |
设置动态更新 | Windows 11版本 22H2 的 YYYYY-MM 安装程序动态更新 |
最新累积更新 | Windows 11版本 22H2 的 YYYY-MM 累积更新 |
服务堆栈动态更新 | Windows 11版本 22H2 的 YYYYY-MM 服务堆栈更新 |
Windows 11版本 21H2 动态更新包
需要标题、产品和说明来区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅在必要时作为给定累积更新的先决条件单独发布服务堆栈。
更新包 | Title | 产品 | 描述 |
---|---|---|---|
安全 OS 动态更新 | 适用于Windows 11的 YYYY-MM 动态更新 | Windows 安全 OS 动态更新 | ComponentUpdate |
设置动态更新 | 适用于Windows 11的 YYYY-MM 动态更新 | Windows 10及更高版本的动态更新 | SetupUpdate |
最新累积更新 | Windows 11的 YYYY-MM 累积更新 | ||
服务堆栈动态更新 | Windows 11版本 21H2 的 YYYYY-MM 服务堆栈更新 |
Windows 10版本 22H2 动态更新包
需要标题、产品和说明来区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅在必要时作为给定累积更新的先决条件单独发布服务堆栈。
更新包 | Title | 产品 | 描述 |
---|---|---|---|
安全 OS 动态更新 | 适用于 Windows 10 版本 22H2 的 YYYYY-MM 动态更新 | Windows 安全 OS 动态更新 | ComponentUpdate |
设置动态更新 | 适用于 Windows 10 版本 22H2 的 YYYYY-MM 动态更新 | Windows 10及更高版本的动态更新 | SetupUpdate |
最新累积更新 | Windows 10 版本 22H2 的 YYYY-MM 累积更新 | ||
服务堆栈动态更新 | Windows 10版本 22H2 的 YYYY-MM 服务堆栈更新 |
如果要使用其他语言或按需功能自定义映像,请从 批量许可服务中心下载补充媒体 ISO 文件。 例如,如果对设备禁用动态更新,并且用户需要特定的按需功能,则可以将这些功能预安装到映像中。
更新 Windows 安装媒体
正确更新安装媒体涉及对多个不同目标执行许多操作, (映像文件) 。 某些操作会针对不同的目标重复执行。 目标图像文件包括:
- Windows 预安装环境 (WinPE) :用于安装、部署和修复 Windows 操作系统的小型操作系统
- Windows 恢复环境 (WinRE) :修复操作系统无法启动的常见原因。 WinRE 基于 WinPE,可以使用其他驱动程序、语言、可选包和其他故障排除或诊断工具进行自定义。
- Windows 操作系统:存储在 \sources\install.wim 中的一个或多个 Windows 版本
- Windows 安装媒体:Windows 安装媒体中文件和文件夹的完整集合。 例如,\sources 文件夹、\boot 文件夹、Setup.exe 等。
下表显示了将各种任务应用于文件的正确顺序。 例如,完整序列从将服务堆栈更新添加到 WinRE (1) 开始,最后将启动管理器从 WinPE 添加到新媒体 (28) 。
任务 | WinRE (winre.wim) | 操作系统 (install.wim) | WinPE (boot.wim) | 新媒体 |
---|---|---|---|---|
添加服务堆栈动态更新 | 1 | 9 | 17 | |
添加语言包 | 2 | 10 | 18 | |
添加本地化的可选包 | 3 | 19 | ||
添加字体支持 | 4 | 20 | ||
添加文本转语音 | 5 | 21 | ||
更新 Lang.ini | 22 | |||
按需添加功能 | 11 | |||
添加安全 OS 动态更新 | 6 | |||
添加安装程序动态更新 | 26 | |||
从 WinPE 添加 setup.exe 和 setuphost.exe | 27 | |||
从 WinPE 添加启动管理器 | 28 | |||
添加最新的累积更新 | 12 | 23 | ||
清理映像 | 7 | 13 | 24 | |
添加可选组件 | 14 | |||
添加 .NET 和 .NET 累积更新 | 15 | |||
导出映像 | 8 | 16 | 25 |
注意
从 2021 年 2 月开始,最新的累积更新和服务堆栈更新将合并并分发到 Microsoft 更新目录中,作为新的组合累积更新。 对于需要服务堆栈更新以更新安装媒体的步骤 1、9 和 18,应使用组合累积更新。 有关组合累积更新的详细信息,请参阅 服务堆栈更新。
注意
Microsoft将通过“更新以删除 Adobe Flash Player”KB4577586从 Windows 中删除 Flash 组件。 还可以通过在步骤 20 到 21 之间的目录) 上提供的KB4577586 (部署更新,随时删除 Flash。 自 2021 年 7 月起,KB4577586,“删除 Adobe Flash Player 的更新”将包含在Windows 10版本 1607 和 1507 的最新累积更新中。 此更新还将包含在每月汇总以及适用于 Windows 8.1、Windows Server 2012 和 Windows Embedded 8 Standard 的仅限安全的更新中。 有关详细信息,请参阅 Adobe Flash Player 终止支持更新。
多个 Windows 版本
main操作系统文件 (install.wim) 可能包含多个版本的 Windows。 根据索引部署它,可能只需要给定版本的更新。 或者,可能是所有版本都需要更新。 此外,请确保在按需功能之前安装语言,并且始终最后应用最新的累积更新。
其他语言和功能
无需向映像添加更多语言和功能来完成更新,但可以自定义映像的语言、可选组件和按需功能(超出起始映像中的语言)。 添加更多语言和功能时,请务必按正确的顺序进行这些更改:首先应用服务堆栈更新,然后是语言添加,然后添加功能,最后是最新的累积更新。 在本例中,提供的示例脚本安装第二种语言 (日语 (ja-JP) ) 。 由于此语言由 lp.cab 提供支持,因此无需添加语言体验包。 日语将添加到main操作系统和恢复环境,以允许用户使用日语查看恢复屏幕。 这包括添加恢复映像中当前安装的包的本地化版本。
可选组件和 .NET 功能可以脱机安装,但这样做会创建需要设备重启的挂起操作。 因此,对执行映像清理的调用将失败。 有两个选项可避免清理失败。 一种选择是跳过映像清理步骤,但这会导致更大的 install.wim。 另一个选项是在清理后、导出前的步骤中安装 .NET 和可选组件。 这是示例脚本中的 选项。 执行此操作后,必须从原始 install.wim (开始,在下次 ((例如下个月) )维护或更新映像时,不会) 挂起的操作。
检查点累积更新
从 Windows 11 版本 24H2 开始,最新的累积更新可能具有首先安装所需的先决条件累积更新。 这些更新称为检查点累积更新。 在这些情况下,累积更新文件级别差异基于以前的累积更新而不是 Windows RTM 版本。 好处是更新包更小,安装速度更快。 从 Microsoft更新目录获取最新的累积更新时,可从下载按钮获取检查点累积更新。 此外,累积更新知识库文章将提供其他信息。
若要在维护 Windows OS (步骤 9 & 12) 和 WinPE (步骤 17 & 23) 时安装检查点 () ,请调用 Add-WindowsPackage
目标累积更新。 中的 -PackagePath
文件夹将用于根据需要发现和安装一个或多个检查点。 文件夹中只应包含 -PackagePath
目标累积更新和检查点累积更新。 将处理修订 <= 目标累积更新的累积更新包。 如果不使用其他语言和/或可选功能自定义映像,则首先) 对 (检查点累积更新的单独调用 Add-WindowsPackage
可用于上述步骤 9 & 17。 无法对步骤 12 和 23 使用单独的调用。
Windows PowerShell脚本将动态汇报应用于现有映像
这些示例仅用于说明,因此缺少错误处理。 该脚本假定以下包本地存储在此文件夹结构中:
文件夹 | 描述 |
---|---|
C:\mediaRefresh | 包含 PowerShell 脚本的父文件夹 |
C:\mediaRefresh\oldMedia | 包含要刷新的原始媒体的文件夹。 例如, 包含 Setup.exe 和 \sources 文件夹。 |
C:\mediaRefresh\newMedia | 将包含更新媒体的文件夹。 它从 \oldMedia 复制,然后用作所有更新和清理操作的目标。 |
入门
脚本首先声明全局变量并创建用于装载映像的文件夹。 然后,创建原始媒体的副本(从 \oldMedia 到 \newMedia),在出现脚本错误并且必须从已知状态开始时保留原始媒体。 此外,它还提供旧媒体和新媒体的比较,以评估更改。 若要确保新媒体更新,请确保它们不是只读的。
#Requires -RunAsAdministrator
function Get-TS { return "{0:HH:mm:ss}" -f [DateTime]::Now }
Write-Output "$(Get-TS): Starting media refresh"
# Declare language for showcasing adding optional localized components
$LANG = "ja-jp"
$LANG_FONT_CAPABILITY = "jpan"
# Declare media for FOD and LPs
# Note: Starting with Windows 11, version 21H2, the language pack (LANGPACK) ISO has been superseded by the FOD ISO.
# Language packs and the \Windows Preinstallation Environment packages are part of the LOF ISO.
# If you are using this script for Windows 10, modify to mount and use the LANGPACK ISO.
$FOD_ISO_PATH = "C:\mediaRefresh\packages\FOD-PACKAGES_OEM_PT1_amd64fre_MULTI.iso"
# Declare Dynamic Update packages. A dedicated folder is used for the latest cumulative update, and as needed
# checkpoint cumulative updates.
$LCU_PATH = "C:\mediaRefresh\packages\CU\LCU.msu"
$SSU_PATH = "C:\mediaRefresh\packages\Other\SSU_DU.msu"
$SETUP_DU_PATH = "C:\mediaRefresh\packages\Other\Setup_DU.cab"
$SAFE_OS_DU_PATH = "C:\mediaRefresh\packages\Other\SafeOS_DU.cab"
$DOTNET_CU_PATH = "C:\mediaRefresh\packages\Other\DotNet_CU.msu"
# Declare folders for mounted images and temp files
$MEDIA_OLD_PATH = "C:\mediaRefresh\oldMedia"
$MEDIA_NEW_PATH = "C:\mediaRefresh\newMedia"
$WORKING_PATH = "C:\mediaRefresh\temp"
$MAIN_OS_MOUNT = "C:\mediaRefresh\temp\MainOSMount"
$WINRE_MOUNT = "C:\mediaRefresh\temp\WinREMount"
$WINPE_MOUNT = "C:\mediaRefresh\temp\WinPEMount"
# Mount the Features on Demand ISO
Write-Output "$(Get-TS): Mounting FOD ISO"
$FOD_ISO_DRIVE_LETTER = (Mount-DiskImage -ImagePath $FOD_ISO_PATH -ErrorAction stop | Get-Volume).DriveLetter
# Note: Starting with Windows 11, version 21H2, the correct path for main OS language and optional features
# moved to \LanguagesAndOptionalFeatures instead of the root. For Windows 10, use $FOD_PATH = $FOD_ISO_DRIVE_LETTER + ":\"
$FOD_PATH = $FOD_ISO_DRIVE_LETTER + ":\LanguagesAndOptionalFeatures"
# Declare language related cabs
$WINPE_OC_PATH = "$FOD_ISO_DRIVE_LETTER`:\Windows Preinstallation Environment\x64\WinPE_OCs"
$WINPE_OC_LANG_PATH = "$WINPE_OC_PATH\$LANG"
$WINPE_OC_LANG_CABS = Get-ChildItem $WINPE_OC_LANG_PATH -Name
$WINPE_OC_LP_PATH = "$WINPE_OC_LANG_PATH\lp.cab"
$WINPE_FONT_SUPPORT_PATH = "$WINPE_OC_PATH\WinPE-FontSupport-$LANG.cab"
$WINPE_SPEECH_TTS_PATH = "$WINPE_OC_PATH\WinPE-Speech-TTS.cab"
$WINPE_SPEECH_TTS_LANG_PATH = "$WINPE_OC_PATH\WinPE-Speech-TTS-$LANG.cab"
$OS_LP_PATH = "$FOD_PATH\Microsoft-Windows-Client-Language-Pack_x64_$LANG.cab"
# Create folders for mounting images and storing temporary files
New-Item -ItemType directory -Path $WORKING_PATH -ErrorAction Stop | Out-Null
New-Item -ItemType directory -Path $MAIN_OS_MOUNT -ErrorAction stop | Out-Null
New-Item -ItemType directory -Path $WINRE_MOUNT -ErrorAction stop | Out-Null
New-Item -ItemType directory -Path $WINPE_MOUNT -ErrorAction stop | Out-Null
# Keep the original media, make a copy of it for the new, updated media.
Write-Output "$(Get-TS): Copying original media to new media path"
Copy-Item -Path $MEDIA_OLD_PATH"\*" -Destination $MEDIA_NEW_PATH -Force -Recurse -ErrorAction stop | Out-Null
Get-ChildItem -Path $MEDIA_NEW_PATH -Recurse | Where-Object { -not $_.PSIsContainer -and $_.IsReadOnly } | ForEach-Object { $_.IsReadOnly = $false }
更新 WinRE 和每个main操作系统 Windows 版本
该脚本将更新 main 操作系统文件中的每个 Windows 版本, (install.wim) 。 对于每个版本,将装载main OS 映像。
对于第一个映像,Winre.wim 将复制到工作文件夹并装载。 然后,它应用服务堆栈动态更新,因为它的组件用于更新其他组件。 由于脚本可以选择添加日语,因此它会将语言包添加到映像,并安装 Winre.wim 中已安装的所有可选包的日语版本。 然后,它应用安全 OS 动态更新包。 它通过清理和导出图像以减小图像大小完成。
接下来,对于装载的 OS 映像,脚本首先应用服务堆栈动态更新。 然后,它添加日语支持,然后添加日语功能。 与动态更新包不同,它使用 Add-WindowsCapability
添加这些功能。 有关此类功能及其关联功能名称的完整列表,请参阅 按需可用功能。 现在是启用其他可选组件或添加其他按需功能的时候了。 如果此类功能具有关联的累积更新 (例如 .NET) ,则是时候应用这些更新了。 然后,该脚本继续应用最新的累积更新。 最后,脚本将清理并导出映像。 可以脱机安装可选组件以及 .NET 功能,但这需要重启设备。 这就是脚本在清理后和导出之前安装 .NET 和可选组件的原因。
对于main操作系统文件中的每个 Windows 版本,都会重复此过程。 为了减小大小,将保存第一个映像中的服务 Winre.wim 文件,并用于更新每个后续 Windows 版本。 这会减小 install.wim 的最终大小。
#
# Update each main OS Windows image including the Windows Recovery Environment (WinRE)
#
# Get the list of images contained within the main OS
$WINOS_IMAGES = Get-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\install.wim"
Foreach ($IMAGE in $WINOS_IMAGES) {
# first mount the main OS image
Write-Output "$(Get-TS): Mounting main OS, image index $($IMAGE.ImageIndex)"
Mount-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\install.wim" -Index $IMAGE.ImageIndex -Path $MAIN_OS_MOUNT -ErrorAction stop| Out-Null
if ($IMAGE.ImageIndex -eq "1") {
#
# update Windows Recovery Environment (WinRE) within this OS image
#
Copy-Item -Path $MAIN_OS_MOUNT"\windows\system32\recovery\winre.wim" -Destination $WORKING_PATH"\winre.wim" -Force -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Mounting WinRE"
Mount-WindowsImage -ImagePath $WORKING_PATH"\winre.wim" -Index 1 -Path $WINRE_MOUNT -ErrorAction stop | Out-Null
# Add servicing stack update (Step 1 from the table)
# Depending on the Windows release that you are updating, there are 2 different approaches for updating the servicing stack
# The first approach is to use the combined cumulative update. This is for Windows releases that are shipping a combined
# cumulative update that includes the servicing stack updates (i.e. SSU + LCU are combined). Windows 11, version 21H2 and
# Windows 11, version 22H2 are examples. In these cases, the servicing stack update is not published seperately; the combined
# cumulative update should be used for this step. However, in hopefully rare cases, there may breaking change in the combined
# cumulative update format, that requires a standalone servicing stack update to be published, and installed first before the
# combined cumulative update can be installed.
# This is the code to handle the rare case that the SSU is published and required for the combined cumulative update
# Write-Output "$(Get-TS): Adding package $SSU_PATH"
# Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $SSU_PATH | Out-Null
# Now, attempt the combined cumulative update.
# There is a known issue where the servicing stack update is installed, but the cumulative update will fail. This error should
# be caught and ignored, as the last step will be to apply the Safe OS update and thus the image will be left with the correct
# packages installed.
Write-Output "$(Get-TS): Adding package $LCU_PATH to WinRE"
try
{
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $LCU_PATH | Out-Null
}
Catch
{
$theError = $_
Write-Output "$(Get-TS): $theError"
if ($theError.Exception -like "*0x8007007e*") {
Write-Output "$(Get-TS): This failure is a known issue with combined cumulative update, we can ignore."
}
else {
throw
}
}
# The second approach for Step 1 is for Windows releases that have not adopted the combined cumulative update
# but instead continue to have a seperate servicing stack update published. In this case, we'll install the SSU
# update. This second approach is commented out below.
# Write-Output "$(Get-TS): Adding package $SSU_PATH"
# Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $SSU_PATH | Out-Null
#
# Optional: Add the language to recovery environment
#
# Install lp.cab cab
Write-Output "$(Get-TS): Adding package $WINPE_OC_LP_PATH to WinRE"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $WINPE_OC_LP_PATH -ErrorAction stop | Out-Null
# Install language cabs for each optional package installed
$WINRE_INSTALLED_OC = Get-WindowsPackage -Path $WINRE_MOUNT
Foreach ($PACKAGE in $WINRE_INSTALLED_OC) {
if ( ($PACKAGE.PackageState -eq "Installed") -and ($PACKAGE.PackageName.startsWith("WinPE-")) -and ($PACKAGE.ReleaseType -eq "FeaturePack") ) {
$INDEX = $PACKAGE.PackageName.IndexOf("-Package")
if ($INDEX -ge 0) {
$OC_CAB = $PACKAGE.PackageName.Substring(0, $INDEX) + "_" + $LANG + ".cab"
if ($WINPE_OC_LANG_CABS.Contains($OC_CAB)) {
$OC_CAB_PATH = Join-Path $WINPE_OC_LANG_PATH $OC_CAB
Write-Output "$(Get-TS): Adding package $OC_CAB_PATH to WinRE"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $OC_CAB_PATH -ErrorAction stop | Out-Null
}
}
}
}
# Add font support for the new language
if ( (Test-Path -Path $WINPE_FONT_SUPPORT_PATH) ) {
Write-Output "$(Get-TS): Adding package $WINPE_FONT_SUPPORT_PATH to WinRE"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $WINPE_FONT_SUPPORT_PATH -ErrorAction stop | Out-Null
}
# Add TTS support for the new language
if (Test-Path -Path $WINPE_SPEECH_TTS_PATH) {
if ( (Test-Path -Path $WINPE_SPEECH_TTS_LANG_PATH) ) {
Write-Output "$(Get-TS): Adding package $WINPE_SPEECH_TTS_PATH to WinRE"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $WINPE_SPEECH_TTS_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding package $WINPE_SPEECH_TTS_LANG_PATH to WinRE"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $WINPE_SPEECH_TTS_LANG_PATH -ErrorAction stop | Out-Null
}
}
# Add Safe OS
Write-Output "$(Get-TS): Adding package $SAFE_OS_DU_PATH to WinRE"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $SAFE_OS_DU_PATH -ErrorAction stop | Out-Null
# Perform image cleanup
Write-Output "$(Get-TS): Performing image cleanup on WinRE"
DISM /image:$WINRE_MOUNT /cleanup-image /StartComponentCleanup /ResetBase /Defer | Out-Null
# Dismount
Dismount-WindowsImage -Path $WINRE_MOUNT -Save -ErrorAction stop | Out-Null
# Export
Write-Output "$(Get-TS): Exporting image to $WORKING_PATH\winre.wim"
Export-WindowsImage -SourceImagePath $WORKING_PATH"\winre.wim" -SourceIndex 1 -DestinationImagePath $WORKING_PATH"\winre2.wim" -ErrorAction stop | Out-Null
}
Copy-Item -Path $WORKING_PATH"\winre2.wim" -Destination $MAIN_OS_MOUNT"\windows\system32\recovery\winre.wim" -Force -ErrorAction stop | Out-Null
#
# update Main OS
#
# Add servicing stack update (Step 18 from the table)
# Depending on the Windows release that you are updating, there are 2 different approaches for updating the servicing stack
# The first approach is to use the combined cumulative update. This is for Windows releases that are shipping a combined cumulative update that
# includes the servicing stack updates (i.e. SSU + LCU are combined). Windows 11, version 21H2 and Windows 11, version 22H2 are examples. In these
# cases, the servicing stack update is not published seperately; the combined cumulative update should be used for this step. However, in hopefully
# rare cases, there may breaking change in the combined cumulative update format, that requires a standalone servicing stack update to be published,
# and installed first before the combined cumulative update can be installed.
# This is the code to handle the rare case that the SSU is published and required for the combined cumulative update
# Write-Output "$(Get-TS): Adding package $SSU_PATH"
# Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $SSU_PATH | Out-Null
# Now, attempt the combined cumulative update. Unlike WinRE and WinPE, we don't need to check for error 0x8007007e
Write-Output "$(Get-TS): Adding package $LCU_PATH to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $LCU_PATH | Out-Null
# The second approach for Step 18 is for Windows releases that have not adopted the combined cumulative update
# but instead continue to have a seperate servicing stack update published. In this case, we'll install the SSU
# update. This second approach is commented out below.
# Write-Output "$(Get-TS): Adding package $SSU_PATH to main OS, index $($IMAGE.ImageIndex)"
# Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $SSU_PATH | Out-Null
# Optional: Add language to main OS
Write-Output "$(Get-TS): Adding package $OS_LP_PATH to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $OS_LP_PATH -ErrorAction stop | Out-Null
# Optional: Add a Features on Demand to the image
Write-Output "$(Get-TS): Adding language FOD: Language.Fonts.Jpan~~~und-JPAN~0.0.1.0 to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "Language.Fonts.$LANG_FONT_CAPABILITY~~~und-$LANG_FONT_CAPABILITY~0.0.1.0" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding language FOD: Language.Basic~~~$LANG~0.0.1.0 to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "Language.Basic~~~$LANG~0.0.1.0" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding language FOD: Language.OCR~~~$LANG~0.0.1.0 to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "Language.OCR~~~$LANG~0.0.1.0" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding language FOD: Language.Handwriting~~~$LANG~0.0.1.0 to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "Language.Handwriting~~~$LANG~0.0.1.0" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding language FOD: Language.TextToSpeech~~~$LANG~0.0.1.0 to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "Language.TextToSpeech~~~$LANG~0.0.1.0" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding language FOD: Language.Speech~~~$LANG~0.0.1.0 to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "Language.Speech~~~$LANG~0.0.1.0" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
# Note: If I wanted to enable additional Features on Demand, I'd add these here.
# Add latest cumulative update
Write-Output "$(Get-TS): Adding package $LCU_PATH to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $LCU_PATH -ErrorAction stop | Out-Null
# Perform image cleanup
Write-Output "$(Get-TS): Performing image cleanup on main OS, index $($IMAGE.ImageIndex)"
DISM /image:$MAIN_OS_MOUNT /cleanup-image /StartComponentCleanup | Out-Null
#
# Note: If I wanted to enable additional Optional Components, I'd add these here.
# In addition, we'll add .NET 3.5 here as well. Both .NET and Optional Components might require
# the image to be booted, and thus if we tried to cleanup after installation, it would fail.
#
Write-Output "$(Get-TS): Adding NetFX3~~~~ to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsCapability -Name "NetFX3~~~~" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
# Add .NET Cumulative Update
Write-Output "$(Get-TS): Adding package $DOTNET_CU_PATH to main OS, index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $DOTNET_CU_PATH -ErrorAction stop | Out-Null
# Dismount
Dismount-WindowsImage -Path $MAIN_OS_MOUNT -Save -ErrorAction stop | Out-Null
# Export
Write-Output "$(Get-TS): Exporting image to $WORKING_PATH\install2.wim"
Export-WindowsImage -SourceImagePath $MEDIA_NEW_PATH"\sources\install.wim" -SourceIndex $IMAGE.ImageIndex -DestinationImagePath $WORKING_PATH"\install2.wim" -ErrorAction stop | Out-Null
}
Move-Item -Path $WORKING_PATH"\install2.wim" -Destination $MEDIA_NEW_PATH"\sources\install.wim" -Force -ErrorAction stop | Out-Null
更新 WinPE
此脚本类似于更新 WinRE 的脚本,但会装载 Boot.wim,最后应用具有最新累积更新的包并保存。 它针对 Boot.wim 中的所有映像重复此操作,通常为两个映像。 它首先应用服务堆栈动态更新。 由于脚本使用日语自定义此媒体,因此它会从语言包 ISO 上的 WinPE 文件夹中安装语言包。 此外,它还向语音添加了字体支持和文本 (TTS) 支持。 由于脚本正在添加新语言,因此它会重新生成 lang.ini,用于标识映像中安装的语言。 对于第二个映像,我们将保存 setup.exe 和 setuphost.exe 以供以后使用,以确保这些版本与安装媒体中的 \sources\setup.exe 和 \sources\setuphost.exe 版本匹配。 如果这些二进制文件不相同,Windows 安装程序将在安装过程中失败。 我们还将保存服务启动管理器文件,供稍后在脚本中使用。 最后,该脚本清理并导出 Boot.wim,并将其复制回新媒体。
#
# update Windows Preinstallation Environment (WinPE)
#
# Get the list of images contained within WinPE
$WINPE_IMAGES = Get-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\boot.wim"
Foreach ($IMAGE in $WINPE_IMAGES) {
# update WinPE
Write-Output "$(Get-TS): Mounting WinPE, image index $($IMAGE.ImageIndex)"
Mount-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\boot.wim" -Index $IMAGE.ImageIndex -Path $WINPE_MOUNT -ErrorAction stop | Out-Null
# Add servicing stack update (Step 9 from the table)
# Depending on the Windows release that you are updating, there are 2 different approaches for updating the servicing stack
# The first approach is to use the combined cumulative update. This is for Windows releases that are shipping a combined
# cumulative update that includes the servicing stack updates (i.e. SSU + LCU are combined). Windows 11, version 21H2 and
# Windows 11, version 22H2 are examples. In these cases, the servicing stack update is not published separately; the combined
# cumulative update should be used for this step. However, in hopefully rare cases, there may breaking change in the combined
# cumulative update format, that requires a standalone servicing stack update to be published, and installed first before the
# combined cumulative update can be installed.
# This is the code to handle the rare case that the SSU is published and required for the combined cumulative update
# Write-Output "$(Get-TS): Adding package $SSU_PATH"
# Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $SSU_PATH | Out-Null
# Now, attempt the combined cumulative update.
# There is a known issue where the servicing stack update is installed, but the cumulative update will fail.
# This error should be caught and ignored, as the last step will be to apply the cumulative update
# (or in this case the combined cumulative update) and thus the image will be left with the correct packages installed.
try
{
Write-Output "$(Get-TS): Adding package $LCU_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $LCU_PATH | Out-Null
}
Catch
{
$theError = $_
Write-Output "$(Get-TS): $theError"
if ($theError.Exception -like "*0x8007007e*") {
Write-Output "$(Get-TS): This failure is a known issue with combined cumulative update, we can ignore."
}
else {
throw
}
}
# The second approach for Step 9 is for Windows releases that have not adopted the combined cumulative update
# but instead continue to have a separate servicing stack update published. In this case, we'll install the SSU
# update. This second approach is commented out below.
# Write-Output "$(Get-TS): Adding package $SSU_PATH"
# Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $SSU_PATH | Out-Null
# Install lp.cab cab
Write-Output "$(Get-TS): Adding package $WINPE_OC_LP_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $WINPE_OC_LP_PATH -ErrorAction stop | Out-Null
# Install language cabs for each optional package installed
$WINPE_INSTALLED_OC = Get-WindowsPackage -Path $WINPE_MOUNT
Foreach ($PACKAGE in $WINPE_INSTALLED_OC) {
if ( ($PACKAGE.PackageState -eq "Installed") -and ($PACKAGE.PackageName.startsWith("WinPE-")) -and ($PACKAGE.ReleaseType -eq "FeaturePack") ) {
$INDEX = $PACKAGE.PackageName.IndexOf("-Package")
if ($INDEX -ge 0) {
$OC_CAB = $PACKAGE.PackageName.Substring(0, $INDEX) + "_" + $LANG + ".cab"
if ($WINPE_OC_LANG_CABS.Contains($OC_CAB)) {
$OC_CAB_PATH = Join-Path $WINPE_OC_LANG_PATH $OC_CAB
Write-Output "$(Get-TS): Adding package $OC_CAB_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $OC_CAB_PATH -ErrorAction stop | Out-Null
}
}
}
}
# Add font support for the new language
if ( (Test-Path -Path $WINPE_FONT_SUPPORT_PATH) ) {
Write-Output "$(Get-TS): Adding package $WINPE_FONT_SUPPORT_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $WINPE_FONT_SUPPORT_PATH -ErrorAction stop | Out-Null
}
# Add TTS support for the new language
if (Test-Path -Path $WINPE_SPEECH_TTS_PATH) {
if ( (Test-Path -Path $WINPE_SPEECH_TTS_LANG_PATH) ) {
Write-Output "$(Get-TS): Adding package $WINPE_SPEECH_TTS_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $WINPE_SPEECH_TTS_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Adding package $WINPE_SPEECH_TTS_LANG_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $WINPE_SPEECH_TTS_LANG_PATH -ErrorAction stop | Out-Null
}
}
# Generates a new Lang.ini file which is used to define the language packs inside the image
if ( (Test-Path -Path $WINPE_MOUNT"\sources\lang.ini") ) {
Write-Output "$(Get-TS): Updating lang.ini"
DISM /image:$WINPE_MOUNT /Gen-LangINI /distribution:$WINPE_MOUNT | Out-Null
}
# Add latest cumulative update
Write-Output "$(Get-TS): Adding package $LCU_PATH to WinPE, image index $($IMAGE.ImageIndex)"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $LCU_PATH -ErrorAction stop | Out-Null
# Perform image cleanup
Write-Output "$(Get-TS): Performing image cleanup on WinPE, image index $($IMAGE.ImageIndex)"
DISM /image:$WINPE_MOUNT /cleanup-image /StartComponentCleanup /ResetBase /Defer | Out-Null
if ($IMAGE.ImageIndex -eq "2") {
# Save setup.exe for later use. This will address possible binary mismatch with the version in the main OS \sources folder
Copy-Item -Path $WINPE_MOUNT"\sources\setup.exe" -Destination $WORKING_PATH"\setup.exe" -Force -ErrorAction stop | Out-Null
# Save setuphost.exe for later use. This will address possible binary mismatch with the version in the main OS \sources folder
# This is only required starting with Windows 11 version 24H2
$TEMP = Get-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\boot.wim" -Index $IMAGE.ImageIndex
if ([System.Version]$TEMP.Version -ge [System.Version]"10.0.26100") {
Copy-Item -Path $WINPE_MOUNT"\sources\setuphost.exe" -Destination $WORKING_PATH"\setuphost.exe" -Force -ErrorAction stop | Out-Null
}
else {
Write-Output "$(Get-TS): Skipping copy of setuphost.exe; image version $($TEMP.Version)"
}
# Save serviced boot manager files later copy to the root media.
Copy-Item -Path $WINPE_MOUNT"\Windows\boot\efi\bootmgfw.efi" -Destination $WORKING_PATH"\bootmgfw.efi" -Force -ErrorAction stop | Out-Null
Copy-Item -Path $WINPE_MOUNT"\Windows\boot\efi\bootmgr.efi" -Destination $WORKING_PATH"\bootmgr.efi" -Force -ErrorAction stop | Out-Null
}
# Dismount
Dismount-WindowsImage -Path $WINPE_MOUNT -Save -ErrorAction stop | Out-Null
#Export WinPE
Write-Output "$(Get-TS): Exporting image to $WORKING_PATH\boot2.wim"
Export-WindowsImage -SourceImagePath $MEDIA_NEW_PATH"\sources\boot.wim" -SourceIndex $IMAGE.ImageIndex -DestinationImagePath $WORKING_PATH"\boot2.wim" -ErrorAction stop | Out-Null
}
Move-Item -Path $WORKING_PATH"\boot2.wim" -Destination $MEDIA_NEW_PATH"\sources\boot.wim" -Force -ErrorAction stop | Out-Null
更新剩余的媒体文件
脚本的这一部分将更新安装程序文件。 它只需将安装程序动态更新包中的单个文件复制到新媒体。 此步骤根据需要引入更新的安装程序文件,以及最新的兼容性数据库和替换组件清单。 此脚本还使用以前从 WinPE 保存的版本对 setup.exe、setuphost.exe 和启动管理器文件执行最终替换。
#
# update remaining files on media
#
# Add Setup DU by copy the files from the package into the newMedia
Write-Output "$(Get-TS): Adding package $SETUP_DU_PATH"
cmd.exe /c $env:SystemRoot\System32\expand.exe $SETUP_DU_PATH -F:* $MEDIA_NEW_PATH"\sources" | Out-Null
# Copy setup.exe from boot.wim, saved earlier.
Write-Output "$(Get-TS): Copying $WORKING_PATH\setup.exe to $MEDIA_NEW_PATH\sources\setup.exe"
Copy-Item -Path $WORKING_PATH"\setup.exe" -Destination $MEDIA_NEW_PATH"\sources\setup.exe" -Force -ErrorAction stop | Out-Null
# Copy setuphost.exe from boot.wim, saved earlier.
if (Test-Path -Path $WORKING_PATH"\setuphost.exe") {
Write-Output "$(Get-TS): Copying $WORKING_PATH\setuphost.exe to $MEDIA_NEW_PATH\sources\setuphost.exe"
Copy-Item -Path $WORKING_PATH"\setuphost.exe" -Destination $MEDIA_NEW_PATH"\sources\setuphost.exe" -Force -ErrorAction stop | Out-Null
}
# Copy bootmgr files from boot.wim, saved earlier.
$MEDIA_NEW_FILES = Get-ChildItem $MEDIA_NEW_PATH -Force -Recurse -Filter b*.efi
Foreach ($File in $MEDIA_NEW_FILES){
if (($File.Name -ieq "bootmgfw.efi") -or ($File.Name -ieq "bootx64.efi") -or ($File.Name -ieq "bootia32.efi") -or ($File.Name -ieq "bootaa64.efi"))
{
Write-Output "$(Get-TS): Copying $WORKING_PATH\bootmgfw.efi to $($File.FullName)"
Copy-Item -Path $WORKING_PATH"\bootmgfw.efi" -Destination $File.FullName -Force -ErrorAction stop | Out-Null
}
elseif ($File.Name -ieq "bootmgr.efi")
{
Write-Output "$(Get-TS): Copying $WORKING_PATH\bootmgr.efi to $($File.FullName)"
Copy-Item -Path $WORKING_PATH"\bootmgr.efi" -Destination $File.FullName -Force -ErrorAction stop | Out-Null
}
}
整理
最后一步,该脚本删除临时文件的工作文件夹,并卸载语言包和按需功能 ISO。
#
# Perform final cleanup
#
# Remove our working folder
Remove-Item -Path $WORKING_PATH -Recurse -Force -ErrorAction stop | Out-Null
# Dismount ISO images
Write-Output "$(Get-TS): Dismounting ISO images"
Dismount-DiskImage -ImagePath $FOD_ISO_PATH -ErrorAction stop | Out-Null
Write-Output "$(Get-TS): Media refresh completed!"