We have updated the official Windows 10 deployment documentation with the current process used to acquire and apply Dynamic Update packages to existing Windows 10 images prior to deployment. Please see Update Windows 10 media with Dynamic Update for the latest guidance, including Windows PowerShell scripts you can use to automate the process. |
In the recent blog entitled The benefits of Windows 10 Dynamic Update, Monika Sinha provided an overview of Dynamic Update, the benefits of enabling it, how it works, and how to manage this content in a managed environment.
For IT pros that want to have these updates applied to their Windows 10 image prior to deployment, they need to understand how Dynamic Update packages can be acquired and applied to their image. This blog will walk through the steps for acquiring and applying Dynamic Update packages, showcasing a sample PowerShell script.
Dynamic Update refresher
As soon as a Windows 10 feature update initiates, whether from media or a Windows Update service-connected environment, Dynamic Update is one of the first steps invoked. Windows 10 Setup reaches out to an Internet-facing URL hosted by Microsoft to fetch Dynamic Update content, then applies those updates to your OS installation media.
Content acquired includes:
- Setup Updates: Fixes to Setup binaries or any files that Setup uses for feature updates.
- Safe OS Updates: Fixes for the "safe OS" that are used to update Windows recovery environment (WinRE).
- Servicing Stack Updates: Fixes that are necessary to address the Windows 10 servicing stack issue and thus required to complete the feature update.
- Latest Cumulative Update: Installs the latest cumulative quality update.
- Driver Updates: Latest version of applicable drivers that have already been published by manufacturers into Windows Update and specifically targeted for Dynamic Update.
In addition to these updates, Dynamic Update will preserve Language Pack (LP) and Features on Demand (FODs) content during the upgrade process. These are not updates to LPs and FODs, but reacquisition to ensure the user has these elements present with the update completes.
For some environments, enabling acquired Dynamic Update content and installing that content on the device is not an option. For example, the device may not have internet connectivity. If one is performing a media-based feature update, most Dynamic Update content can be acquired and applied to the image before initiating setup on the device.
Let's briefly walk through the high-level steps. Then, we'll look at how to perform these steps using a PowerShell script.
Acquiring the content
Visit the Microsoft Update Catalog and acquire the Dynamic Update cumulative content packages used to apply to the image. At the main search bar, searching for ""1809 Dynamic Update" will show a listing as follows:
It appears that there is a Safe OS Dynamic Update available to download. However, what about the other packages? To understand how to map these search results requires closer inspection of each Title, Product and Description.
The below table shows you how to differentiate each type of Dynamic Update package:
Dynamic Update Content |
Title |
Product |
Description |
SafeOS Dynamic Update |
2019-08 Dynamic Update... |
Windows 10 Dynamic Update,Windows Safe OS Dynamic Update |
ComponentUpdate: |
Setup Dynamic Update |
2019-08 Dynamic Update... |
Windows 10 Dynamic Update |
SetupUpdate: |
LCU |
2019-08 Cumulative Update for Windows 10 |
Windows 10 |
Install this update to resolve issues in Windows... |
SSU Dynamic Update |
2019-09 Servicing Stack Update for Windows 10 |
Windows 10... |
Install this update to resolve issues in Windows... |
In order to customize the OS image with additional languages or FODs, have the OS supplemental media ISOs available, which are downloadable from the Microsoft Volume Licensing Service Center. For example, since Dynamic Update will be disabled for your devices, and should users require specific FODs, these can be preinstalled into the image.
Patching the Windows 10 installation media
Now we can work on how the packages and acquired supplemental media can be used to patch Windows 10 OS media. To get started, let’s look at the high-level steps we’ll perform using PowerShell.
The table below shows the steps (as rows) that are executed against a target (as columns). Many of these tasks are repeated for target. For example, tasks that are repeated for each of the multiple Windows image files, such as applying SSU Dynamic Update, cleaning the image, and exporting the image. The targets of these actions are as follows:
- WinPE: The Windows Preinstallation Environment (WinPE) is a small operating system used to install, deploy, and repair Windows 10 desktop editions, Windows Server, and other Windows operating systems. From WinPE, it’s possible to set up a hard drive before installing Windows, install Windows by using apps or scripts from a network or a local drive, or capture and apply Windows images. Further, it’s possible to modify the Windows operating system while it's not running, set up automatic recovery tools, recover data from unbootable devices, and add a custom shell or GUI to automate tasks.
- WinRE: The Windows Recovery Environment (WinRE) repair common causes of unbootable operating systems. WinRE is based on Windows Preinstallation Environment and can be customized with additional drivers, languages, Windows PE optional packages, and other troubleshooting or diagnostic tools.
- Main OS: The main OS is one or more Windows 10 editions stored within \sources\install.wim.
- Media: Media in the table below represents the complete collection of files and folders in the Windows 10 installation media. For example, \sources folder, \boot folder, setup.exe, etc.
WinRE (winre.wim)
WinPE (boot.wim)
Main OS (install.wim)
New media
Add SSU Dynamic Update
1
9
18
Add Lang Pack
2
10
19
Add Localized Optional Packages
3
11
Add Font Support
4
12
Add TTS Support
5
13
Update Lang.ini
14
Add Features On Demand
20
Add Safe OS Dynamic Update
6
Add Setup Dynamic Update
26
Add LCU
15
21
Clean Image
7
16
22
Add Optional Components
23
Add .NET and .NET Cumulative Updates
24
Export Image
8
17
25
Handling multiple Windows editions
The main OS (install.wim) contains multiple editions of Windows 10. It’s possible that only a patch for the edition is required to deploy, based on the index. Or, if needed, loop through all editions and patch. Further, ensure that languages are installed before Features on Demand, and that the LCU is always applied last.
Customizing Windows with additional languages and features
Although not required from a patching perspective, this is an opportunity to customize the image with languages, Optional Components, and Features On Demand. We highlight this to ensure the correct sequencing is used. Applying the patches, followed by language additions, then by FOD additions, and lastly finish with the LCU. In the script below, we will be installing a single language, in this case Japanese (ja-JP). Since this is a lp.cab backed language, there isn’t a need to add a Language Experience Pack (LXP). We’ll add Japanese to both the main OS and to the recovery environment to allow the user to see the recovery screens in Japanese. This includes adding localized versions of the packages currently installed in the recovery image.
Optional Components, along with the .NET FOD can be installed offline; however, these installs generate pending operations that require a machine reboot. As a result, the call to perform image cleanup would fail. For this reason, you have a couple of options. You can skip the image cleanup step, but that will result in a larger install.wim. Or you can perform the installation of .NET and Optional Components after cleanup and before export. This is the option outlined in the PowerShell script below. Note: Employing this option means that when you maintain/patch your image next time (i.e. next month), you will need to start with the original install.wim (with no pending actions).
Using PowerShell to apply Dynamic Update content to an existing Windows 10 image
In this section, we’ll take the steps above and show how they can be executed using PowerShell. The code shown is for illustration purposes only, and thus lacks error handling. To get started, the PowerShell script assumes the following content is stored locally with the following folder structure:
Folder |
Description |
C:\mediaRefresh |
Parent folder that contains the PowerShell script |
C:\mediaRefresh\oldMedia |
Folder that contains the original media that will be refreshed. For example, contains setup.exe, and \sources folder. |
C:\mediaRefresh\newMedia |
Folder that will contain the patched media. It is copied from \oldMedia, then used as the target for all patching and cleanup operations. |
Getting started
Start the script by declaring global variables and creating folders to use for mounting images. Then, make a copy of the original media, from \oldMedia to \newMedia, keeping the original media in case there is a script error and thus we can start over from a known state. Also, it will provide a comparison of old versus new media to evaluate changes. To ensure the new media updates, ensure they are not read only.
function Get-TS { return "{0:HH:mm:ss}" -f (Get-Date) }
Write-Host "$(Get-TS): Starting media refresh"
# Declare media for FOD and LPs
$FOD_ISO_PATH = "C:\mediaRefresh\packages\FOD-PACKAGES_OEM_PT1_amd64fre_MULTI.iso"
$LP_ISO_PATH = "C:\mediaRefresh\packages\CLIENTLANGPACKDVD_OEM_MULTI.iso"
# Declare language for showcasing adding optional localized components
$LANG = "ja-jp"
$LANG_FONT_CAPABILITY = "jpan"
# 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
$WORKING_PATH = "C:\mediaRefresh\temp"
$MEDIA_OLD_PATH = "C:\mediaRefresh\oldMedia"
$MEDIA_NEW_PATH = "C:\mediaRefresh\newMedia"
$MAIN_OS_MOUNT = $WORKING_PATH + "\MainOSMount"
$WINRE_MOUNT = $WORKING_PATH + "\WinREMount"
$WINPE_MOUNT = $WORKING_PATH + "\WinPEMount"
# Mount the LP ISO
Write-Host "$(Get-TS): Mounting LP ISO"
$LP_ISO_DRIVE_LETTER = (Mount-DiskImage -ImagePath $LP_ISO_PATH -ErrorAction stop | Get-Volume).DriveLetter
# Declare language related cabs
$WINPE_OC_PATH = Join-Path $LP_ISO_DRIVE_LETTER":" -ChildPath "Windows Preinstallation Environment" | Join-Path -ChildPath "x64" | Join-Path -ChildPath "WinPE_OCs"
$WINPE_OC_LANG_PATH = Join-Path $WINPE_OC_PATH $LANG
$WINPE_OC_LANG_CABS = Get-ChildItem $WINPE_OC_LANG_PATH -name
$WINPE_OC_LP_PATH = Join-Path $WINPE_OC_LANG_PATH "lp.cab"
$WINPE_FONT_SUPPORT_PATH = Join-Path $WINPE_OC_PATH "WinPE-FontSupport-$LANG.cab"
$WINPE_SPEECH_TTS_PATH = Join-Path $WINPE_OC_PATH "WinPE-Speech-TTS.cab"
$WINPE_SPEECH_TTS_LANG_PATH = Join-Path $WINPE_OC_PATH "WinPE-Speech-TTS-$LANG.cab"
$OS_LP_PATH = $LP_ISO_DRIVE_LETTER + ":\x64\langpacks\" + "Microsoft-Windows-Client-Language-Pack_x64_" + $LANG + ".cab"
# Mount the FOD ISO
Write-Host "$(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 + ":\"
# 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, patched media.
Write-Host "$(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 }
Patch WinRE
Start by assuming we’re only patching a single edition, indicated by Index = 1 (Windows 10 Education Edition). Then, mount the image, save winre.wim to the working folder, and mount it. To get started, apply SSU Dynamic Update, given its components are used for updating other components. Since we are optionally adding Japanese, add the language pack to the image, and install the Japanese versions of all optional packages already installed in winre.wim. Then, apply the Safe OS Dynamic Update package.
To finish, clean and export the image to reduce the image size. Note: Skip adding the LCU to winre.wim because it contains unnecessary components in the recovery environment. The components that are updated and applicable are contained within the Safe OS Dynamic Update package. This also helps to keep the image small.
# Mount the main OS, I'll use this throughout the script
Write-Host "$(Get-TS): Mounting main OS"
Mount-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\install.wim" -Index 1 -Path $MAIN_OS_MOUNT -ErrorAction stop| Out-Null
#
# Patch Windows Recovery Environment (WinRE)
#
Copy-Item -Path $MAIN_OS_MOUNT"\windows\system32\recovery\winre.wim" -Destination $WORKING_PATH"\winre.wim" -Force -Recurse -ErrorAction stop | Out-Null
Write-Host "$(Get-TS): Mounting WinRE"
Mount-WindowsImage -ImagePath $WORKING_PATH"\winre.wim" -Index 1 -Path $WINRE_MOUNT -ErrorAction stop | Out-Null
# Add SSU
Write-Host "$(Get-TS): Adding package $SSU_PATH"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $SSU_PATH -ErrorAction stop | Out-Null
#
# Optional: Add the language to recovery environment
#
# Install lp.cab cab
Write-Host "$(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 component 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-Host "$(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-Host "$(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-Host "$(Get-TS): Adding package $WINPE_SPEECH_TTS_PATH"
Add-WindowsPackage -Path $WINRE_MOUNT -PackagePath $WINPE_SPEECH_TTS_PATH -ErrorAction stop | Out-Null
Write-Host "$(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-Host "$(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-Host "$(Get-TS): Performing image cleanup on WinRE"
DISM /image:$WINRE_MOUNT /cleanup-image /StartComponentCleanup | Out-Null
# Dismount
Dismount-WindowsImage -Path $WINRE_MOUNT -Save -ErrorAction stop | Out-Null
# Export
Write-Host "$(Get-TS): Exporting image to $WORKING_PATH\winre2.wim"
Export-WindowsImage -SourceImagePath $WORKING_PATH"\winre.wim" -SourceIndex 1 -DestinationImagePath $WORKING_PATH"\winre2.wim" -ErrorAction stop | Out-Null
Move-Item -Path $WORKING_PATH"\winre2.wim" -Destination $WORKING_PATH"\winre.wim" -Force -ErrorAction stop | Out-Null
Patch WinPE
Following a similar pattern as above, but this time mount boot.wim, apply the packages with LCU going last, and save. This is done for all images inside of boot.wim, typically two images. Again, start by applying the SSU Dynamic Update. Since we are customizing this media with Japanese, again install the language pack from the WinPE folder on the language pack ISO. Additionally, add font support and text to speech (TTS) support. Since we are adding a new language, rebuild lang.ini, used to identify languages installed in the image. Finally, clean and export boot.wim, and copy back to the new media.
#
# Patch 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) {
# Patch WinPE
Write-Host "$(Get-TS): Mounting WinPE"
Mount-WindowsImage -ImagePath $MEDIA_NEW_PATH"\sources\boot.wim" -Index $IMAGE.ImageIndex -Path $WINPE_MOUNT -ErrorAction stop | Out-Null
# Add SSU
Write-Host "$(Get-TS): Adding package $SSU_PATH"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $SSU_PATH -ErrorAction stop | Out-Null
# Install lp.cab cab
Write-Host "$(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 component 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-Host "$(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-Host "$(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-Host "$(Get-TS): Adding package $WINPE_SPEECH_TTS_PATH"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $WINPE_SPEECH_TTS_PATH -ErrorAction stop | Out-Null
Write-Host "$(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-Host "$(Get-TS): Updating lang.ini"
DISM /image:$WINPE_MOUNT /Gen-LangINI /distribution:$WINPE_MOUNT | Out-Null
}
# Add LCU
Write-Host "$(Get-TS): Adding package $LCU_PATH"
Add-WindowsPackage -Path $WINPE_MOUNT -PackagePath $LCU_PATH -ErrorAction stop | Out-Null
# Perform image cleanup
Write-Host "$(Get-TS): Performing image cleanup on WinPE"
DISM /image:$WINPE_MOUNT /cleanup-image /StartComponentCleanup | Out-Null
# Dismount
Dismount-WindowsImage -Path $WINPE_MOUNT -Save -ErrorAction stop | Out-Null
#Export WinPE
Write-Host "$(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
Patch the main OS
When moving on to the main OS, note that it is already mounted from above. Again, start by applying SSU Dynamic Update. Then, add in Japanese language support. Next, add the Japanese language FOD. Unlike the Dynamic Update packages, leverage Add-WindowsCapability to add FODs. For a full list of FODs, and their associated capability name, see Available Features on Demand.
Now is the time to enable other Optional Components or add other FODs. Further, if the FOD has an associated cumulative update (e.g. .Net), apply these now. Proceed with applying the LCU. As a rule, this always get applied last. Finally, clean and export the image.
As mentioned above, Optional Components, along with the .NET FOD, can be installed offline, however, these installs generate pending operations that require a machine reboot. Therefore, the script below shows how to perform the installation of .NET and Optional Components as a step after cleanup and before export.
#
# Patch Main OS
#
# Add SSU
Write-Host "$(Get-TS): Adding package $SSU_PATH"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $SSU_PATH -ErrorAction stop | Out-Null
# Optional: Add language to main OS
Write-Host "$(Get-TS): Adding package $OS_LP_PATH"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $OS_LP_PATH -ErrorAction stop | Out-Null
# Optional: Add a FODs to the image
Write-Host "$(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-Host "$(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-Host "$(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-Host "$(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-Host "$(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-Host "$(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 optional components, I'd add these here. For example, .Net 3.5
# Note: If I have updates for optional components, I'd add these here. For example, Cumulative Update for .Net 3.5
# Add LCU
Write-Host "$(Get-TS): Adding package $LCU_PATH"
Add-WindowsPackage -Path $MAIN_OS_MOUNT -PackagePath $LCU_PATH -ErrorAction stop | Out-Null
# Copy our updated recovery image from earlier into the main OS
# Note: If I were updating more than 1 edition, I'd want to copy the same recovery image file
# into each edition to enable single instancing
Copy-Item -Path $WORKING_PATH"\winre.wim" -Destination $MAIN_OS_MOUNT"\windows\system32\recovery\winre.wim" -Force -Recurse -ErrorAction stop | Out-Null
# Perform image cleanup
Write-Host "$(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 may require
# the image to be booted, and thus if we tried to cleanup after installation, it would fail.
#
Write-Host "$(Get-TS): Adding NetFX3~~~~"
Add-WindowsCapability -Name "NetFX3~~~~" -Path $MAIN_OS_MOUNT -Source $FOD_PATH -ErrorAction stop | Out-Null
# Add .Net Cumulative Update
Write-Host "$(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-Host "$(Get-TS): Exporting image to $WORKING_PATH\install2.wim"
Export-WindowsImage -SourceImagePath $MEDIA_NEW_PATH"\sources\install.wim" -SourceIndex 1 -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
Patch remaining media files
Next, update the setup files. This is a simple copy of the individual files in the Setup Dynamic Update package to the new media. This step brings an updated setup.exe as needed, along with the latest compatibility database, and replacement component manifests.
#
# Patch remaining files on media
#
# Add Setup DU by copy the files from the package into the newMedia
Write-Host "$(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
Finishing up!
As a last step, we’ll remove our working folder of temporary files, and unmount our language pack and FOD ISOs.
#
# Perform final cleanup
#
# Remove our working folder
Remove-Item -Path $WORKING_PATH -Recurse -Force -ErrorAction stop | Out-Null
# Dismount ISO images
Write-Host "$(Get-TS): Dismounting ISO images"
Dismount-DiskImage -ImagePath $LP_ISO_PATH -ErrorAction stop | Out-Null
Dismount-DiskImage -ImagePath $FOD_ISO_PATH -ErrorAction stop | Out-Null
Write-Host "$(Get-TS): Media refresh completed!"