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 "[$(Get-Date -Format s)] [$($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 "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] No verified ESD files found. Run Update-OSDeployCoreESD to download ESD files first."
            return
        }

        $WindowsOSRoot = [System.IO.Path]::Combine($Script:OSDeployCorePath, 'cache', 'windows-os')
        $WindowsRERoot = [System.IO.Path]::Combine($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 "[$(Get-Date -Format s)] [$($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 "[$(Get-Date -Format s)] [$($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 ([System.IO.Directory]::Exists($DestinationDirectory)) {
                Write-Warning "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] '$DestinationName' already exists in windows-os. Skipping duplicate import."
                continue
            }
            if ([System.IO.Directory]::Exists($ImportWinREDirectory)) {
                Write-Warning "[$(Get-Date -Format s)] [$($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'

            [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-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
            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-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
            [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

            # -------------------------------------------------------------------------
            # 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 "[$(Get-Date -Format s)] [$($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 = [System.IO.Path]::Combine($env:TEMP, "OSDeployCore-Mount-$([Guid]::NewGuid().ToString('N').Substring(0, 8))")
            [System.IO.Directory]::CreateDirectory($MountPath) | 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
                }
                $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-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 ...'
                $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 "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet .mum files found: $($EthernetClientMums.Length)"
                if ($EthernetClientMums.Length -gt 0) {
                    $EthernetDrivers = foreach ($mumPath in $EthernetClientMums) {
                        Write-Verbose "[$(Get-Date -Format s)] [$($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')
                        $infEl     = $MumXml.Root.Descendants($ns + 'inf') | Select-Object -First 1
                        $DriverInf = $infEl?.Value
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet Identity.name: $($Identity?.Attribute('name')?.Value) | version: $($Identity?.Attribute('version')?.Value) | arch: $($Identity?.Attribute('processorArchitecture')?.Value) | inf: $DriverInf"
                        if ($Identity -and $DriverInf) {
                            [PSCustomObject]@{
                                Name         = $Identity.Attribute('name').Value -replace '^Microsoft-Windows-Ethernet-Client-', '' -replace '-FOD-Package$', ''
                                Version      = [version]$Identity.Attribute('version').Value
                                Architecture = $Identity.Attribute('processorArchitecture').Value
                                InfFile      = $DriverInf
                            }
                        }
                        else {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet .mum skipped — Identity or DriverInf is null"
                        }
                    }
                    # 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 "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet unique drivers after dedup: $($EthernetDrivers.Count)"

                    foreach ($Driver in $EthernetDrivers) {
                        Write-Host -ForegroundColor DarkGray "$($Driver.Name)-$($Driver.Version)"
                        $InfFileWithoutExtension = [System.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"
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Searching DriverStore: $driverStoreRepo\$InfFileWithoutExtension*"
                        $DriverFolder = [System.IO.Directory]::EnumerateDirectories($driverStoreRepo, "$InfFileWithoutExtension*") | Select-Object -First 1
                        if ($DriverFolder) {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Ethernet driver folder found: $DriverFolder"
                            $EthernetDst = [System.IO.Path]::Combine($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" "$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: $packagesPath"
                }
                #endregion

                #region Wi-Fi drivers
                Write-OSDeployCoreProgress 'Extracting Wi-Fi drivers ...'
                $WifiClientMums = if ([System.IO.Directory]::Exists($packagesPath)) {
                    [System.IO.Directory]::GetFiles($packagesPath, 'Microsoft-Windows-Wifi-Client-*.mum')
                } else { @() }
                Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi .mum files found: $($WifiClientMums.Length)"
                if ($WifiClientMums.Length -gt 0) {
                    $WifiDrivers = foreach ($mumPath in $WifiClientMums) {
                        Write-Verbose "[$(Get-Date -Format s)] [$($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')
                        $infEl     = $MumXml.Root.Descendants($ns + 'inf') | Select-Object -First 1
                        $DriverInf = $infEl?.Value
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi Identity.name: $($Identity?.Attribute('name')?.Value) | version: $($Identity?.Attribute('version')?.Value) | arch: $($Identity?.Attribute('processorArchitecture')?.Value) | inf: $DriverInf"
                        if ($Identity -and $DriverInf) {
                            [PSCustomObject]@{
                                Name         = $Identity.Attribute('name').Value -replace '^Microsoft-Windows-Wifi-Client-', '' -replace '-FOD-Package$', ''
                                Version      = [version]$Identity.Attribute('version').Value
                                Architecture = $Identity.Attribute('processorArchitecture').Value
                                InfFile      = $DriverInf
                            }
                        }
                        else {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi .mum skipped — Identity or DriverInf is null"
                        }
                    }
                    # 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 "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi unique drivers after dedup: $($WifiDrivers.Count)"

                    foreach ($Driver in $WifiDrivers) {
                        Write-Host -ForegroundColor DarkGray "$($Driver.Name)-$($Driver.Version)"
                        $InfFileWithoutExtension = [System.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"
                        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Searching DriverStore: $driverStoreRepo\$InfFileWithoutExtension*"
                        $DriverFolder = [System.IO.Directory]::EnumerateDirectories($driverStoreRepo, "$InfFileWithoutExtension*") | Select-Object -First 1
                        if ($DriverFolder) {
                            Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder found: $DriverFolder"
                            $WifiDst = [System.IO.Path]::Combine($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" "$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: $packagesPath"
                }
                #endregion
            }
            finally {
                # Always dismount to avoid orphaned mounts
                if ([System.IO.Directory]::Exists($MountPath)) {
                    Write-OSDeployCoreProgress 'Dismounting Windows image ...'
                    Dismount-WindowsImage -Path $MountPath -Discard -ErrorAction SilentlyContinue | 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-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"
    }
}