public/Update-OSDeployCoreRE.ps1

#Requires -PSEdition Core
#Requires -Version 7.4

function Update-OSDeployCoreRE {
    <#
    .SYNOPSIS
        Exports Windows Recovery Environment images from cached Enterprise ESD files
 
    .DESCRIPTION
        Retrieves Enterprise ESD files that match the newest OS catalog and pass SHA256
        verification, optionally limiting cache verification to one architecture, and exports
        Windows Recovery Environment images into the OSDeployCore Windows RE cache.
 
        To produce each WinRE source, the command also stages a Windows OS cache layout. It
        uses ESD index 1 for setup media, index 2 for WinPE, and index 3 for WinSetup, then
        exports the first Enterprise non-N image as install.wim. It mounts that image to
        extract WinRE, boot files, registry hives, selected system files, and Ethernet and
        Wi-Fi drivers.
 
        An ESD is treated as a duplicate only when both its matching Windows OS and WinRE
        destination directories already exist. Duplicate exports are skipped and return no
        object. A completed export returns its supporting Windows OS destination directory.
 
    .PARAMETER Architecture
        Limits ESD cache verification and export to amd64 or arm64. When omitted, all verified
        cached architectures are processed.
 
    .EXAMPLE
        PS> Update-OSDeployCoreRE
 
        Scans cached Enterprise ESD files and exports Windows RE sources for all available
        architectures.
 
    .EXAMPLE
        PS> Update-OSDeployCoreRE -Verbose
 
        Exports Windows RE sources while displaying detailed processing information.
 
    .EXAMPLE
        PS> Update-OSDeployCoreRE -WhatIf
 
        Shows which Windows OS and WinRE cache directories would be created without exporting
        content.
 
    .INPUTS
        None. This function does not accept pipeline input.
 
    .OUTPUTS
        System.IO.DirectoryInfo. Returns the Windows OS destination directory for each
        completed Windows RE export. Duplicate or skipped exports return no object.
 
    .NOTES
        Author: David Segura
        Company: Recast Software
        Version: 1.0.0
        Date: 2026-08-28
 
        Requires Windows 11 25H2 or later, PowerShell 7.4 or later installed from MSI,
        curl.exe, the DISM PowerShell cmdlets, robocopy, and Administrator rights.
 
        The selected install image is determined from image names, not from a fixed ESD index.
    #>


    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([System.IO.DirectoryInfo])]
    param (
        [Parameter()]
        [ValidateSet('amd64', 'arm64')]
        [System.String]
        $Architecture
    )

    begin {
        #=================================================
        Write-HostOSDeployBanner
        #=================================================
        # Require License for this function. If the license is not valid, return without executing the function.
        if (-not (Test-OSDeployLicenseGate -CommandName $MyInvocation.MyCommand.Name)) {
            return
        }
        #=================================================
        # Stop before importing operating system content when a required host capability is missing.
        if (-not (Test-IsWindows11)) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Windows 11 is required."
        }
        if (-not (Test-IsWindows1125H2)) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Windows 11 25H2 (build 26200) is required."
        }
        if (-not (Test-PwshVersionMin)) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] PowerShell 7.4 or higher is required."
        }
        if (-not (Test-PwshPSHome)) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] The MSI installation of PowerShell 7 is required."
        }
        if (-not (Test-CommandCurl)) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] curl.exe is required but was not found in the current PATH. curl.exe ships with Windows 10 1803+."
        }
        if (-not (Test-IsAdministrator)) {
            throw "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Administrator rights are required. Re-run PowerShell as Administrator and try again."
        }
        #=================================================
        Initialize-OSDeployCorePaths
        #=================================================
        # Retrieve only verified ESD files for the requested architecture.
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Retrieving verified ESD files via Get-OSDeployCoreESD"
        $esdFiles = if ($Architecture) {
            Get-OSDeployCoreESD -Architecture $Architecture
        }
        else {
            Get-OSDeployCoreESD
        }
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Architecture filter: $(if ($Architecture) { $Architecture } else { 'all' }) ($($esdFiles.Count) ESD(s) matched)"
    }

    process {
        # Stop when no verified ESD cache entries are available to import.
        if (-not $esdFiles) {
            Write-Warning "[$(Get-Date -Format s)] No verified ESD files found. Run Update-OSDeployCoreESD to download ESD files first."
            return
        }

        $WindowsOSRoot = [System.IO.Path]::Combine($Script:OSDeployCorePath, 'cache', 'windows-os')
        Write-HostDateTimeDarkCyan "Windows OS image cache: $WindowsOSRoot"

        $WindowsRERoot = [System.IO.Path]::Combine($Script:OSDeployCorePath, 'cache', 'windows-re')
        Write-HostDateTimeDarkCyan "Windows Recovery Environment cache: $WindowsRERoot"

        foreach ($esdFile in $esdFiles) {
            $esdPath     = $esdFile.FullName
            $esdFileName = $esdFile.Name

            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Processing ESD: $esdFileName"

            # -------------------------------------------------------------------------
            # Derive version and architecture from the ESD filename
            # Filename pattern: 26200.8457.260507-0702.25h2_ge_release_..._x64FRE_en-us.esd
            # -------------------------------------------------------------------------
            if ($esdFileName -match '^(\d+\.\d+)') {
                $buildNumber = $Matches[1]
            }
            else {
                Write-Warning "[$(Get-Date -Format s)] Cannot determine build number from '$esdFileName'. Skipping."
                continue
            }

            if ($esdFileName -match '_x64FRE_') {
                $archNorm = 'amd64'
            }
            elseif ($esdFileName -match '_A64FRE_') {
                $archNorm = 'arm64'
            }
            else {
                Write-Warning "[$(Get-Date -Format s)] Cannot determine architecture from '$esdFileName'. Skipping."
                continue
            }

            $DestinationName      = "$buildNumber-$archNorm-enterprise-en-us"
            $DestinationDirectory = Join-Path $WindowsOSRoot $DestinationName
            $ImportWinREDirectory = Join-Path $WindowsRERoot $DestinationName

            Write-Verbose "[$($MyInvocation.MyCommand.Name)] DestinationName: $DestinationName"

            # Skip ESD files whose matching OS and WinRE imports already exist.
            if ([System.IO.Directory]::Exists($DestinationDirectory) -and [System.IO.Directory]::Exists($ImportWinREDirectory)) {
                Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Existing Windows OS and WinRE image directories detected for $DestinationName"
                Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Skipping duplicate import"
                Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Build-OSDeployBoot can use this image to create bootable media."
                continue
            }

            # Honor WhatIf and Confirm before creating the Windows OS and WinRE cache layouts.
            if (-not $PSCmdlet.ShouldProcess($DestinationName, 'Export Windows RE image from ESD')) {
                continue
            }

            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Exporting Windows RE source for $DestinationName ..."

            $DestinationCore  = Join-Path $DestinationDirectory '.core'
            $DestinationTemp  = Join-Path $DestinationDirectory '.temp'
            $DestinationLogs  = Join-Path $DestinationTemp 'logs'
            $DestinationWim   = Join-Path $DestinationDirectory '.wim'
            $DestinationMedia = Join-Path $DestinationDirectory 'WinOS-Media'

            [System.IO.Directory]::CreateDirectory($DestinationCore)  | Out-Null
            [System.IO.Directory]::CreateDirectory($DestinationLogs)  | Out-Null
            [System.IO.Directory]::CreateDirectory($DestinationWim)   | Out-Null
            [System.IO.Directory]::CreateDirectory($DestinationMedia) | Out-Null

            # Write id.json
            @{ id = $DestinationName } | ConvertTo-Json -Depth 5 |
                Out-File (Join-Path $DestinationCore 'id.json') -Encoding utf8 -Force

            # -------------------------------------------------------------------------
            # Index 1 — Expand Windows Setup Media layout to WinOS-Media\
            # -------------------------------------------------------------------------
            Write-HostDateTimeDarkGray 'Expanding Windows Setup Media (Index 1) ...'
            $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Expand-SetupMedia.log"
            Expand-WindowsImage -ImagePath $esdPath -Index 1 -ApplyPath $DestinationMedia -LogPath $CurrentLog | Out-Null

            # Remove read-only attributes set by Expand-WindowsImage
            foreach ($filePath in [System.IO.Directory]::EnumerateFiles($DestinationMedia, '*', [System.IO.SearchOption]::AllDirectories)) {
                $fi = [System.IO.FileInfo]::new($filePath)
                if ($fi.IsReadOnly) { $fi.IsReadOnly = $false }
            }

            # -------------------------------------------------------------------------
            # Index 2 — WinPE → .wim\winpe.wim
            # -------------------------------------------------------------------------
            Write-HostDateTimeDarkGray 'Exporting WinPE (Index 2) ...'
            $CurrentLog  = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-WinPE.log"
            $WinpeWimPath = Join-Path $DestinationWim 'winpe.wim'
            Export-WindowsImage -SourceImagePath $esdPath -SourceIndex 2 -DestinationImagePath $WinpeWimPath -LogPath $CurrentLog | Out-Null

            $WinpeImage = Get-WindowsImage -ImagePath $WinpeWimPath -Index 1
            $WinpeImage | ConvertTo-Json -Depth 5 | Out-File (Join-Path $DestinationCore 'winpe-windowsimage.json') -Encoding utf8 -Force
            $WinpeImage | Export-Clixml -Path (Join-Path $DestinationCore 'winpe-windowsimage.xml')
            $WinpeImageContent = Get-WindowsImageContent -ImagePath $WinpeWimPath -Index 1
            $WinpeImageContent | Out-File (Join-Path $DestinationCore 'winpe-windowsimagecontent.txt') -Encoding ascii -Force

            # -------------------------------------------------------------------------
            # Index 3 — WinSetup → .wim\winse.wim
            # -------------------------------------------------------------------------
            Write-HostDateTimeDarkGray 'Exporting WinSetup (Index 3) ...'
            $CurrentLog  = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-WinSE.log"
            $WinseWimPath = Join-Path $DestinationWim 'winse.wim'
            Export-WindowsImage -SourceImagePath $esdPath -SourceIndex 3 -DestinationImagePath $WinseWimPath -LogPath $CurrentLog | Out-Null

            $WinseImage = Get-WindowsImage -ImagePath $WinseWimPath -Index 1
            $WinseImage | ConvertTo-Json -Depth 5 | Out-File (Join-Path $DestinationCore 'winse-windowsimage.json') -Encoding utf8 -Force
            $WinseImage | Export-Clixml -Path (Join-Path $DestinationCore 'winse-windowsimage.xml')
            $WinseImageContent = Get-WindowsImageContent -ImagePath $WinseWimPath -Index 1
            $WinseImageContent | Out-File (Join-Path $DestinationCore 'winse-windowsimagecontent.txt') -Encoding ascii -Force

            # -------------------------------------------------------------------------
            # Build WinOS-Media\sources\boot.wim from winpe.wim + winse.wim
            # boot.wim index 1 = WinPE, index 2 = WinSetup (standard layout)
            # -------------------------------------------------------------------------
            Write-HostDateTimeDarkGray 'Building boot.wim ...'
            $BootWimPath     = Join-Path $DestinationMedia 'sources\boot.wim'
            $BootWimSourcesDir = Split-Path $BootWimPath -Parent
            [System.IO.Directory]::CreateDirectory($BootWimSourcesDir) | Out-Null

            # Remove an existing boot.wim placed by the media expansion (if any)
            if ([System.IO.File]::Exists($BootWimPath)) {
                [System.IO.File]::Delete($BootWimPath)
            }

            $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-BootWim-PE.log"
            Export-WindowsImage -SourceImagePath $WinpeWimPath -SourceIndex 1 -DestinationImagePath $BootWimPath -LogPath $CurrentLog | Out-Null

            $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-BootWim-SE.log"
            Export-WindowsImage -SourceImagePath $WinseWimPath -SourceIndex 1 -DestinationImagePath $BootWimPath -LogPath $CurrentLog | Out-Null

            # -------------------------------------------------------------------------
            # Select the Enterprise non-N image; skip this ESD when it is unavailable.
            # -------------------------------------------------------------------------
            Write-HostDateTimeDarkGray 'Locating Enterprise image index ...'
            $allEsdImages    = Get-WindowsImage -ImagePath $esdPath
            $enterpriseEntry = $allEsdImages |
                Where-Object { $_.ImageName -like '*Enterprise*' -and $_.ImageName -notlike '*Enterprise N*' } |
                Select-Object -First 1

            if (-not $enterpriseEntry) {
                Write-Warning "[$(Get-Date -Format s)] No Enterprise (non-N) image found in '$esdFileName'. Skipping."
                continue
            }

            $enterpriseIndex      = $enterpriseEntry.ImageIndex
            $DestinationImagePath = Join-Path $DestinationMedia 'sources\install.wim'

            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Enterprise index: $enterpriseIndex ($($enterpriseEntry.ImageName))"
            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Exporting Enterprise image (Index $enterpriseIndex) ..."
            $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-install.wim.log"
            Export-WindowsImage -SourceImagePath $esdPath -SourceIndex $enterpriseIndex -DestinationImagePath $DestinationImagePath -LogPath $CurrentLog | Out-Null

            # -------------------------------------------------------------------------
            # Export install.wim metadata
            # -------------------------------------------------------------------------
            $Image = Get-WindowsImage -ImagePath $DestinationImagePath -Index 1
            $Image | Export-Clixml -Path (Join-Path $DestinationCore 'winos-windowsimage.xml')
            $Image | ConvertTo-Json -Depth 5 | Out-File (Join-Path $DestinationCore 'winos-windowsimage.json') -Encoding utf8
            $ImageContent = Get-WindowsImageContent -ImagePath $DestinationImagePath -Index 1
            $ImageContent | Out-File (Join-Path $DestinationCore 'winos-windowsimagecontent.txt') -Encoding ascii -Force

            # Resolve architecture from the exported image's metadata for properties.json
            $Architecture = $archNorm
            $Language     = ($Image.Languages | Select-Object -First 1).ToLower()

            # Write windows-os properties.json
            $WinOSProperties = [ordered]@{
                Type                 = 'WinOS'
                Id                   = $DestinationName
                Name                 = $DestinationName
                CreatedTime          = $Image.CreatedTime
                ModifiedTime         = $Image.ModifiedTime
                InstallationType     = $Image.InstallationType
                Version              = $Image.Version.ToString()
                Architecture         = $Architecture
                Languages            = @($Image.Languages)
                ImageSize            = $Image.ImageSize
                DirectoryCount       = $Image.DirectoryCount
                FileCount            = $Image.FileCount
                ImageName            = $Image.ImageName
                EditionId            = $Image.EditionId
                Path                 = $DestinationDirectory
                ImagePath            = $DestinationImagePath
                ImageIndex           = 1
                ImageDescription     = $Image.ImageDescription
                WIMBoot              = $Image.WIMBoot
                ImageType            = $Image.ImageType
                ProductName          = $Image.ProductName
                Hal                  = $Image.Hal
                ProductType          = $Image.ProductType
                ProductSuite         = $Image.ProductSuite
                MajorVersion         = $Image.MajorVersion
                MinorVersion         = $Image.MinorVersion
                Build                = $Image.Build
                SPBuild              = $Image.SPBuild
                SPLevel              = $Image.SPLevel
                ImageBootable        = $Image.ImageBootable
                SystemRoot           = $Image.SystemRoot
                DefaultLanguageIndex = $Image.DefaultLanguageIndex
            }
            $WinOSProperties | ConvertTo-Json -Depth 5 |
                Out-File (Join-Path $DestinationDirectory 'properties.json') -Encoding utf8 -Force

            # -------------------------------------------------------------------------
            # Mount install.wim read-only to extract WinRE and supplemental content
            # -------------------------------------------------------------------------
            $MountPath = [System.IO.Path]::Combine($env:TEMP, "OSDeployCore-Mount-$([Guid]::NewGuid().ToString('N').Substring(0, 8))")
            [System.IO.Directory]::CreateDirectory($MountPath) | Out-Null

            Write-HostDateTimeDarkGray 'Mounting Windows image (read-only) ...'
            try {
                Mount-WindowsImage -ImagePath $DestinationImagePath -Index 1 -Path $MountPath -ReadOnly -ErrorAction Stop | Out-Null
                $MountDirectory = $MountPath

                #region WinRE extraction
                Write-HostDateTimeDarkGray 'Extracting WinRE ...'
                $winreSource   = Join-Path $MountDirectory 'Windows\System32\Recovery\winre.wim'
                $reagentSource = Join-Path $MountDirectory 'Windows\System32\Recovery\ReAgent.xml'

                if (Test-Path $reagentSource) {
                    Copy-Item -Path $reagentSource -Destination (Join-Path $DestinationTemp 'os-reagent.xml') | Out-Null
                }
                if (Test-Path $winreSource) {
                    Copy-Item -Path $winreSource -Destination (Join-Path $DestinationWim 'winre.wim') | Out-Null

                    $WinreWimPath  = Join-Path $DestinationWim 'winre.wim'
                    $WinreImage    = Get-WindowsImage -ImagePath $WinreWimPath -Index 1
                    $WinreImage | ConvertTo-Json -Depth 5 | Out-File (Join-Path $DestinationCore 'winre-windowsimage.json') -Encoding utf8 -Force
                    $WinreImage | Export-Clixml -Path (Join-Path $DestinationCore 'winre-windowsimage.xml')
                    $WinreImageContent = Get-WindowsImageContent -ImagePath $WinreWimPath -Index 1
                    $WinreImageContent | Out-File (Join-Path $DestinationCore 'winre-windowsimagecontent.txt') -Encoding ascii -Force
                }
                #endregion

                #region Registry hives
                Write-HostDateTimeDarkGray 'Backing up registry hives ...'
                $RegistryHives = @('SOFTWARE', 'SYSTEM')
                $RobocopyLog   = Join-Path $DestinationLogs 'os-registry.log'
                foreach ($Item in $RegistryHives) {
                    robocopy "$MountDirectory\Windows\System32\config" "$DestinationTemp" $Item /b /np /ts /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                }
                $softwareSrc = [System.IO.Path]::Combine($DestinationTemp, 'SOFTWARE')
                $systemSrc   = [System.IO.Path]::Combine($DestinationTemp, 'SYSTEM')
                if ([System.IO.File]::Exists($softwareSrc)) { [System.IO.File]::Move($softwareSrc, [System.IO.Path]::Combine($DestinationTemp, 'os-software.hive'), $true) }
                if ([System.IO.File]::Exists($systemSrc))   { [System.IO.File]::Move($systemSrc,   [System.IO.Path]::Combine($DestinationTemp, 'os-system.hive'),   $true) }
                #endregion

                #region Boot files
                $BootPath = Join-Path $MountDirectory 'Windows\Boot'
                if (Test-Path $BootPath) {
                    Write-HostDateTimeDarkGray 'Backing up boot files ...'
                    $RobocopyLog = Join-Path $DestinationLogs 'os-boot.log'
                    robocopy "$BootPath" (Join-Path $DestinationCore 'os-boot') *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                }
                #endregion

                #region Windows executables and subdirectories
                Write-HostDateTimeDarkGray 'Backing up OS system files ...'
                $BackupOSFiles = @(
                    'aerolite*.*'
                    'bcp47*.dll'
                    'bits*.*'
                    'BitsTransfer*.*'
                    'BranchCache*.*'
                    'cacls.exe*'
                    'choice.exe*'
                    'comp.exe*.*'
                    'credssp*.*'
                    'curl.exe'
                    'dism.exe*'
                    'dismApi.dll*'
                    'ddp*.*'
                    'defrag.exe*'
                    'djoin*.*'
                    'dmcmnutils*.*'
                    'dssec*.*'
                    'dsuiext*.*'
                    'edputil*.*'
                    'es.dll*'
                    'explorerframe*.*'
                    'forfiles*.*'
                    'getmac*.*'
                    'gpedit*.*'
                    'hyyp.sys*'
                    'magnification*.*'
                    'magnify*.*'
                    'makecab.*'
                    'mdmpostprocessevaluator*.*'
                    'mdmregistration*.*'
                    'mscms*.*'
                    'msinfo32.*'
                    'mstsc*.*'
                    'netprofm*.*'
                    'npmproxy*.*'
                    'nslookup.*'
                    'osk*.*'
                    'PCPKsp.dll*'
                    'pdh.dll*'
                    'PeerDist*.*'
                    'perfmon*.*'
                    'setx.*'
                    'shellstyle*.*'
                    'shutdown.*'
                    'shutdownext.*'
                    'shutdownux.*'
                    'srpapi.dll*'
                    'ssdpapi*.*'
                    'StructuredQuery*.*'
                    'systeminfo.*'
                    'tar.exe'
                    'tskill.*'
                    'w32tm*.*'
                    'winver.*'
                    'WSDApi*.*'
                )
                $RobocopyLog  = Join-Path $DestinationLogs 'os-files.log'
                $System32Src  = Join-Path $MountDirectory 'Windows\System32'
                $System32Dst  = Join-Path $DestinationCore 'os-files\Windows\System32'
                foreach ($Item in $BackupOSFiles) {
                    robocopy "$System32Src" "$System32Dst" $Item /s /xd rescache servicing /ndl /b /np /ts /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                }

                # Dism
                $System32Src = Join-Path $MountDirectory 'Windows\System32\Dism'
                $System32Dst = Join-Path $DestinationCore 'os-files\Windows\System32\Dism'
                robocopy "$System32Src" "$System32Dst" *.* /e /ndl /b /np /ts /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null

                # PowerShell Modules
                $PsModuleSrc = Join-Path $MountDirectory 'Program Files\WindowsPowerShell'
                $PsModuleDst = Join-Path $DestinationCore 'os-files\Program Files\WindowsPowerShell'
                robocopy "$PsModuleSrc" "$PsModuleDst" *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                #endregion

                #region Ethernet drivers
                Write-HostDateTimeDarkGray 'Extracting Ethernet drivers ...'
                $packagesPath    = [System.IO.Path]::Combine($MountDirectory, 'Windows', 'servicing', 'Packages')
                $driverStoreRepo = [System.IO.Path]::Combine($MountDirectory, 'Windows', 'System32', 'DriverStore', 'FileRepository')
                $EthernetClientMums = if ([System.IO.Directory]::Exists($packagesPath)) {
                    [System.IO.Directory]::GetFiles($packagesPath, 'Microsoft-Windows-Ethernet-Client-*.mum')
                } else { @() }
                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet .mum files found: $($EthernetClientMums.Length)"
                if ($EthernetClientMums.Length -gt 0) {
                    $EthernetDrivers = foreach ($mumPath in $EthernetClientMums) {
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parsing Ethernet .mum: $mumPath"
                        $MumXml    = [System.Xml.Linq.XDocument]::Load($mumPath)
                        $ns        = $MumXml.Root.Name.Namespace
                        $Identity  = $MumXml.Root.Element($ns + 'assemblyIdentity')
                        $DriverElement = $MumXml.Root.Descendants($ns + 'driver') | Select-Object -First 1
                        $DriverInfAttr = if ($DriverElement) { $DriverElement.Attribute('inf') } else { $null }
                        $DriverInf = if ($DriverInfAttr) {
                            $DriverInfAttr.Value
                        }
                        else {
                            $MumXml.Root.Descendants($ns + 'inf') | Select-Object -First 1 | ForEach-Object { $_.Value }
                        }
                        $identityNameAttr = if ($Identity) { $Identity.Attribute('name') } else { $null }
                        $identityVersionAttr = if ($Identity) { $Identity.Attribute('version') } else { $null }
                        $identityArchAttr = if ($Identity) { $Identity.Attribute('processorArchitecture') } else { $null }
                        $identityName = if ($identityNameAttr) { $identityNameAttr.Value } else { $null }
                        $identityVersion = if ($identityVersionAttr) { $identityVersionAttr.Value } else { $null }
                        $identityArch = if ($identityArchAttr) { $identityArchAttr.Value } else { $null }
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet Identity.name: $identityName | version: $identityVersion | arch: $identityArch | inf: $DriverInf"
                        if ($Identity -and $DriverInf -and $identityName -and $identityVersion -and $identityArch) {
                            [PSCustomObject]@{
                                Name         = $identityName -replace '^Microsoft-Windows-Ethernet-Client-', '' -replace '-FOD-Package$', ''
                                Version      = [version]$identityVersion
                                Architecture = $identityArch
                                InfFile      = $DriverInf
                            }
                        }
                        else {
                            $skipReason = if ($identityName -like '*-Wrapper') {
                                'wrapper manifest does not contain a driver element'
                            }
                            elseif (-not $DriverElement) {
                                'driver element not found'
                            }
                            elseif (-not $DriverInf) {
                                "driver element has no inf attribute; attributes: $($DriverElement.Attributes() -join ', ')"
                            }
                            else {
                                'missing assembly identity or required identity attributes'
                            }
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet .mum skipped - $skipReason"
                        }
                    }
                    # Deduplicate: keep highest version per driver name (O(n) hashtable vs O(n log n) Group-Object)
                    $dedupHash = @{}
                    foreach ($d in $EthernetDrivers) {
                        if (-not $dedupHash.ContainsKey($d.Name) -or $d.Version -gt $dedupHash[$d.Name].Version) {
                            $dedupHash[$d.Name] = $d
                        }
                    }
                    $EthernetDrivers = $dedupHash.Values
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet unique drivers after dedup: $($EthernetDrivers.Count)"

                    foreach ($Driver in $EthernetDrivers) {
                        $InfFileWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Driver.InfFile)
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet driver: $($Driver.Name) v$($Driver.Version) arch=$($Driver.Architecture) inf=$($Driver.InfFile) infBase=$InfFileWithoutExtension"
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Searching DriverStore: $driverStoreRepo\$InfFileWithoutExtension*"
                        $DriverFolder = [System.IO.Directory]::EnumerateDirectories($driverStoreRepo, "$InfFileWithoutExtension*") | Select-Object -First 1
                        if ($DriverFolder) {
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet driver folder found: $DriverFolder"
                            $EthernetDst = [System.IO.Path]::Combine($Script:OSDeployBootAssetsPath, "winpedrivers-$($Driver.Architecture)", "microsoft-windows-ethernet-$($Driver.Version)", $Driver.Name)
                            Write-Host -ForegroundColor DarkGray $EthernetDst
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet destination: $EthernetDst"
                            if (Test-Path "$EthernetDst\*") {
                                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Skipping existing Ethernet driver: $($Driver.Name)-$($Driver.Version)"
                            }
                            else {
                                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Copying Ethernet driver: $($Driver.Name)-$($Driver.Version)"
                                robocopy "$DriverFolder" "$EthernetDst" *.* /e /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                            }
                        }
                        else {
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet driver folder NOT found for inf base: $InfFileWithoutExtension"
                        }
                    }
                }
                else {
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] No Ethernet .mum files found in: $packagesPath"
                }
                #endregion

                #region Wi-Fi drivers
                Write-HostDateTimeDarkGray 'Extracting Wi-Fi drivers ...'
                $WifiClientMums = if ([System.IO.Directory]::Exists($packagesPath)) {
                    [System.IO.Directory]::GetFiles($packagesPath, 'Microsoft-Windows-Wifi-Client-*.mum')
                } else { @() }
                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi .mum files found: $($WifiClientMums.Length)"
                if ($WifiClientMums.Length -gt 0) {
                    $WifiDrivers = foreach ($mumPath in $WifiClientMums) {
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parsing Wi-Fi .mum: $mumPath"
                        $MumXml    = [System.Xml.Linq.XDocument]::Load($mumPath)
                        $ns        = $MumXml.Root.Name.Namespace
                        $Identity  = $MumXml.Root.Element($ns + 'assemblyIdentity')
                        $DriverElement = $MumXml.Root.Descendants($ns + 'driver') | Select-Object -First 1
                        $DriverInfAttr = if ($DriverElement) { $DriverElement.Attribute('inf') } else { $null }
                        $DriverInf = if ($DriverInfAttr) {
                            $DriverInfAttr.Value
                        }
                        else {
                            $MumXml.Root.Descendants($ns + 'inf') | Select-Object -First 1 | ForEach-Object { $_.Value }
                        }
                        $identityNameAttr = if ($Identity) { $Identity.Attribute('name') } else { $null }
                        $identityVersionAttr = if ($Identity) { $Identity.Attribute('version') } else { $null }
                        $identityArchAttr = if ($Identity) { $Identity.Attribute('processorArchitecture') } else { $null }
                        $identityName = if ($identityNameAttr) { $identityNameAttr.Value } else { $null }
                        $identityVersion = if ($identityVersionAttr) { $identityVersionAttr.Value } else { $null }
                        $identityArch = if ($identityArchAttr) { $identityArchAttr.Value } else { $null }
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi Identity.name: $identityName | version: $identityVersion | arch: $identityArch | inf: $DriverInf"
                        if ($Identity -and $DriverInf -and $identityName -and $identityVersion -and $identityArch) {
                            [PSCustomObject]@{
                                Name         = $identityName -replace '^Microsoft-Windows-Wifi-Client-', '' -replace '-FOD-Package$', ''
                                Version      = [version]$identityVersion
                                Architecture = $identityArch
                                InfFile      = $DriverInf
                            }
                        }
                        else {
                            $skipReason = if ($identityName -like '*-Wrapper') {
                                'wrapper manifest does not contain a driver element'
                            }
                            elseif (-not $DriverElement) {
                                'driver element not found'
                            }
                            elseif (-not $DriverInf) {
                                "driver element has no inf attribute; attributes: $($DriverElement.Attributes() -join ', ')"
                            }
                            else {
                                'missing assembly identity or required identity attributes'
                            }
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi .mum skipped - $skipReason"
                        }
                    }
                    # Deduplicate: keep highest version per driver name (O(n) hashtable vs O(n log n) Group-Object)
                    $dedupHashWifi = @{}
                    foreach ($d in $WifiDrivers) {
                        if (-not $dedupHashWifi.ContainsKey($d.Name) -or $d.Version -gt $dedupHashWifi[$d.Name].Version) {
                            $dedupHashWifi[$d.Name] = $d
                        }
                    }
                    $WifiDrivers = $dedupHashWifi.Values
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi unique drivers after dedup: $($WifiDrivers.Count)"

                    foreach ($Driver in $WifiDrivers) {
                        $InfFileWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Driver.InfFile)
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi driver: $($Driver.Name) v$($Driver.Version) arch=$($Driver.Architecture) inf=$($Driver.InfFile) infBase=$InfFileWithoutExtension"
                        Write-Verbose "[$($MyInvocation.MyCommand.Name)] Searching DriverStore: $driverStoreRepo\$InfFileWithoutExtension*"
                        $DriverFolder = [System.IO.Directory]::EnumerateDirectories($driverStoreRepo, "$InfFileWithoutExtension*") | Select-Object -First 1
                        if ($DriverFolder) {
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder found: $DriverFolder"
                            $WifiDst = [System.IO.Path]::Combine($Script:OSDeployBootAssetsPath, "winpedrivers-$($Driver.Architecture)", "microsoft-windows-wifi-$($Driver.Version)", $Driver.Name)
                            Write-Host -ForegroundColor DarkGray $WifiDst
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi destination: $WifiDst"
                            if (Test-Path "$WifiDst\*") {
                                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Skipping existing Wi-Fi driver: $($Driver.Name)-$($Driver.Version)"
                            }
                            else {
                                Write-Verbose "[$($MyInvocation.MyCommand.Name)] Copying Wi-Fi driver: $($Driver.Name)-$($Driver.Version)"
                                robocopy "$DriverFolder" "$WifiDst" *.* /e /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                            }
                        }
                        else {
                            Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder NOT found for inf base: $InfFileWithoutExtension"
                        }
                    }
                }
                else {
                    Write-Verbose "[$($MyInvocation.MyCommand.Name)] No Wi-Fi .mum files found in: $packagesPath"
                }
                #endregion
            }
            finally {
                # Always dismount to avoid orphaned mounts
                if ([System.IO.Directory]::Exists($MountPath)) {
                    Write-HostDateTimeDarkGray 'Dismounting Windows image ...'
                    $dismountSucceeded = $false
                    foreach ($dismountAttempt in 1..3) {
                        try {
                            Dismount-WindowsImage -Path $MountPath -Discard -ErrorAction Stop | Out-Null
                            $dismountSucceeded = $true
                            break
                        }
                        catch {
                            if ($dismountAttempt -lt 3) {
                                Write-Warning "[$(Get-Date -Format s)] Unable to dismount the Windows image. Retrying in 5 seconds."
                                Start-Sleep -Seconds 5
                            }
                        }
                    }

                    if (-not $dismountSucceeded) {
                        Write-Warning "[$(Get-Date -Format s)] Close all Windows Explorer windows and pause any antivirus scans that may be accessing the mounted image."
                        Write-Host 'Press any key to retry the dismount ...'
                        [void][Console]::ReadKey($true)
                        Dismount-WindowsImage -Path $MountPath -Discard -ErrorAction Stop | Out-Null
                    }
                    try { [System.IO.Directory]::Delete($MountPath, $true) } catch { }
                }
            }

            # Remove Read-Only from all imported files
            foreach ($filePath in [System.IO.Directory]::EnumerateFiles($DestinationDirectory, '*', [System.IO.SearchOption]::AllDirectories)) {
                $fi = [System.IO.FileInfo]::new($filePath)
                if ($fi.IsReadOnly) { $fi.IsReadOnly = $false }
            }

            #region Build the WinRE directory
            Write-HostDateTimeDarkGray 'Building WinRE directory ...'
            robocopy (Join-Path $DestinationDirectory '.core') (Join-Path $ImportWinREDirectory '.core') *.* /e /xf OSImage.* winpe-windowsimage* winse-windowsimage* /tee /r:0 /w:0 | Out-Null
            robocopy (Join-Path $DestinationDirectory '.temp') (Join-Path $ImportWinREDirectory '.temp') *.* /e /xd logs /tee /r:0 /w:0 | Out-Null
            robocopy (Join-Path $DestinationDirectory '.wim')  (Join-Path $ImportWinREDirectory '.wim')  winre.wim /e /tee /r:0 /w:0 | Out-Null

            # Write windows-re properties.json
            $WinreWimPath = Join-Path $ImportWinREDirectory '.wim\winre.wim'
            if (Test-Path $WinreWimPath) {
                $WinreImageForProps = Get-WindowsImage -ImagePath $WinreWimPath -Index 1
                $WinREProperties = [ordered]@{
                    Type             = 'WinRE'
                    Id               = $DestinationName
                    Name             = $DestinationName
                    CreatedTime      = $WinreImageForProps.CreatedTime
                    ModifiedTime     = $WinreImageForProps.ModifiedTime
                    InstallationType = $WinreImageForProps.InstallationType
                    Version          = $WinreImageForProps.Version.ToString()
                    Architecture     = $Architecture
                    Languages        = @($WinreImageForProps.Languages)
                    ImageSize        = $WinreImageForProps.ImageSize
                    DirectoryCount   = $WinreImageForProps.DirectoryCount
                    FileCount        = $WinreImageForProps.FileCount
                    ImageName        = $WinreImageForProps.ImageName
                    OSImageName      = $Image.ImageName
                    OSEditionId      = $Image.EditionId
                    OSVersion        = $Image.Version.ToString()
                    OSCreatedTime    = $Image.CreatedTime
                    OSModifiedTime   = $Image.ModifiedTime
                    Path             = $ImportWinREDirectory
                    ImagePath        = $WinreWimPath
                    ImageIndex       = 1
                }
                $WinREProperties | ConvertTo-Json -Depth 5 |
                    Out-File (Join-Path $ImportWinREDirectory 'properties.json') -Encoding utf8 -Force
            }
            #endregion

            Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Windows RE export complete: $DestinationName"
            Get-Item -Path $DestinationDirectory
        }
    }

    end {
        #=================================================
        Write-Verbose "[$($MyInvocation.MyCommand.Name)] End"
        #=================================================
    }
}