Blog Post

Windows IT Pro Blog
15 MIN READ

Updating Windows 10 media with Dynamic Update packages

Steve_DiAcetis's avatar
Nov 05, 2019

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

 

 

 

 

 

Learn more

Updated Jan 09, 2021
Version 8.0
  • Hello, Steve. I'm afraid this blog post is two years late. For one thing, dynamic updates have not been released for a year now. v1903 and v1909 do not have them.

    Edit (2020-01-27): Oh. my! I'm seeing dynamic updates in the catalog! In fact, it seems many dynamic updates have been published over the course of years. I'm guessing the catalog didn't show them due to a bug or something, which is now resolved. If I knew complaining here has an effect, I'd have done that sooner.

    For another, we've developed our own in-house scripts by now. At least, you could have properly formatted your code either with syntax highlighting or with the HTML <code> tag.

    Edit (2020-04-08): The code is now properly formatted with the HTML <code> tag. Thanks a lot. 😃

    By the way, your Get-TS function is unnecessary; you could replace it with: Get-Date "HH:mm:ss"

  • Hannes_DFW's avatar
    Hannes_DFW
    Copper Contributor

    Excellent information! I'm very interested in this because, until now, I've only updated the main OS image, never WinRE or the BOOT.WIM. After reading this I plan to revise my procedures.

     

    I do have a comment and a question:

     

    1) Some of the double quote marks in the PowerShell script are curly quotes. The PowerShell script won't work like that. For anyone else copying the script from this page - make sure to to change all curly quotes to straight quotes! Most of the problems are end quotes but I did see at least one opening quote that was a curly quote.

     

    2) Toward the end of the script, the comments state that optional components are being installed after the cleanup of the main OS image. It specifically references .NET 3.5. If I am NOT installing any optional version of .NET framework, can't I install the .NET CU before I do the image cleanup operation? Up until now, I've always been updating the main OS image with DISM and I've been adding in the .NET framework CU before the image cleanup with no difficulties.

  • It appears there is no Safe OS Dynamic Update available for Windows 10 v1909. Here is what I get:

     

     

  • Hannes_DFW's avatar
    Hannes_DFW
    Copper Contributor

    MasterMysterious

     

    Not seeing a Safe OS Dynamic Update for a particular version of Windows is not an indication of a problem. That update applies only to the WinRE image, so if there is nothing within WinRE that needs to be fixed, you won't see a Safe OS Dynamic Update.

     

  • abdinur's avatar
    abdinur
    Copper Contributor

    Not seeing a Safe OS Dynamic Update for a particular version of Windows is not an indication of a problem. That update applies only to the WinRE image, so if there is nothing within WinRE that needs to be fixed, you won't see a Safe OS Dynamic Update.