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

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

更新包 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 27
从 WinPE 添加启动管理器 28
添加最新的累积更新 12 23
清理映像 7 13 24
添加可选组件 14
添加 .NET 和 .NET 累积更新 15
导出映像 8 16 25

注意

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

注意

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 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
$LCU_PATH        = "C:\mediaRefresh\packages\LCU.msu"
$SSU_PATH        = "C:\mediaRefresh\packages\SSU_DU.msu"
$SETUP_DU_PATH   = "C:\mediaRefresh\packages\Setup_DU.cab"
$SAFE_OS_DU_PATH = "C:\mediaRefresh\packages\SafeOS_DU.cab"
$DOTNET_CU_PATH  = "C:\mediaRefresh\packages\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 WinPE
$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.

        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"
        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"
                        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"
            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"
                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"
                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"
        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"
    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"
    # 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"
    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"
    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"
    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"
    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"
    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"
    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"
    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"
    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"
    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~~~~"
    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"
    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 以供以后使用,以确保此版本与安装媒体中的 \sources\setup.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
    {
        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"
    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"
                    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"
        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"
            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"
            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"
    Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $LCU_PATH -ErrorAction stop | Out-Null  

    # Perform image cleanup
    Write-Output "$(Get-TS): Performing image cleanup on WinPE"
    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 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 和启动管理器文件执行最终替换。

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