使用动态更新更新 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
添加可选组件 12
添加安全 OS 动态更新 6
添加安装程序动态更新 26
从 WinPE 添加 Setup.exe 和 setuphost.exe 27
从 WinPE 添加启动管理器 28
添加最新的累积更新 13 23
清理映像 7 14 24
添加 .NET 和 .NET 累积更新 15
导出映像 8 16 25

注意

从 2021 年 2 月开始,最新的累积更新和服务堆栈更新合并在 Microsoft 更新目录中作为新的组合累积更新分发。 对于需要服务堆栈更新以更新安装媒体的步骤 1、9 和 17,应使用组合累积更新。 有关组合累积更新的详细信息,请参阅 服务堆栈更新

注意

Microsoft通过 KB4577586 删除 Adobe Flash Player 的更新从 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),在出现脚本错误并且必须从已知状态开始时保留原始媒体。 此外,它还提供旧媒体和新媒体的比较,以评估更改。 若要确保新媒体更新,请确保它们不是只读的。 该脚本还演示了添加其他语言、按需功能和可选组件。 它们不是必需的,但添加了以突出显示何时应按顺序添加它们。 从 Windows 11 版本 21H2 开始,语言包 (LANGPACK) ISO 由按需功能 ISO 取代。 语言包和 Windows 预安装环境包是按需功能 ISO 的一部分。 此外,main OS 语言和可选功能的路径将移动到 \LanguagesAndOptionalFeatures 而不是根。 如果将此脚本用于Windows 10,请修改 以装载和使用该语言包 (LANGPACK) ISO。

#Requires -RunAsAdministrator

function Get-TS { return "{0:HH:mm:ss}" -f [DateTime]::Now }

Write-Output "$(Get-TS): Starting media refresh"

# 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"
$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 media for FOD and LPs
$FOD_ISO_PATH    = "C:\mediaRefresh\packages\CLIENT_LOF_PACKAGES_OEM.iso"

# Array of Features On Demand for main OS
# This is optional to showcase where these are added
$FOD = @(
'XPS.Viewer~~~~0.0.1.0'
)

# Array of Legacy Features for main OS
# This is optional to showcase where these are added
$OC = @(
'MediaPlayback'
'WindowsMediaPlayer'
)

# 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
$FOD_PATH = $FOD_ISO_DRIVE_LETTER + ":\LanguagesAndOptionalFeatures"

# Declare language for showcasing adding optional localized components
$LANG  = "ja-jp"
$LANG_FONT_CAPABILITY = "jpan"

# 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"

# Declare folders for mounted images and temp files
$MEDIA_OLD_PATH  = "C:\mediaRefresh\oldMedia\Ge\client_professional_en-us"
$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"

# 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 将复制到工作文件夹并装载。 然后,它通过最新的累积更新应用服务堆栈,因为它的组件用于更新其他组件。 根据要更新的 Windows 版本,有两种不同的方法来更新服务堆栈。 第一种方法是使用组合累积更新。 这适用于提供组合累积更新的 Windows 版本,其中包括服务堆栈更新 (即 SSU + LCU 合并) 。 例如,Windows 11版本 21H2 和 Windows 11 版本 22H2。 在这些情况下,不会单独发布服务堆栈更新;合并的累积更新应用于此步骤。 但是,在极少数情况下,合并累积更新格式更改中可能存在中断性变更,这需要发布独立的服务堆栈更新,并首先安装,然后才能安装合并的累积更新。 由于脚本可以选择添加日语,因此它会将语言包添加到映像,并安装 Winre.wim 中已安装的所有可选包的日语版本。 然后,它应用安全 OS 动态更新包。 它通过清理和导出图像以减小图像大小完成。

接下来,对于装载的 OS 映像,脚本首先通过最新的累积更新应用服务堆栈。 然后,它添加日语支持,然后添加日语功能。 与动态更新包不同,它使用 Add-WindowsCapability 添加这些功能。 有关此类功能及其关联功能名称的完整列表,请参阅 按需可用功能。 现在是启用其他可选组件或添加其他按需功能的时候了。 如果此类功能具有关联的累积更新 (例如 .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)
        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-Warning "$(Get-TS): Failed with error 0x8007007e. This failure is a known issue with combined cumulative update, we can ignore."
            }
            else 
            {
                throw
            }
        }

        #
        # 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
        if ($LastExitCode -ne 0) 
        {
            throw "Error: Failed to perform image cleanup on WinRE. Exit code: $LastExitCode"
        }

        # 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 17 from the table). 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


    # Optional: Add language to main OS and corresponding language experience Features on Demand
    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

    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

    # Optional: Add additional Features On Demand
	For ( $index = 0; $index -lt $FOD.count; $index++)#
	{
        Write-Output "$(Get-TS): Adding $($FOD[$index]) to main OS, index $($IMAGE.ImageIndex)"
        Add-WindowsCapability -Name $($FOD[$index]) -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
	}    
    
    # Optional: Add Legacy Features
    For ( $index = 0; $index -lt $OC.count; $index++)
    {
        Write-Output "$(Get-TS): Adding $($OC[$index]) to main OS, index $($IMAGE.ImageIndex)"
        DISM /Image:$MAIN_OS_MOUNT /Enable-Feature /FeatureName:$($OC[$index]) /All | Out-Null
        if ($LastExitCode -ne 0) 
        {
            throw "Error: Failed to add $($OC[$index]) to main OS, index $($IMAGE.ImageIndex). Exit code: $LastExitCode"
        }
    }

    # 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. Some Optional Components might require the image to be booted, and thus 
    # image cleanup may fail. We'll catch and handle as a warning.
    Write-Output "$(Get-TS): Performing image cleanup on main OS, index $($IMAGE.ImageIndex)"
    DISM /image:$MAIN_OS_MOUNT /cleanup-image /StartComponentCleanup | Out-Null
    if ($LastExitCode -ne 0) 
    {
        if ($LastExitCode -eq -2146498554)  
        {       
            # We hit 0x800F0806 CBS_E_PENDING. We will ignore this with a warning
            # This is likely due to legacy components being added that require online operations.
            Write-Warning "$(Get-TS): Failed to perform image cleanup on main OS, index $($IMAGE.ImageIndex). Exit code: $LastExitCode. The operation cannot be performed until pending servicing operations are completed. The image must be booted to complete the pending servicing operation."
        }
        else
        {
            throw "Error: Failed to perform image cleanup on main OS, index $($IMAGE.ImageIndex). Exit code: $LastExitCode"
        }
    }

    # Finally, we'll add .NET 3.5 and the .NET cumulative update
    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)
    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-Warning "$(Get-TS): Failed with error 0x8007007e. This failure is a known issue with combined cumulative update, we can ignore."
        }
        else 
        {
            throw
        }
    }

    # 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
        if ($LastExitCode -ne 0) 
        {
            throw "Error: Failed to update lang.ini. Exit code: $LastExitCode"
        }
    }

    # 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 ($LastExitCode -ne 0) 
    {
        throw "Error: Failed to perform image cleanup on WinPE, image index $($IMAGE.ImageIndex). Exit code: $LastExitCode"
    }

    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
if ($LastExitCode -ne 0) 
{
    throw "Error: Failed to expand $SETUP_DU_PATH. Exit code: $LastExitCode"
}

# 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!"