使用动态更新更新 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 Server 2025 动态更新包

游戏 可以区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅当给定累积更新需要时才发布服务堆栈。

更新包 Title
安全 OS 动态更新 适用于 Microsoft 服务器操作系统版本 24H2 的 YYYY-MM 安全 OS 动态更新
设置动态更新 适用于 Microsoft 服务器操作系统版本 24H2 的 YYYY-MM 安装程序动态更新
最新累积更新 Microsoft服务器操作系统版本 24H2 的 YYYY-MM 累积更新
服务堆栈动态更新 Microsoft服务器操作系统版本 24H2 的 YYYYY-MM 服务堆栈更新

Windows Server 版本 23H2 动态更新包

游戏 可以区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅当给定累积更新需要时才发布服务堆栈。 Azure Stack HCI 版本 23H2 采用类似的格式。

更新包 Title
安全 OS 动态更新 适用于 Microsoft 服务器操作系统版本 23H2 的 YYYY-MM 安全 OS 动态更新
设置动态更新 Microsoft服务器操作系统版本 23H2 的 YYYYY-MM 安装程序动态更新
最新累积更新 Microsoft服务器操作系统版本 23H2 的 YYYY-MM 累积更新
服务堆栈动态更新 Microsoft服务器操作系统版本 23H2 的 YYYY-MM 服务堆栈更新

Azure Stack HCI 版本 22H2 动态更新包

需要标题产品和说明来区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅在必要时作为给定累积更新的先决条件单独发布服务堆栈。

更新包 Title 产品 描述
安全 OS 动态更新 Microsoft服务器操作系统版本 22H2 的 YYYYY-MM 动态更新 Windows 安全 OS 动态更新 ComponentUpdate
设置动态更新 Microsoft服务器操作系统版本 22H2 的 YYYYY-MM 动态更新 Windows 10及更高版本的动态更新 SetupUpdate
最新累积更新 Microsoft服务器操作系统版本 22H2 的 YYYY-MM 累积更新
服务堆栈动态更新 Microsoft服务器操作系统版本 22H2 的 YYYY-MM 服务堆栈更新

Windows Server 2022 更高版本的动态更新包

需要标题产品和说明来区分每个动态包。 最新的累积更新已嵌入服务堆栈。 仅在必要时作为给定累积更新的先决条件单独发布服务堆栈。

更新包 Title 产品 描述
安全 OS 动态更新 Microsoft服务器操作系统版本 21H2 的 YYYYY-MM 动态更新 Windows 安全 OS 动态更新 ComponentUpdate
设置动态更新 Microsoft服务器操作系统版本 21H2 的 YYYYY-MM 动态更新 Windows 10及更高版本的动态更新 SetupUpdate
最新累积更新 Microsoft服务器操作系统版本 21H2 的 YYYY-MM 累积更新
服务堆栈动态更新 Microsoft 服务器操作系统版本 21H2 的 YYYYY-MM 服务堆栈更新

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 Server 2025 开始,最新的累积更新可能具有先决条件的累积更新,需要先安装。 这些更新称为检查点累积更新。 在这些情况下,累积更新文件级别差异基于以前的累积更新而不是 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!"