使用 Dynamic Update 更新 Windows 安裝媒體

本文說明如何在部署前取得並套用動態更新套件至現有 Windows 映像檔,並附有可用於自動化此流程的 Windows PowerShell 腳本。

每個 Windows 版本的磁碟授權媒體皆可在 VLSC) (及其他相關頻道(如 Windows Update 用戶端政策、Windows Server Update Services (WSUS) 及 Visual Studio 訂閱)中取得。 你可以使用 Dynamic Update 確保 Windows 裝置在原地升級時擁有最新的功能更新包,同時保留語言包和 FOD () 的功能隨選包,這些可能已安裝。 動態更新也免除了在原地升級過程中安裝獨立品質更新的需求。

動態更新

無論是從媒體還是連接Windows Update) 的環境開始安裝功能更新 (,動態更新都是第一步之一。 Windows 設定會聯絡 Microsoft 端點取得動態更新套件,然後將這些更新套用到你的作業系統安裝媒體。 更新套件包含以下類型的更新:

  • 匯報 Setup.exe 設定用來更新功能的其他二進位檔或其他檔案
  • 匯報用於 Windows 復原環境的 SafeOS) (的「安全作業系統」
  • 完成功能更新所需的服務堆疊匯報 欲了解更多資訊,請參見服務堆疊更新。
  • 最新的累積 (品質) 更新
  • 針對專為動態更新設計的製造商已發布的適用驅動匯報

動態更新透過重新取得語言包和隨選功能套件來保留它們。

裝置必須能夠連上網際網路才能獲得動態匯報。 在某些環境中,不能取得動態更新匯報。 你仍然可以透過取得動態更新套件,並在裝置設定前套用到映像檔來進行媒體功能更新。

取得動態更新套件

你可以從 Microsoft Update 目錄取得動態更新套件。 在該網站,使用右上角的搜尋欄找到特定版本的動態更新套件。 各種動態更新套件可能不會全部出現在單一搜尋結果中,因此你可能需要用不同關鍵字搜尋才能找到所有更新。 檢查結果的不同部分,確保你已經辨識出所需的檔案。 以下表格顯示了搜尋結果中需要尋找的關鍵值。

Windows Server 2025 動態更新套件

標題 可以區分每個動態套件。 最新的累積更新會將服務堆疊嵌入其中。 服務堆疊僅在必要於特定累積更新時發布。

更新套件 Title
安全作業系統動態更新 YYYY-MM 安全作業系統動態更新 Microsoft 伺服器作業系統版本 24H2
設定動態更新 YYYY-MM 為 Microsoft server 作業系統設定動態更新版本 24H2
最新累積更新 YYYY-MM Microsoft 伺服器作業系統版本 24H2 累積更新
Servicing stack 動態更新 YYYY-MM Microsoft 伺服器作業系統版本 24H2 的服務堆疊更新

Windows Server,版本 23H2 動態更新套件

標題 可以區分每個動態套件。 最新的累積更新會將服務堆疊嵌入其中。 服務堆疊僅在必要於特定累積更新時發布。 Azure Stack HCI 版本 23H2 格式類似。

更新套件 Title
安全作業系統動態更新 YYYY-MM Microsoft 伺服器作業系統動態更新 23H2 版本
設定動態更新 YYYY-MM 為 Microsoft 伺服器作業系統 23H2 設定動態更新
最新累積更新 YYYY-MM Microsoft 伺服器作業系統版本 23H2 累積更新
Servicing stack 動態更新 YYYY-MM Microsoft 伺服器作業系統版本 23H2 的服務堆疊更新

Azure Stack HCI, version 22H2 Dynamic Update packages

標題產品描述是區分每個動態套件的必要條件。 最新的累積更新已將維修堆疊嵌入。 服務堆疊僅在必要時作為累積更新的前提,單獨發佈。

更新套件 Title 產品 說明
安全作業系統動態更新 YYYY-MM Microsoft 伺服器作業系統動態更新,版本 22H2 Windows 安全作業系統動態更新 ComponentUpdate
設定動態更新 YYYY-MM Microsoft 伺服器作業系統動態更新,版本 22H2 Windows 10 及更新版本的動態更新 設定更新
最新累積更新 YYYY-MM Microsoft 伺服器作業系統累積更新,版本 22H2
Servicing stack 動態更新 YYYY-MM Microsoft 伺服器作業系統服務堆疊更新,版本 22H2

Windows Server 2022 後續版動態更新套件

標題產品描述是區分每個動態套件的必要條件。 最新的累積更新已將維修堆疊嵌入。 服務堆疊僅在必要時作為累積更新的前提,單獨發佈。

更新套件 Title 產品 說明
安全作業系統動態更新 YYYY-MM Microsoft 伺服器作業系統動態更新,版本 21H2 Windows 安全作業系統動態更新 ComponentUpdate
設定動態更新 YYYY-MM Microsoft 伺服器作業系統動態更新,版本 21H2 Windows 10 及更新版本的動態更新 設定更新
最新累積更新 YYYY-MM Microsoft 伺服器作業系統累積更新,版本 21H2
Servicing stack 動態更新 YYYY-MM Microsoft 伺服器作業系統服務堆疊更新,版本 21H2

Windows 11、版本 22H2 及更新版的動態更新套件

標題 可以區分每個動態套件。 最新的累積更新會將服務堆疊嵌入其中。 服務堆疊僅在必要於特定累積更新時發布。 以下標題為 Windows 11 版本 22H2。 Windows 11、版本 23H2 與版本 24H2 採用類似格式:

更新套件 Title
安全作業系統動態更新 YYYY-MM 安全作業系統動態更新,適用於 Windows 11 版本 22H2
設定動態更新 YYYY-MM Windows 11 22H2 動態更新設定
最新累積更新 YYYY-MM Windows 11 版本 22H2 累積更新
Servicing stack 動態更新 YYYY-MM Windows 11 版本 22H2 服務堆疊更新

Windows 11,版本 21H2 動態更新套件

標題產品描述是區分每個動態套件的必要條件。 最新的累積更新已將維修堆疊嵌入。 服務堆疊僅在必要時作為累積更新的前提,單獨發佈。

更新套件 Title 產品 說明
安全作業系統動態更新 YYYY-MM Windows 11 動態更新 Windows 安全作業系統動態更新 ComponentUpdate
設定動態更新 YYYY-MM Windows 11 動態更新 Windows 10 及更新版本的動態更新 設定更新
最新累積更新 YYYY-MM Windows 11 累積更新
Servicing stack 動態更新 YYYY-MM Windows 11 版本 21H2 服務堆疊更新

Windows 10,22H2 動態更新套件

標題產品描述是區分每個動態套件的必要條件。 最新的累積更新已將維修堆疊嵌入。 服務堆疊僅在必要時作為累積更新的前提,單獨發佈。

更新套件 Title 產品 說明
安全作業系統動態更新 YYYY-MM Windows 10 22H2 版本動態更新 Windows 安全作業系統動態更新 ComponentUpdate
設定動態更新 YYYY-MM Windows 10 22H2 版本動態更新 Windows 10 及更新版本的動態更新 設定更新
最新累積更新 YYYY-MM Windows 10 版本 22H2 累積更新
Servicing stack 動態更新 YYYY-MM 服務堆疊更新 for Windows 10 版本 22H2

若想自訂映像檔,加入更多語言或隨選功能,請從 磁碟授權服務中心下載補充媒體 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
新增安全作業系統動態更新 6
新增設定動態更新 26
加入 WinPE 的 Setup.exe 和 setuphost.exe 27
從 WinPE 新增開機管理器 28
新增最新累積更新 13 23
清理影像 7 14 24
新增 .NET 及 .NET 累積更新 15
匯出影像 8 16 25

注意

  • 從 Windows 11、24H2 版本及 Windows Server 2025 開始,最新的累積更新可能包含必須先安裝的前置累積更新。 請參考下方的 檢查點累積更新 章節。
  • 自 2021 年 2 月起,最新的累積更新與服務堆疊更新會合併並以新的累積更新形式發佈於 Microsoft 更新目錄中。 對於步驟 1、9 和 17 需要維修堆疊更新來更新安裝媒體,你應該使用合併的累積更新。 關於合併累積更新的更多資訊,請參見 服務堆疊更新。 取得動態更新套件時,請確保套件與最新累積更新的月份相同。 例如,如果 SafeOS 動態更新或設定動態更新在與最新累積更新同一月份無法取得,則請使用各自最新發布版本。
  • 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 版本

install.wim (主要作業系統檔案可能包含多個版本的 Windows) 。 根據索引,可能只需要針對某個版本進行更新即可部署。 或者,所有版本都需要更新。 此外,請確保語言在功能隨選前安裝,且最新的累積更新總是最後套用。

附加語言與特色

你不必在映像檔中新增更多語言和功能來完成更新,但這是個機會,可以透過更多語言、可選元件和隨需功能來自訂圖片,超出起始映像檔的範圍。 當你新增更多語言和功能時,重要的是要依正確順序進行這些變更:先套用服務堆疊更新,接著是語言新增,再是功能新增,最後是最新的累積更新。 提供的範例腳本安裝了第二語言 (,在此案例中是日語 (日語) ) 。 由於此語言有 lp.cab 支持,無需新增語言體驗包。 日文被加入主作業系統及復原環境,讓使用者能看到日文的復原畫面。 這包括新增目前安裝在復原映像中的套件的在地化版本。

可選元件及 .NET 功能可離線安裝。 然而,這樣做會產生待處理的操作,需要裝置重新啟動。 因此,執行影像清理的呼叫將失敗。 有兩種方法可以避免清理失敗。 一個方法是跳過映像清理步驟,但那樣會讓 install.wim 變大。 另一個選擇是在清理後、匯出前的步驟中安裝 .NET 和可選元件。 這是範例腳本中的選項。 這樣一來,你就必須從原始的 install.wim (開始,且沒有待處理的動作,) 下次維護或更新映像檔時 (例如下個月) 。

檢查點累積更新

從 Windows 11、24H2 版本及 Windows Server 2025 開始,最新的累積更新可能包含必須先安裝的前置累積更新。 這些更新稱為檢查點累積更新。 在這些情況下,累積更新檔案層級的差異是基於先前的累積更新,而非 Windows RTM 版本。 好處是更新套件較小且安裝更快。 當您從 Microsoft Update 目錄取得最新的累積更新時,下載按鈕即可取得檢查點的累積更新。 此外,累積更新的知識庫文章也提供了更多資訊。

安裝檢查點時 () ,服務 Windows 作業系統時 (第 9 & 12) ,以及 WinPE (第 17 & 23) ,請呼叫 Add-WindowsPackage 目標累積更新。 資料夾 from -PackagePath 用來發現並安裝一個或多個檢查點。 資料夾裡應該 -PackagePath 只有目標累積更新和檢查點累積更新。 累積更新套件 <的修訂值為 = 目標累積更新,則會被處理。 如果你沒有用額外語言和/或選用功能自訂圖片,那麼 Add-WindowsPackage 可以先呼叫 (檢查點累積更新,) 步驟可以用於上述第 9 & 第 17 步。 第12和23步不能分開通話。

根據你所服務的影像版本,以及你特定的影像優化任務,你可能需要或不需要上述一個或全部檢查點。 讓我們來看看三個例子:

範例 1:新增語言或功能: 若您為現有映像檔新增語言或功能,必須安裝目標累積更新及所有前述檢查點。 這是因為語言與功能僅在新 Windows 作業系統 (RTM) 上市時發布一次,因此自 RTM 以來所有服務變更皆需進行,以避免 rip 狀態。

範例 2:在最新檢查點更新映像檔: 如果你沒有新增語言或功能,且現有影像在最新檢查點,那麼只需目標累積更新即可取得最新狀態。

範例 3:更新不在最新檢查點的影像: 如果你沒有新增語言或功能,且現有影像不在最新檢查點上,則需要一個或多個前置檢查點,連同目標累積更新,才能取得最新狀態。

欲了解更多資訊,請參閱 Checkpoint 累積更新與 Microsoft Update 目錄使用情況。

Windows PowerShell 腳本用於對現有映像套用動態匯報

這些範例僅供說明,因此缺乏錯誤處理。 腳本假設以下套件在此資料夾結構中本地儲存:

資料夾 描述
C:\mediaRefresh 包含 PowerShell 腳本的父資料夾
C:\mediaRefresh\oldMedia 包含原始媒體的資料夾,該資料夾會被重新整理。 例如,contains Setup.exe 和 \sources 資料夾。
C:\mediaRefresh\newMedia 一個會包含更新媒體的資料夾。 它是從 \oldMedia 複製過來的,然後作為所有更新和清理作業的目標。

入門

腳本一開始會宣告全域變數並建立用於掛載影像的資料夾。 接著,從 \oldMedia 複製原始媒體到 \newMedia,保留原始媒體,以防腳本錯誤,必須從已知狀態重新開始。 此外,它也提供了舊媒體與新媒體的比較,以評估變化。 為了確保新媒體能持續更新,請確保它們不是唯讀。 腳本還展示了新增語言、隨選功能及可選元件的功能。 這些不是必須的,但會加用來在序列中標示。 從 Windows 11 21H2 版本開始,語言包 (LANGPACK) ISO 被 Features on Demand ISO 取代。 語言包與 \Windows 預安裝環境套件是功能隨選 ISO 的一部分。 此外,主作業系統語言與可選功能的路徑由根節點移至 \LanguagesAndOptionalFeatures。 如果你用這個腳本做Windows 10,請修改成掛載並使用 LANGPACK (ISO 語言包) 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 及每個主要作業系統 Windows 版本

該腳本會在主作業系統檔案 install.wim (更新每個版本的 Windows,) 。 每個版本都會掛載主作業系統映像檔。

第一張圖片會將 Winre.wim 複製到工作資料夾並掛載。 接著透過最新的累積更新套用服務堆疊,因為其元件用於更新其他元件。 根據你更新的 Windows 版本,有兩種不同的服務堆疊更新方式。 第一種方法是使用合併的累積更新。 這是針對包含服務堆疊更新的 Windows 版本,該更新包含服務堆疊更新 (也就是 SSU + LCU 合併) 。 Windows 11,版本 21H2,以及 Windows 11,版本 22H2 是例子。 在這些情況下,服務堆疊更新不會單獨發佈;此步驟應使用合併的累積更新。 然而,在極少數情況下,合併累積更新格式變更可能出現破壞性變更,這需要先發布獨立的服務堆疊更新,並先安裝,才能安裝合併累積更新。 由於腳本可選擇性地新增日文,它會將語言包加入映像檔,並安裝已安裝在 Winre.wim 的所有可選套件的日文版本。 接著,它套用 Safe OS 動態更新套件。 最後會清理並匯出影像,以縮小影像大小。

接著,對於掛載的作業系統映像,腳本會先透過最新的累積更新套用服務堆疊。 接著,它加入了日語支援,接著是日語功能。 與動態更新套件不同,它用 Add-WindowsCapability 來新增這些功能。 欲了解完整功能清單及其相關功能名稱,請參見 「隨選可用功能」。 現在正是啟用其他可選元件或隨需新增其他功能的時候。 如果這類功能有累積更新 (例如.NET) ,現在就是套用的時候。 腳本接著嘗試清理映像檔,最後一步套用最新的累積更新。 重要的是最後套用最新累積更新,以確保隨選功能、可選元件和語言從初始版本狀態開始更新。 .NET 功能是例外,隨著累積更新一起加入。 最後,腳本會匯出圖片。

這個過程會在每個版本的 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 new 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
        Copy-Item -Path $WINPE_MOUNT"\Windows\boot\efi\boot.stl" -Destination $WORKING_PATH"\boot.stl" -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

更新剩餘媒體檔案

這段腳本會更新設定檔案。 它只是將設定動態更新套件中的個別檔案複製到新媒體上。 此步驟會根據需要帶來更新的設定檔案,以及最新的相容性資料庫和替換元件清單。 此腳本也會最終替換 setup.exe、setuphost.exe 和開機管理器檔案,使用先前從 WinPE 儲存的版本。

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


# Copy boot.stl from boot.wim, saved earlier, even if it doesnt exist in the new media.
if (Test-Path -Path $WORKING_PATH"\boot.stl")
{
    Write-Output "$(Get-TS): Copying $WORKING_PATH\boot.stl to $MEDIA_NEW_PATH\efi\microsoft\boot\boot.stl"
    Copy-Item -Path $WORKING_PATH"\boot.stl" -Destination $MEDIA_NEW_PATH"\efi\microsoft\boot\boot.stl" -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!"