private/Import-OSDeployCoreOS.ps1
|
#Requires -PSEdition Core #Requires -Version 7.4 function Import-OSDeployCoreOS { <# .SYNOPSIS Imports Windows images from mounted installation media into OSDeployCore .DESCRIPTION Finds mounted Windows installation media and opens an Out-GridView selector for one or more image indexes. For each selection, the function exports install.wim, copies the installation media, extracts WinRE, WinPE, and WinSE content, and writes image, registry, boot, driver, and operating-system metadata. OS content is stored below the OSDeployCore cache\windows-os directory. Matching recovery content is stored below cache\windows-re. Destination names use the build, revision, architecture, edition ID, and first image language. Existing OS or recovery destinations are not overwritten. The function initializes OSDeployCore paths before media selection. WhatIf and Confirm apply to each selected image import, but do not prevent that path initialization. .EXAMPLE PS> Import-OSDeployCoreOS Opens the image selector and imports the selected Windows images. .EXAMPLE PS> Import-OSDeployCoreOS -WhatIf Selects mounted-media images and shows which imports would run without creating their destination content. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.IO.DirectoryInfo. Returns the OS destination directory for each image imported. .NOTES Author: David Segura Company: Recast Software Version: 0.1.0 Date: 2026-08-28 Requires PowerShell 7.4 or later, Administrator rights, mounted Windows installation media, Out-GridView, DISM, curl.exe, makecab.exe, and robocopy.exe. The Windows ADK Windows PE add-on content used by the extraction workflow must be available. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([System.IO.DirectoryInfo])] param () begin { Write-HostOSDeployBanner Write-Verbose "[$($MyInvocation.MyCommand.Name)] Start" Initialize-OSDeployCorePaths # Requires Run as Administrator if (-not (Test-IsAdministrator)) { Write-Warning "[$(Get-Date -Format s)] This function must be Run as Administrator" return } $WindowsMediaImages = Get-MountedWindowsImageIndex -GridView Multiple } process { # Stop before creating cache directories when discovery returned no selectable images. if ($null -eq $WindowsMediaImages) { Write-Warning "[$(Get-Date -Format s)] WindowsImage on Windows Installation Media was not found. Mount a Windows Installation ISO and try again." Write-HostDateTimeDarkGray 'Windows 11 x64 Download: https://www.microsoft.com/en-us/software-download/windows11' Write-HostDateTimeDarkGray 'Windows 11 arm64 Download: https://www.microsoft.com/en-us/software-download/windows11arm64' return } $WindowsOSRoot = [System.IO.Path]::Combine($Script:OSDeployCorePath, 'cache', 'windows-os') $WindowsRERoot = [System.IO.Path]::Combine($Script:OSDeployCorePath, 'cache', 'windows-re') foreach ($SourceWindowsImage in $WindowsMediaImages) { # Build destination name: version-architecture-editionid-language (all lowercase) $versionParts = $SourceWindowsImage.Version.ToString().Split('.') $trimmedVersion = "$($versionParts[2]).$($versionParts[3])" $Architecture = $SourceWindowsImage.Architecture.ToLower() $EditionId = $SourceWindowsImage.EditionId.ToLower() $Language = ($SourceWindowsImage.Languages | Select-Object -First 1).ToLower() $DestinationName = "$trimmedVersion-$Architecture-$EditionId-$Language" Write-Verbose "[$($MyInvocation.MyCommand.Name)] DestinationName: $DestinationName" $DestinationDirectory = Join-Path $WindowsOSRoot $DestinationName $ImportWinREDirectory = Join-Path $WindowsRERoot $DestinationName # Keep the OS and WinRE caches paired by refusing partial or duplicate destination names. if ([System.IO.Directory]::Exists($DestinationDirectory)) { Write-Warning "[$(Get-Date -Format s)] '$DestinationName' already exists in windows-os. Skipping duplicate import." continue } if ([System.IO.Directory]::Exists($ImportWinREDirectory)) { Write-Warning "[$(Get-Date -Format s)] '$DestinationName' already exists in windows-re. Skipping duplicate import." continue } # Gate all directory creation and image extraction behind WhatIf/Confirm handling. if (-not $PSCmdlet.ShouldProcess($DestinationName, 'Import Windows OS image from mounted media')) { continue } Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Importing $($SourceWindowsImage.ImageName) ($Architecture) ..." $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 # Copy media files (excluding install.wim and install.esd) Write-HostDateTimeDarkGray 'Copying Windows media files ...' robocopy "$($SourceWindowsImage.MediaRoot)" "$DestinationMedia" *.* /e /xf install.wim install.esd | Out-Null # Remove read-only attributes set by media copy foreach ($filePath in [System.IO.Directory]::EnumerateFiles($DestinationMedia, '*', [System.IO.SearchOption]::AllDirectories)) { $fi = [System.IO.FileInfo]::new($filePath) if ($fi.IsReadOnly) { $fi.IsReadOnly = $false } } # Export the selected Windows image $DestinationImagePath = Join-Path $DestinationMedia 'sources\install.wim' $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-windowsimage.log" Write-HostDateTimeDarkGray 'Exporting Windows image ...' Export-WindowsImage -SourceImagePath $SourceWindowsImage.ImagePath -SourceIndex $SourceWindowsImage.ImageIndex -DestinationImagePath $DestinationImagePath -LogPath $CurrentLog | Out-Null # Export OS image 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 # 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 the Windows image read-only to extract files $MountPath = [System.IO.Path]::Combine($env:TEMP, "OSDeployCore-Mount-$([Guid]::NewGuid().ToString('N').Substring(0, 8))") [System.IO.Directory]::CreateDirectory($MountPath) | Out-Null Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Mounting Windows image (read-only) to $MountPath" try { Mount-WindowsImage -ImagePath $DestinationImagePath -Index 1 -Path $MountPath -ReadOnly -ErrorAction Stop | Out-Null $MountDirectory = $MountPath #region WinRE extraction $winreSource = Join-Path $MountDirectory 'Windows\System32\Recovery\winre.wim' $reagentSource = Join-Path $MountDirectory 'Windows\System32\Recovery\ReAgent.xml' Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Extracting WinRE from $winreSource" # ReAgent.xml is optional on some media, so copy it only when the image contains it. if (Test-Path $reagentSource) { Copy-Item -Path $reagentSource -Destination (Join-Path $DestinationTemp 'os-reagent.xml') | Out-Null } if (Test-Path $winreSource) { Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Copying WinRE to $DestinationWim" 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 WinPE and WinSE extraction $BootWim = Join-Path $SourceWindowsImage.MediaRoot 'sources\boot.wim' # Installation media without boot.wim can still provide a valid OS and WinRE import. if (Test-Path $BootWim) { Write-HostDateTimeDarkGray 'Exporting WinPE and WinSE ...' # WinPE (boot.wim index 1) $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-WinPE.log" Export-WindowsImage -SourceImagePath $BootWim -SourceIndex 1 -DestinationImagePath (Join-Path $DestinationWim 'winpe.wim') -LogPath $CurrentLog | Out-Null $WinpeWimPath = Join-Path $DestinationWim 'winpe.wim' $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 # WinSE (boot.wim index 2) $CurrentLog = Join-Path $DestinationLogs "$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-WinSE.log" Export-WindowsImage -SourceImagePath $BootWim -SourceIndex 2 -DestinationImagePath (Join-Path $DestinationWim 'winse.wim') -LogPath $CurrentLog | Out-Null $WinseWimPath = Join-Path $DestinationWim 'winse.wim' $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 } #endregion #region Registry hives Write-HostDateTimeDarkGray 'Backing up registry hives ...' $RegistryHives = @('SOFTWARE', 'SYSTEM') $RobocopyLog = Join-Path $DestinationLogs 'os-registry.log' foreach ($Item in $RegistryHives) { robocopy "$MountDirectory\Windows\System32\config" "$DestinationTemp" $Item /b /np /ts /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } $softwareSrc = [System.IO.Path]::Combine($DestinationTemp, 'SOFTWARE') $systemSrc = [System.IO.Path]::Combine($DestinationTemp, 'SYSTEM') if ([System.IO.File]::Exists($softwareSrc)) { [System.IO.File]::Move($softwareSrc, [System.IO.Path]::Combine($DestinationTemp, 'os-software.hive'), $true) } if ([System.IO.File]::Exists($systemSrc)) { [System.IO.File]::Move($systemSrc, [System.IO.Path]::Combine($DestinationTemp, 'os-system.hive'), $true) } #endregion #region Boot files $BootPath = Join-Path $MountDirectory 'Windows\Boot' if (Test-Path $BootPath) { Write-HostDateTimeDarkGray 'Backing up boot files ...' $RobocopyLog = Join-Path $DestinationLogs 'os-boot.log' robocopy "$BootPath" (Join-Path $DestinationCore 'os-boot') *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } #endregion #region Windows executables and subdirectories Write-HostDateTimeDarkGray 'Backing up OS system files ...' $BackupOSFiles = @( 'aerolite*.*' 'bcp47*.dll' 'bits*.*' 'BitsTransfer*.*' 'BranchCache*.*' 'cacls.exe*' 'choice.exe*' 'comp.exe*.*' 'credssp*.*' 'curl.exe' '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 # Ethernet drivers Write-HostDateTimeDarkGray 'Extracting Ethernet drivers ...' $packagesPath = [System.IO.Path]::Combine($MountDirectory, 'Windows', 'servicing', 'Packages') $driverStoreRepo = [System.IO.Path]::Combine($MountDirectory, 'Windows', 'System32', 'DriverStore', 'FileRepository') $EthernetClientMums = if ([System.IO.Directory]::Exists($packagesPath)) { [System.IO.Directory]::GetFiles($packagesPath, 'Microsoft-Windows-Ethernet-Client-*.mum') } else { @() } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet .mum files found: $($EthernetClientMums.Length)" if ($EthernetClientMums.Length -gt 0) { $EthernetDrivers = foreach ($mumPath in $EthernetClientMums) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parsing Ethernet .mum: $mumPath" $MumXml = [System.Xml.Linq.XDocument]::Load($mumPath) $ns = $MumXml.Root.Name.Namespace $Identity = $MumXml.Root.Element($ns + 'assemblyIdentity') $infEl = $MumXml.Root.Descendants($ns + 'inf') | Select-Object -First 1 $DriverInf = $infEl?.Value Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet Identity.name: $($Identity?.Attribute('name')?.Value) | version: $($Identity?.Attribute('version')?.Value) | arch: $($Identity?.Attribute('processorArchitecture')?.Value) | inf: $DriverInf" # Ignore incomplete package manifests because they cannot identify a DriverStore folder. 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 "[$($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 "[$($MyInvocation.MyCommand.Name)] Ethernet unique drivers after dedup: $($EthernetDrivers.Count)" foreach ($Driver in $EthernetDrivers) { $InfFileWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Driver.InfFile) Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet driver: $($Driver.Name) v$($Driver.Version) arch=$($Driver.Architecture) inf=$($Driver.InfFile) infBase=$InfFileWithoutExtension" Write-Verbose "[$($MyInvocation.MyCommand.Name)] Searching DriverStore: $driverStoreRepo\$InfFileWithoutExtension*" $DriverFolder = [System.IO.Directory]::EnumerateDirectories($driverStoreRepo, "$InfFileWithoutExtension*") | Select-Object -First 1 # Copy only drivers whose package INF can be matched to an installed DriverStore folder. if ($DriverFolder) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet driver folder found: $DriverFolder" $EthernetDst = [System.IO.Path]::Combine($Script:OSDeployBootAssetsPath, "winpedrivers-$($Driver.Architecture)", "microsoft-windows-ethernet-$($Driver.Version)", $Driver.Name) Write-Host -ForegroundColor DarkGray $EthernetDst Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet destination: $EthernetDst" if (Test-Path "$EthernetDst\*") { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Skipping existing Ethernet driver: $($Driver.Name)-$($Driver.Version)" } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Copying Ethernet driver: $($Driver.Name)-$($Driver.Version)" robocopy "$DriverFolder" "$EthernetDst" *.* /e /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Ethernet driver folder NOT found for inf base: $InfFileWithoutExtension" } } } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] No Ethernet .mum files found in: $packagesPath" } # Wi-Fi drivers Write-HostDateTimeDarkGray 'Extracting Wi-Fi drivers ...' $WifiClientMums = if ([System.IO.Directory]::Exists($packagesPath)) { [System.IO.Directory]::GetFiles($packagesPath, 'Microsoft-Windows-Wifi-Client-*.mum') } else { @() } Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi .mum files found: $($WifiClientMums.Length)" if ($WifiClientMums.Length -gt 0) { $WifiDrivers = foreach ($mumPath in $WifiClientMums) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parsing Wi-Fi .mum: $mumPath" $MumXml = [System.Xml.Linq.XDocument]::Load($mumPath) $ns = $MumXml.Root.Name.Namespace $Identity = $MumXml.Root.Element($ns + 'assemblyIdentity') $infEl = $MumXml.Root.Descendants($ns + 'inf') | Select-Object -First 1 $DriverInf = $infEl?.Value Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi Identity.name: $($Identity?.Attribute('name')?.Value) | version: $($Identity?.Attribute('version')?.Value) | arch: $($Identity?.Attribute('processorArchitecture')?.Value) | inf: $DriverInf" # Ignore incomplete package manifests because they cannot identify a DriverStore folder. 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 "[$($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 "[$($MyInvocation.MyCommand.Name)] Wi-Fi unique drivers after dedup: $($WifiDrivers.Count)" foreach ($Driver in $WifiDrivers) { $InfFileWithoutExtension = [System.IO.Path]::GetFileNameWithoutExtension($Driver.InfFile) Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi driver: $($Driver.Name) v$($Driver.Version) arch=$($Driver.Architecture) inf=$($Driver.InfFile) infBase=$InfFileWithoutExtension" Write-Verbose "[$($MyInvocation.MyCommand.Name)] Searching DriverStore: $driverStoreRepo\$InfFileWithoutExtension*" $DriverFolder = [System.IO.Directory]::EnumerateDirectories($driverStoreRepo, "$InfFileWithoutExtension*") | Select-Object -First 1 # Copy only drivers whose package INF can be matched to an installed DriverStore folder. if ($DriverFolder) { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder found: $DriverFolder" $WifiDst = [System.IO.Path]::Combine($Script:OSDeployBootAssetsPath, "winpedrivers-$($Driver.Architecture)", "microsoft-windows-wifi-$($Driver.Version)", $Driver.Name) Write-Host -ForegroundColor DarkGray $WifiDst Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi destination: $WifiDst" if (Test-Path "$WifiDst\*") { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Skipping existing Wi-Fi driver: $($Driver.Name)-$($Driver.Version)" } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Copying Wi-Fi driver: $($Driver.Name)-$($Driver.Version)" robocopy "$DriverFolder" "$WifiDst" *.* /e /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Wi-Fi driver folder NOT found for inf base: $InfFileWithoutExtension" } } } else { Write-Verbose "[$($MyInvocation.MyCommand.Name)] No Wi-Fi .mum files found in: $packagesPath" } # OpenSSH <# $OpenSSHPath = Join-Path $MountDirectory 'Windows\System32\OpenSSH' if (Test-Path $OpenSSHPath) { $OpenSSHDst = Join-Path $DestinationCore 'os-files\Windows\System32\OpenSSH' robocopy "$OpenSSHPath" "$OpenSSHDst" *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } .NOTES Dependencies: Module Functions: Get-MountedWindowsImageIndex, Initialize-OSDeployCorePaths, Test-IsAdministrator, Write-HostOSDeployBanner, Write-HostDateTimeDarkGray Executables: curl.exe, makecab.exe, robocopy.exe Windows Features: DISM, Windows ADK WinPE Addon DotNet Classes: [IO.Path], [System.IO.DirectoryInfo] #> #endregion } finally { # Always dismount to avoid orphaned mounts if ([System.IO.Directory]::Exists($MountPath)) { Write-HostDateTimeDarkGray '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 files foreach ($filePath in [System.IO.Directory]::EnumerateFiles($DestinationDirectory, '*', [System.IO.SearchOption]::AllDirectories)) { $fi = [System.IO.FileInfo]::new($filePath) if ($fi.IsReadOnly) { $fi.IsReadOnly = $false } } #region Build the WinRE directory Write-HostDateTimeDarkGray 'Building WinRE directory ...' robocopy (Join-Path $DestinationDirectory '.core') (Join-Path $ImportWinREDirectory '.core') *.* /e /xf OSImage.* winpe-windowsimage* winse-windowsimage* /tee /r:0 /w:0 | Out-Null robocopy (Join-Path $DestinationDirectory '.temp') (Join-Path $ImportWinREDirectory '.temp') *.* /e /xd logs /tee /r:0 /w:0 | Out-Null robocopy (Join-Path $DestinationDirectory '.wim') (Join-Path $ImportWinREDirectory '.wim') winre.wim /e /tee /r:0 /w:0 | Out-Null # Write windows-re properties.json $WinreWimPath = Join-Path $ImportWinREDirectory '.wim\winre.wim' # Emit WinRE metadata only when recovery media was present in the selected OS image. if (Test-Path $WinreWimPath) { $WinreImageForProps = Get-WindowsImage -ImagePath $WinreWimPath -Index 1 $WinREProperties = [ordered]@{ Type = 'WinRE' Id = $DestinationName Name = $DestinationName CreatedTime = $WinreImageForProps.CreatedTime ModifiedTime = $WinreImageForProps.ModifiedTime InstallationType = $WinreImageForProps.InstallationType Version = $WinreImageForProps.Version.ToString() Architecture = $Architecture Languages = @($WinreImageForProps.Languages) ImageSize = $WinreImageForProps.ImageSize DirectoryCount = $WinreImageForProps.DirectoryCount FileCount = $WinreImageForProps.FileCount ImageName = $WinreImageForProps.ImageName OSImageName = $Image.ImageName OSEditionId = $Image.EditionId OSVersion = $Image.Version.ToString() OSCreatedTime = $Image.CreatedTime OSModifiedTime = $Image.ModifiedTime Path = $ImportWinREDirectory ImagePath = $WinreWimPath ImageIndex = 1 } $WinREProperties | ConvertTo-Json -Depth 5 | Out-File (Join-Path $ImportWinREDirectory 'properties.json') -Encoding utf8 -Force } #endregion Write-Host -ForegroundColor DarkGray "[$(Get-Date -format s)] [INFO] Import complete: $DestinationName" Get-Item -Path $DestinationDirectory } } end { Write-Verbose "[$($MyInvocation.MyCommand.Name)] End" } } |