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 #> [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" } } |