public/Import-OSDWorkspaceWinOS.ps1
function Import-OSDWorkspaceWinOS { <# .SYNOPSIS Imports Windows Recovery Environment (WinRE) images from mounted Windows installation media to OSDWorkspace. .DESCRIPTION The Import-OSDWorkspaceWinOS function extracts and imports Windows Recovery Environment (WinRE) images from mounted Windows installation media ISO files to the OSDWorkspace BootImage directory. This function performs the following operations: 1. Validates administrator privileges 2. Scans for mounted Windows installation media ISO files 3. Displays an Out-GridView selection dialog for available installation indexes 4. Extracts the winre.wim file from the selected installation image(s) 5. Imports the WinRE image(s) to the OSDWorkspace BootImage directory The imported images are stored with a naming convention of "yyMMdd-HHmm Architecture" (e.g., "250429-1545 amd64") to indicate when they were imported and for which architecture. This function supports both Windows 11 amd64 (x64) and arm64 installation media. .EXAMPLE Import-OSDWorkspaceWinOS Scans for mounted Windows installation media ISO files and presents a selection dialog to choose which Windows version(s) to import WinRE from. .EXAMPLE Import-OSDWorkspaceWinOS -Verbose Imports WinRE images with detailed verbose output showing each step of the extraction and import process. .INPUTS None This function does not accept pipeline input. .OUTPUTS None This function does not generate any output objects. .NOTES Author: David Segura Version: 1.0 Date: April 29, 2025 Prerequisites: - PowerShell 5.0 or higher - Windows 10 or higher - Run as Administrator - Windows installation media ISO mounted (via File Explorer or third-party tools) The WinRE images extracted are used as source images for creating custom WinPE boot media with the Build-OSDWorkspaceWinPE function. #> [CmdletBinding()] param () begin { #================================================= $Error.Clear() Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Start" Initialize-OSDWorkspace #================================================= # Requires Run as Administrator $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin ) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] This function must be Run as Administrator" return } #================================================= $WindowsMediaImages = @() $WindowsMediaImages = Get-PSDriveWindowsImageIndex -GridView Multiple #================================================= } process { #================================================= #region InputObject if ($null -eq $WindowsMediaImages) { Write-Warning "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] WindowsImage on Windows Installation Media was not found. Mount a Windows Installation ISO and try again." Write-Host -ForegroundColor Gray "[$(Get-Date -format G)] Windows 11 x64 Download: https://www.microsoft.com/en-us/software-download/windows11" Write-Host -ForegroundColor Gray "[$(Get-Date -format G)] Windows 11 arm64 Download https://www.microsoft.com/en-us/software-download/windows11arm64" return } #endregion #================================================= #region Process foreach WindowsImage foreach ($SourceWindowsImage in $WindowsMediaImages) { Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] foreach" # Set the BuildDateTime $BuildDateTime = $((Get-Date).ToString('yyMMdd-HHmm')) Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] BuildDateTime: $BuildDateTime" # Set the Architecture $Architecture = $SourceWindowsImage.Architecture Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Architecture: $Architecture" # Set the Destination Name $DestinationName = "$($BuildDateTime)-$($Architecture)" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] DestinationName: $DestinationName" # Set the Destination Path $DestinationDirectory = Join-Path $($OSDWorkspace.paths.import_windows_os) "$DestinationName" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] DestinationDirectory: $DestinationDirectory" # Set the Recovery Image Path $ImportWinREDirectory = Join-Path $($OSDWorkspace.paths.import_windows_re) "$DestinationName" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] ImageREDirectory: $ImportWinREDirectory" $DestinationCore = "$DestinationDirectory\.core" $DestinationTemp = "$DestinationDirectory\.temp" $DestinationLogs = "$DestinationTemp\logs" $DestinationWim = "$DestinationDirectory\.wim" $DestinationMedia = "$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 $ImportId = @{id = $DestinationName } $ImportId | ConvertTo-Json -Depth 5 | Out-File "$DestinationCore\id.json" -Encoding utf8 -Force robocopy "$($SourceWindowsImage.MediaRoot)" "$DestinationMedia" *.* /e /xf install.wim install.esd | Out-Null Get-ChildItem -Recurse -Path "$DestinationMedia\*" | Set-ItemProperty -Name IsReadOnly -Value $false -ErrorAction SilentlyContinue | Out-Null $DestinationImagePath = "$DestinationMedia\sources\install.wim" $CurrentLog = "$DestinationLogs\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-windowsimage.log" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] CurrentLog: $CurrentLog" Export-WindowsImage -SourceImagePath $($SourceWindowsImage.ImagePath) -SourceIndex $($SourceWindowsImage.ImageIndex) -DestinationImagePath $DestinationImagePath -LogPath "$CurrentLog" | Out-Null # Export the Operating System information $Image = Get-WindowsImage -ImagePath $DestinationImagePath -Index 1 Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Export $DestinationCore\winos-windowsimage.xml" $Image | Export-Clixml -Path "$DestinationCore\winos-windowsimage.xml" Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] Export $DestinationCore\winos-windowsimage.json" $Image | ConvertTo-Json -Depth 5 | Out-File "$DestinationCore\winos-windowsimage.json" -Encoding utf8 $ImageContent = Get-WindowsImageContent -ImagePath $DestinationImagePath -Index 1 $ImageContent | Out-File "$DestinationCore\winos-windowsimagecontent.txt" -Encoding ascii -Force # Mount the Windows Image and store the details $MountedWindows = Mount-MyWindowsImage -ImagePath $DestinationImagePath -Index 1 -ErrorAction Stop -ReadOnly $MountDirectory = $MountedWindows.Path # Backup WinRE Copy-Item -Path "$MountDirectory\Windows\System32\Recovery\ReAgent.xml" -Destination "$DestinationDirectory\.temp\os-reagent.xml" | Out-Null Copy-Item -Path "$MountDirectory\Windows\System32\Recovery\winre.wim" -Destination "$DestinationWim\winre.wim" | Out-Null $WinreImage = Get-WindowsImage -ImagePath "$DestinationWim\winre.wim" -Index 1 $WinreImage | ConvertTo-Json -Depth 5 | Out-File "$DestinationCore\winre-windowsimage.json" -Encoding utf8 -Force $WinreImage | Export-Clixml -Path "$DestinationCore\winre-windowsimage.xml" $WinreImageContent = Get-WindowsImageContent -ImagePath "$DestinationWim\winre.wim" -Index 1 $WinreImageContent | Out-File "$DestinationCore\winre-windowsimagecontent.txt" -Encoding ascii -Force # Export WinSE and WinPE $BootWim = "$($SourceWindowsImage.MediaRoot)sources\boot.wim" if (Test-Path $BootWim) { $CurrentLog = "$DestinationLogs\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-WinSE.log" $ExportWinSE = Export-WindowsImage -SourceImagePath $BootWim -SourceIndex 1 -DestinationImagePath "$DestinationWim\winse.wim" -LogPath "$CurrentLog" | Out-Null $WinseImage = Get-WindowsImage -ImagePath "$DestinationWim\winse.wim" -Index 1 $WinseImage | ConvertTo-Json -Depth 5 | Out-File "$DestinationCore\winse-windowsimage.json" -Encoding utf8 -Force $WinseImage | Export-Clixml -Path "$DestinationCore\winse-windowsimage.xml" $WinseImageContent = Get-WindowsImageContent -ImagePath "$DestinationWim\winse.wim" -Index 1 $WinseImageContent | Out-File "$DestinationCore\winse-windowsimagecontent.txt" -Encoding ascii -Force $CurrentLog = "$DestinationLogs\$((Get-Date).ToString('yyyy-MM-dd-HHmmss'))-Export-WinPE.log" $ExportWinPE = Export-WindowsImage -SourceImagePath $BootWim -SourceIndex 2 -DestinationImagePath "$DestinationWim\winpe.wim" -LogPath "$CurrentLog" | Out-Null $WinpeImage = Get-WindowsImage -ImagePath "$DestinationWim\winpe.wim" -Index 1 $WinpeImage | ConvertTo-Json -Depth 5 | Out-File "$DestinationCore\winpe-windowsimage.json" -Encoding utf8 -Force $WinpeImage | Export-Clixml -Path "$DestinationCore\winpe-windowsimage.xml" $WinpeImageContent = Get-WindowsImageContent -ImagePath "$DestinationWim\winpe.wim" -Index 1 $WinpeImageContent | Out-File "$DestinationCore\winpe-windowsimagecontent.txt" -Encoding ascii -Force } # Backup OSFiles #================================================= #region RegistryHives $BackupOSFiles = @( 'SOFTWARE' 'SYSTEM' ) $RobocopyLog = "$DestinationLogs\os-registry.log" foreach ($Item in $BackupOSFiles) { robocopy "$MountDirectory\Windows\System32\config" "$DestinationDirectory\.temp" $Item /b /np /ts /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } $RenameItem = Rename-Item -Path "$DestinationDirectory\.temp\SOFTWARE" -NewName 'os-software.hive' -Force -ErrorAction SilentlyContinue $RenameItem = Rename-Item -Path "$DestinationDirectory\.temp\SYSTEM" -NewName 'os-system.hive' -Force -ErrorAction SilentlyContinue #endregion #================================================= #region Boot $RobocopyLog = "$DestinationLogs\os-boot.log" if (Test-Path "$MountDirectory\Windows\Boot") { robocopy "$MountDirectory\Windows\Boot" "$DestinationCore\os-boot" *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } #endregion #================================================= #region WindowsExecutables and Subdirectories $BackupOSFiles = @( 'aerolite*.*' # AeroLite Theme 'bcp47*.dll' # BCP47 'bits*.*' # 2Pint OSD Toolkit 'BitsTransfer*.*' # 2Pint OSD Toolkit 'BranchCache*.*' # 2Pint OSD Toolkit 'cacls.exe*' 'choice.exe*' 'comp.exe*.*' 'credssp*.*' # 2Pint OSD Toolkit 'curl.exe' 'ddp*.*' # 2Pint OSD Toolkit 'defrag.exe*' 'djoin*.*' 'dmcmnutils*.*' # Wireless 'dssec*.*' # 2Pint OSD Toolkit 'dsuiext*.*' # 2Pint OSD Toolkit 'edputil*.*' # Browse Dialog 'es.dll*' # 2Pint OSD Toolkit 'explorerframe*.*' # Browse Dialog 'forfiles*.*' 'getmac*.*' 'gpedit*.*' # 2Pint OSD Toolkit 'hyyp.sys*' # 2Pint OSD Toolkit 'magnification*.*' 'magnify*.*' 'makecab.*' 'mdmpostprocessevaluator*.*' # Wireless 'mdmregistration*.*' # Wireless 'mscms*.*' # On Screen Keyboard 'msinfo32.*' 'mstsc*.*' # RDP 'netprofm*.*' # 2Pint OSD Toolkit 'npmproxy*.*' # 2Pint OSD Toolkit 'nslookup.*' 'osk*.*' # On Screen Keyboard 'pdh.dll*' # RDP 'PeerDist*.*' # 2Pint OSD Toolkit 'perfmon*.*' 'setx.*' 'shellstyle*.*' # AeroLite Theme 'shutdown.*' 'shutdownext.*' 'shutdownux.*' 'srpapi.dll*' # RDP 'ssdpapi*.*' # 2Pint OSD Toolkit 'StructuredQuery*.*' # Browse Dialog 'systeminfo.*' 'tar.exe' 'tskill.*' 'winver.*' 'WSDApi*.*' # 2Pint OSD Toolkit ) $RobocopyLog = "$DestinationLogs\os-files.log" foreach ($Item in $BackupOSFiles) { robocopy "$MountDirectory\Windows\System32" "$DestinationCore\os-files\Windows\System32" $Item /s /xd rescache servicing /ndl /b /np /ts /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } # PowerShell Modules robocopy "$MountDirectory\Program Files\WindowsPowerShell" "$DestinationCore\os-files\Program Files\WindowsPowerShell" *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null # OpenSSH if (Test-Path "$MountDirectory\Windows\System32\OpenSSH") { robocopy "$MountDirectory\Windows\System32\OpenSSH" "$DestinationCore\os-files\Windows\System32\OpenSSH" *.* /e /tee /r:0 /w:0 /log+:"$RobocopyLog" | Out-Null } #endregion #================================================= #region Dismount the Windows Image Dismount-WindowsImage -Path $MountDirectory -Discard | Out-Null # Remove Read-Only from all files Get-ChildItem -Path $DestinationDirectory -File -Recurse -Force | ForEach-Object { Set-ItemProperty -Path $_.FullName -Name IsReadOnly -Value $false -Force -ErrorAction Ignore | Out-Null } # Build the WinRE directory robocopy "$DestinationDirectory\.core" "$ImportWinREDirectory\.core" *.* /e /xf OSImage.* winpe-windowsimage* winse-windowsimage* /tee /r:0 /w:0 | Out-Null robocopy "$DestinationDirectory\.temp" "$ImportWinREDirectory\.temp" *.* /e /xd logs /tee /r:0 /w:0 | Out-Null robocopy "$DestinationDirectory\.wim" "$ImportWinREDirectory\.wim" winre.wim /e /tee /r:0 /w:0 | Out-Null # Update the Index $null = Get-OSDWSWinOSSource $null = Get-OSDWSWinRESource Get-Item -Path $DestinationDirectory #endregion #================================================= } #endregion #================================================= } end { #================================================= Write-Verbose "[$(Get-Date -format G)] [$($MyInvocation.MyCommand.Name)] End" #================================================= } } |