public/Update-OSDeployCoreOS.ps1

#Requires -PSEdition Core
#Requires -Version 7.4

function Update-OSDeployCoreOS {
    <#
    .SYNOPSIS
        Imports Windows OS images from cached Enterprise ESD files to OSDeployCore.
 
    .DESCRIPTION
        The Update-OSDeployCoreOS function builds a complete Windows OS image layout
        from Enterprise ESD files already present in the OSDeployCore cache.
 
        This function performs the following operations:
        1. Validates administrator privileges
        2. Retrieves verified ESD files from the OSDeployCore cache via Get-OSDeployCoreESD
        3. For each available ESD (x64 and ARM64):
           a. Expands ESD Index 1 (Windows Setup Media) to create the WinOS-Media layout
           b. Exports ESD Index 2 (WinPE) to .wim\winpe.wim
           c. Exports ESD Index 3 (WinSetup) to .wim\winse.wim
           d. Constructs WinOS-Media\sources\boot.wim from the WinPE and WinSetup images
           e. Exports the Enterprise (non-N) image to WinOS-Media\sources\install.wim
           f. Mounts install.wim to extract WinRE, registry hives, boot files, OS
              system files, and Ethernet/Wi-Fi drivers
        4. Creates a parallel windows-re directory with WinRE-specific content
 
        The imported images are stored under $env:ProgramData\OSDeployCore with a naming
        convention of "version-architecture-editionid-language"
        (e.g., "26200.8457-amd64-enterprise-en-us"). Duplicate imports are detected and
        skipped.
 
    .EXAMPLE
        Update-OSDeployCoreOS
 
        Scans for existing cached Enterprise ESD files and imports all available
        architectures to OSDeployCore.
 
    .EXAMPLE
        Update-OSDeployCoreOS -Verbose
 
        Imports Windows OS images from ESD with detailed verbose output showing each step
        of the extraction and import process.
 
    .EXAMPLE
        Update-OSDeployCoreOS -WhatIf
 
        Shows which OS image directories would be created without performing any work.
 
    .INPUTS
        None
 
        This function does not accept pipeline input.
 
    .OUTPUTS
        [System.IO.DirectoryInfo]
 
        Returns the destination directory object for each imported image.
 
    .NOTES
        Author: David Segura
        Version: 0.1.0
 
        ESD index layout:
            Index 1 Windows Setup Media (expanded to WinOS-Media\)
            Index 2 Microsoft Windows PE (exported to .wim\winpe.wim + boot.wim index 1)
            Index 3 Microsoft Windows Setup (exported to .wim\winse.wim + boot.wim index 2)
            Index 4+ Windows OS editions (Enterprise non-N is selected for install.wim)
 
        Prerequisites:
            - PowerShell 7.4 or higher
            - Windows 10 or higher
            - Run as Administrator
 
    Dependencies:
      Module Functions: Get-OSDeployCoreESD, Initialize-OSDeployCorePaths, Test-IsAdministrator, Update-OSDeployCoreESD, Write-OSDeployBanner, Write-OSDeployCoreProgress
      Executables: curl.exe, makecab.exe, robocopy.exe
      Windows Features: DISM, Windows ADK WinPE Addon
      .NET Classes: [IO.Path], [System.IO.DirectoryInfo]
    #>


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

    begin {
        Write-OSDeployBanner
        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Start"
        Initialize-OSDeployCorePaths

        # Requires Run as Administrator
        if (-not (Test-IsAdministrator)) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] This function must be Run as Administrator"
            return
        }

        # Retrieve verified ESD files from the OSDeployCore cache
        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Retrieving verified ESD files via Get-OSDeployCoreESD"
        $esdFiles = Get-OSDeployCoreESD
        if ($Architecture -and $esdFiles) {
            $archPattern = if ($Architecture -eq 'amd64') { '_x64FRE_' } else { '_A64FRE_' }
            $esdFiles = @($esdFiles | Where-Object { $_.Name -match $archPattern })
            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Architecture filter applied: $Architecture ($($esdFiles.Count) ESD(s) matched)"
        }
    }

    process {
        if (-not $esdFiles) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] No verified ESD files found. Run Update-OSDeployCoreESD to download ESD files first."
            return
        }

        $WindowsOSRoot = Join-Path $Script:OSDeployCorePath 'cache' 'windows-os'
        $WindowsRERoot = Join-Path $Script:OSDeployCorePath 'cache' 'windows-re'

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

            Write-Verbose "[$(Get-Date -Format s)] [$($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 "[$($MyInvocation.MyCommand.Name)] Cannot determine build number from '$esdFileName'. Skipping."
                continue
            }

            if ($esdFileName -match '_x64FRE_') {
                $archNorm = 'amd64'
            }
            elseif ($esdFileName -match '_A64FRE_') {
                $archNorm = 'arm64'
            }
            else {
                Write-Warning "[$($MyInvocation.MyCommand.Name)] 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 "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] DestinationName: $DestinationName"

            # Check for duplicate import
            if (Test-Path -Path $DestinationDirectory) {
                Write-Warning "[$($MyInvocation.MyCommand.Name)] '$DestinationName' already exists in windows-os. Skipping duplicate import."
                continue
            }
            if (Test-Path -Path $ImportWinREDirectory) {
                Write-Warning "[$($MyInvocation.MyCommand.Name)] '$DestinationName' already exists in windows-re. Skipping duplicate import."
                continue
            }

            if (-not $PSCmdlet.ShouldProcess($DestinationName, 'Import Windows OS image from ESD')) {
                continue
            }

            Write-OSDeployCoreProgress "Importing $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'

            New-Item -Path $DestinationCore  -ItemType Directory -Force -ErrorAction Stop | Out-Null
            New-Item -Path $DestinationLogs  -ItemType Directory -Force -ErrorAction Stop | Out-Null
            New-Item -Path $DestinationWim   -ItemType Directory -Force -ErrorAction Stop | Out-Null
            New-Item -Path $DestinationMedia -ItemType Directory -Force -ErrorAction Stop | 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-OSDeployCoreProgress '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
            Get-ChildItem -Recurse -Path "$DestinationMedia\*" |
                Set-ItemProperty -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue | Out-Null

            # -------------------------------------------------------------------------
            # Index 2 — WinPE → .wim\winpe.wim
            # -------------------------------------------------------------------------
            Write-OSDeployCoreProgress '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-OSDeployCoreProgress '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-OSDeployCoreProgress 'Building boot.wim ...'
            $BootWimPath     = Join-Path $DestinationMedia 'sources\boot.wim'
            $BootWimSourcesDir = Split-Path $BootWimPath -Parent
            if (-not (Test-Path $BootWimSourcesDir)) {
                New-Item -Path $BootWimSourcesDir -ItemType Directory -Force | Out-Null
            }

            # Remove an existing boot.wim placed by the media expansion (if any)
            if (Test-Path $BootWimPath) {
                Remove-Item -Path $BootWimPath -Force
            }

            $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

            # -------------------------------------------------------------------------
            # Find Enterprise (non-N) index in the ESD, then export to install.wim
            # -------------------------------------------------------------------------
            Write-OSDeployCoreProgress '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 "[$($MyInvocation.MyCommand.Name)] No Enterprise (non-N) image found in '$esdFileName'. Skipping."
                continue
            }

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

            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Enterprise index: $enterpriseIndex ($($enterpriseEntry.ImageName))"
            Write-OSDeployCoreProgress "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 = Join-Path $env:TEMP "OSDeployCore-Mount-$([Guid]::NewGuid().ToString('N').Substring(0, 8))"
            New-Item -Path $MountPath -ItemType Directory -Force | Out-Null

            Write-OSDeployCoreProgress '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-OSDeployCoreProgress '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-OSDeployCoreProgress '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
                }
                Rename-Item -Path (Join-Path $DestinationTemp 'SOFTWARE') -NewName 'os-software.hive' -Force -ErrorAction SilentlyContinue
                Rename-Item -Path (Join-Path $DestinationTemp 'SYSTEM')   -NewName 'os-system.hive'   -Force -ErrorAction SilentlyContinue
                #endregion

                #region Boot files
                $BootPath = Join-Path $MountDirectory 'Windows\Boot'
                if (Test-Path $BootPath) {
                    Write-OSDeployCoreProgress '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-OSDeployCoreProgress 'Backing up OS system files ...'
                $BackupOSFiles = @(
                    'aerolite*.*'
                    'bcp47*.dll'
                    'bits*.*'
                    'BitsTransfer*.*'
                    'BranchCache*.*'
                    'cacls.exe*'
                    'choice.exe*'
                    'comp.exe*.*'
                    'credssp*.*'
                    'curl.exe'
                    '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
                }

                # 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-OSDeployCoreProgress 'Extracting Ethernet drivers ...'
                $EthernetClientMums = Get-ChildItem -Path "$MountDirectory\Windows\servicing\Packages\Microsoft-Windows-Ethernet-Client-*.mum" -ErrorAction SilentlyContinue
                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet .mum files found: $(($EthernetClientMums | Measure-Object).Count)"
                if ($EthernetClientMums) {
                    $EthernetDrivers = foreach ($MumFile in $EthernetClientMums) {
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Parsing Ethernet .mum: $($MumFile.FullName)"
                        $MumXml   = [xml](Get-Content -Path $MumFile.FullName -Raw)
                        $Identity = $MumXml.assembly.assemblyIdentity
                        $DriverInf = $MumXml.assembly.package.update.driver.inf
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet Identity.name: $($Identity.name) | version: $($Identity.version) | arch: $($Identity.processorArchitecture) | inf: $DriverInf"
                        if ($Identity -and $DriverInf) {
                            [PSCustomObject]@{
                                Name         = $Identity.name -replace '^Microsoft-Windows-Ethernet-Client-', '' -replace '-FOD-Package$', ''
                                Version      = [version]$Identity.version
                                Architecture = $Identity.processorArchitecture
                                InfFile      = $DriverInf
                            }
                        }
                        else {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet .mum skipped — Identity or DriverInf is null"
                        }
                    }
                    $EthernetDrivers = $EthernetDrivers |
                        Group-Object Name |
                        ForEach-Object { $_.Group | Sort-Object Version -Descending | Select-Object -First 1 }
                    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet unique drivers after dedup: $(($EthernetDrivers | Measure-Object).Count)"

                    foreach ($Driver in $EthernetDrivers) {
                        Write-Host -ForegroundColor DarkGray "$($Driver.Name)-$($Driver.Version)"
                        $InfFileWithoutExtension = [IO.Path]::GetFileNameWithoutExtension($Driver.InfFile)
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet driver: $($Driver.Name) v$($Driver.Version) arch=$($Driver.Architecture) inf=$($Driver.InfFile) infBase=$InfFileWithoutExtension"
                        $DriverFolderSearch = "$MountDirectory\Windows\System32\DriverStore\FileRepository\$InfFileWithoutExtension*"
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Searching DriverStore: $DriverFolderSearch"
                        $DriverFolder = Get-ChildItem -Path $DriverFolderSearch -Directory -ErrorAction SilentlyContinue | Select-Object -First 1
                        if ($DriverFolder) {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet driver folder found: $($DriverFolder.FullName)"
                            $EthernetDst = Join-Path $Script:OSDeployCorePath "OSDRepo\winpe-drivers\$($Driver.Architecture)\microsoft-windows-ethernet-$($Driver.Version)\$($Driver.Name)"
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet destination: $EthernetDst"
                            if (Test-Path "$EthernetDst\*") {
                                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Skipping existing Ethernet driver: $($Driver.Name)-$($Driver.Version)"
                            }
                            else {
                                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Copying Ethernet driver: $($Driver.Name)-$($Driver.Version)"
                                robocopy "$($DriverFolder.FullName)" "$EthernetDst" *.* /e /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                            }
                        }
                        else {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet driver folder NOT found for inf base: $InfFileWithoutExtension"
                        }
                    }
                }
                else {
                    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] No Ethernet .mum files found in: $MountDirectory\Windows\servicing\Packages"
                }
                #endregion

                #region Wi-Fi drivers
                Write-OSDeployCoreProgress 'Extracting Wi-Fi drivers ...'
                $WifiClientMums = Get-ChildItem -Path "$MountDirectory\Windows\servicing\Packages\Microsoft-Windows-Wifi-Client-*.mum" -ErrorAction SilentlyContinue
                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi .mum files found: $(($WifiClientMums | Measure-Object).Count)"
                if ($WifiClientMums) {
                    $WifiDrivers = foreach ($MumFile in $WifiClientMums) {
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Parsing Wi-Fi .mum: $($MumFile.FullName)"
                        $MumXml    = [xml](Get-Content -Path $MumFile.FullName -Raw)
                        $Identity  = $MumXml.assembly.assemblyIdentity
                        $DriverInf = $MumXml.assembly.package.update.driver.inf
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi Identity.name: $($Identity.name) | version: $($Identity.version) | arch: $($Identity.processorArchitecture) | inf: $DriverInf"
                        if ($Identity -and $DriverInf) {
                            [PSCustomObject]@{
                                Name         = $Identity.name -replace '^Microsoft-Windows-Wifi-Client-', '' -replace '-FOD-Package$', ''
                                Version      = [version]$Identity.version
                                Architecture = $Identity.processorArchitecture
                                InfFile      = $DriverInf
                            }
                        }
                        else {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi .mum skipped — Identity or DriverInf is null"
                        }
                    }
                    $WifiDrivers = $WifiDrivers |
                        Group-Object Name |
                        ForEach-Object { $_.Group | Sort-Object Version -Descending | Select-Object -First 1 }
                    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi unique drivers after dedup: $(($WifiDrivers | Measure-Object).Count)"

                    foreach ($Driver in $WifiDrivers) {
                        Write-Host -ForegroundColor DarkGray "$($Driver.Name)-$($Driver.Version)"
                        $InfFileWithoutExtension = [IO.Path]::GetFileNameWithoutExtension($Driver.InfFile)
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi driver: $($Driver.Name) v$($Driver.Version) arch=$($Driver.Architecture) inf=$($Driver.InfFile) infBase=$InfFileWithoutExtension"
                        $DriverFolderSearch = "$MountDirectory\Windows\System32\DriverStore\FileRepository\$InfFileWithoutExtension*"
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Searching DriverStore: $DriverFolderSearch"
                        $DriverFolder = Get-ChildItem -Path $DriverFolderSearch -Directory -ErrorAction SilentlyContinue | Select-Object -First 1
                        if ($DriverFolder) {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder found: $($DriverFolder.FullName)"
                            $WifiDst = Join-Path $Script:OSDeployCorePath "OSDRepo\winpe-drivers\$($Driver.Architecture)\microsoft-windows-wifi-$($Driver.Version)\$($Driver.Name)"
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi destination: $WifiDst"
                            if (Test-Path "$WifiDst\*") {
                                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Skipping existing Wi-Fi driver: $($Driver.Name)-$($Driver.Version)"
                            }
                            else {
                                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Copying Wi-Fi driver: $($Driver.Name)-$($Driver.Version)"
                                robocopy "$($DriverFolder.FullName)" "$WifiDst" *.* /e /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null
                            }
                        }
                        else {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder NOT found for inf base: $InfFileWithoutExtension"
                        }
                    }
                }
                else {
                    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] No Wi-Fi .mum files found in: $MountDirectory\Windows\servicing\Packages"
                }
                #endregion
            }
            finally {
                # Always dismount to avoid orphaned mounts
                if (Test-Path $MountPath) {
                    Write-OSDeployCoreProgress 'Dismounting Windows image ...'
                    Dismount-WindowsImage -Path $MountPath -Discard -ErrorAction SilentlyContinue | Out-Null
                    Remove-Item -Path $MountPath -Recurse -Force -ErrorAction SilentlyContinue | Out-Null
                }
            }

            # Remove Read-Only from all imported files
            Get-ChildItem -Path $DestinationDirectory -File -Recurse -Force | ForEach-Object {
                Set-ItemProperty -Path $_.FullName -Name IsReadOnly -Value $false -Force -ErrorAction Ignore | Out-Null
            }

            #region Build the WinRE directory
            Write-OSDeployCoreProgress '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-OSDeployCoreProgress "Import complete: $DestinationName"
            Get-Item -Path $DestinationDirectory
        }
    }

    end {
        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] End"
    }
}