Public/OSDCloudIPU/New-OSDCloudOSWimFile.ps1
function New-OSDCloudOSWimFile { <# Log Files for IPU: https://learn.microsoft.com/en-us/windows/deployment/upgrade/log-files Setup Command Line: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11 #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(ParameterSetName = 'Default')] [ValidateSet( 'Windows 11 24H2 x64', 'Windows 11 24H2 ARM64', 'Windows 11 23H2 x64', 'Windows 11 23H2 ARM64', 'Windows 11 22H2 x64', 'Windows 11 21H2 x64', 'Windows 10 22H2 x64', 'Windows 10 22H2 ARM64')] [System.String] $OSName = 'Windows 11 24H2 x64', #Operating System Edition of the Windows installation #Alias = Edition [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Legacy')] [ValidateSet('Home','Home N','Home Single Language','Education','Education N','Enterprise','Enterprise N','Pro','Pro N')] [Alias('Edition')] [System.String] $OSEdition = 'Pro', #Operating System Language of the Windows installation #Alias = Culture, OSCulture [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Legacy')] [ValidateSet ( 'ar-sa','bg-bg','cs-cz','da-dk','de-de','el-gr', 'en-gb','en-us','es-es','es-mx','et-ee','fi-fi', 'fr-ca','fr-fr','he-il','hr-hr','hu-hu','it-it', 'ja-jp','ko-kr','lt-lt','lv-lv','nb-no','nl-nl', 'pl-pl','pt-br','pt-pt','ro-ro','ru-ru','sk-sk', 'sl-si','sr-latn-rs','sv-se','th-th','tr-tr', 'uk-ua','zh-cn','zh-tw' )] [Alias('Culture','OSCulture')] [System.String] $OSLanguage = 'en-us', #License of the Windows Operating System [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'Legacy')] [ValidateSet('Retail','Volume')] [Alias('License','OSLicense','Activation')] [System.String] $OSActivation = 'Retail', #Create ISO File - Requries Windows ADK (oscdimg.exe) [Parameter(ParameterSetName = 'Default')] [Switch] $CreateISO ) #region Admin Elevation $whoiam = [system.security.principal.windowsidentity]::getcurrent().name $isElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") if ($isElevated) { Write-Host -ForegroundColor Green "[+] Running as $whoiam and IS Admin Elevated" } else { Write-Warning "[-] Running as $whoiam and is NOT Admin Elevated" Break } #================================================ # Get Index & OS Info #================================================ <# if ($OSName -match "ARM64"){ $OSArch = 'ARM64' $IndexInfo = Get-OSDCloudOperatingSystemsIndexes -OSArch ARM64 | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} } else { $OSArch = 'x64' $IndexInfo = Get-OSDCloudOperatingSystemsIndexes | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} } #> if ($OSName -match "ARM64"){ $OSArch = 'ARM64' $OSDCloudOperatingSystem = (Get-OSDCloudOperatingSystemsIndexes -OSArch ARM64) | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} } else { $OSArch = 'x64' $OSDCloudOperatingSystem = Get-OSDCloudOperatingSystemsIndexes | Where-Object {$_.Name -match $OSName} | Where-Object {$_.Activation -eq $OSActivation} | Where-Object {$_.Language -eq $OSLanguage} } $OSEditionID = "$($OSDCloudOperatingSystem.Version) $OSEdition" $OSImageIndex = $OSDCloudOperatingSystem.Indexes.$OSEditionID if ($OSImageIndex -eq $null){ Write-Host -ForegroundColor Red "Unable to determine OSImageIndex for Index $OSEdition" Write-Host -ForegroundColor Yellow "Available Indexes are $($OSDCloudOperatingSystem.IndexNames.replace($(($OSDCloudOperatingSystem).Version),'') -join ', ')" throw "Unable to determine OSImageIndex for $OSName $OSEdition $OSActivation $OSLanguage" } #$OSBuild = $OSDCloudOperatingSystem.Build #$OSReleaseID = $OSDCloudOperatingSystem.ReleaseID #$OSVersion = $OSDCloudOperatingSystem.Version #$ImageFileName = $OSDCloudOperatingSystem.FileName #$ImageFileUrl = $OSDCloudOperatingSystem.Url $ImageFileItem = Find-OSDCloudFile -Name $OSDCloudOperatingSystem.FileName -Path '\OSDCloud\OS\' | Sort-Object FullName | Where-Object {$_.Length -gt 3GB} $ImageFileItem = $ImageFileItem | Where-Object {$_.FullName -notlike "C*"} | Where-Object {$_.FullName -notlike "X*"} | Select-Object -First 1 Write-Host -ForegroundColor DarkGray "=========================================================================" Write-Host -ForegroundColor DarkCyan "These are set based on your input parameters" Write-Host -ForegroundColor Cyan "OSEditionId: " -NoNewline Write-Host -ForegroundColor Green $OSEdition Write-Host -ForegroundColor Cyan "OSImageIndex: " -NoNewline Write-Host -ForegroundColor Green $OSImageIndex Write-Host -ForegroundColor Cyan "OSLanguage: " -NoNewline Write-Host -ForegroundColor Green $OSLanguage Write-Host -ForegroundColor Cyan "OSActivation: " -NoNewline Write-Host -ForegroundColor Green $OSActivation Write-Host -ForegroundColor Cyan "OSArch: " -NoNewline Write-Host -ForegroundColor Green $OSArch Write-Host -ForegroundColor DarkGray "=========================================================================" Write-Host -ForegroundColor Cyan "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Starting Feature Update lookup and Download" #============================================================================ #region Detect & Download ESD File #============================================================================ $ScratchLocation = 'c:\OSDCloud\IPU' $OSMediaLocation = 'c:\OSDCloud\OS' $MediaLocation = "$ScratchLocation\Media\$OSName" if (!(Test-Path -Path $OSMediaLocation)){New-Item -Path $OSMediaLocation -ItemType Directory -Force | Out-Null} if (!(Test-Path -Path $ScratchLocation)){New-Item -Path $ScratchLocation -ItemType Directory -Force | Out-Null} if (Test-Path -Path $MediaLocation){Remove-Item -Path $MediaLocation -Force -Recurse} New-Item -Path $MediaLocation -ItemType Directory -Force | Out-Null $ESD = Get-FeatureUpdate -OSName $OSName -OSActivation $OSActivation -OSLanguage $OSLanguage -OSArchitecture $OSArch if (!($ESD)){ Write-Host -ForegroundColor Red "Unable to Determine proper ESD Upgrade File" throw "Unable to Determine proper ESD Upgrade File" } Write-Host -ForegroundColor Cyan "Name: " -NoNewline Write-Host -ForegroundColor Green $ESD.Name Write-Host -ForegroundColor Cyan "Architecture: " -NoNewline Write-Host -ForegroundColor Green $ESD.Architecture Write-Host -ForegroundColor Cyan "Activation: " -NoNewline Write-Host -ForegroundColor Green $ESD.Activation Write-Host -ForegroundColor Cyan "Build: " -NoNewline Write-Host -ForegroundColor Green $ESD.Build Write-Host -ForegroundColor Cyan "FileName: " -NoNewline Write-Host -ForegroundColor Green $ESD.FileName Write-Host -ForegroundColor Cyan "Url: " -NoNewline Write-Host -ForegroundColor Green $ESD.Url Write-Host -ForegroundColor DarkGray "=========================================================================" Write-Host -ForegroundColor Cyan "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Getting Content for Upgrade Media" <##> #Build Media Paths $SubFolderName = "$($ESD.Version) $($ESD.ReleaseId)" $ImageFolderPath = "$OSMediaLocation\$SubFolderName" if (!(Test-Path -Path $ImageFolderPath)){New-Item -Path $ImageFolderPath -ItemType Directory -Force | Out-Null} $ImagePath = "$ImageFolderPath\$($ESD.FileName)" $ImageDownloadRequired = $true #Check Flash Drive for Media $OSDCloudUSB = Get-Volume.usb | Where-Object {($_.FileSystemLabel -match 'OSDCloud') -or ($_.FileSystemLabel -match 'BHIMAGE')} | Select-Object -First 1 if ($OSDCloudUSB){ $USBImagePath = "$($OSDCloudUSB.DriveLetter):\OSDCloud\OS\$SubFolderName\$($ESD.FileName)" if ((Test-Path -path $USBImagePath) -and (!(Test-Path -path $ImagePath))){ Write-Host -ForegroundColor Green "Found media on OSDCloudUSB - Copying Local" Copy-Item -Path $USBImagePath -Destination $ImagePath } } #Test for Media if (Test-path -path $ImagePath){ Write-Host -ForegroundColor Gray "Found previously downloaded media: $ImagePath" write-host -ForegroundColor Gray " ... Getting SHA1 Hash for validation" $SHA1Hash = Get-FileHash $ImagePath -Algorithm SHA1 if ($SHA1Hash.Hash -eq $esd.SHA1){ Write-Host -ForegroundColor Gray "SHA1 Match $($SHA1Hash.Hash), skipping Download" $ImageDownloadRequired = $false } else { Write-Host -ForegroundColor Gray "SHA1 Match Failed on $ImagePath, removing content" } } if ($ImageDownloadRequired -eq $true){ #Save-WebFile -SourceUrl $ESD.Url -DestinationDirectory $ScratchLocation -DestinationName $ESD.FileName Write-Host -ForegroundColor Gray "Starting Download to $ImagePath, this takes awhile" <# This was taking way too long for some files #Get ESD Size $req = [System.Net.HttpWebRequest]::Create("$($ESD.Url)") $res = $req.GetResponse() (Invoke-WebRequest $ESD.Url -Method Head).Headers.'Content-Length' $ESDSizeMB = $([Math]::Round($res.ContentLength /1000000)) Write-Host "Total Size: $ESDSizeMB MB" #> #Clear Out any Previous Attempts $ExistingBitsJob = Get-BitsTransfer -Name "$($ESD.FileName)" -AllUsers -ErrorAction SilentlyContinue If ($ExistingBitsJob) { Remove-BitsTransfer -BitsJob $ExistingBitsJob } if ((Get-Service -name BITS).Status -ne "Running"){ Write-Host -ForegroundColor Yellow "BITS Service is not Running, which is required to download ESD File, attempting to Start" $StartBITS = Start-Service -Name BITS -PassThru Start-Sleep -Seconds 2 if ($StartBITS.Status -ne "Running"){ } } #Start Download using BITS Write-Host -ForegroundColor DarkGray "Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName $($ESD.FileName) -Description 'Windows Media Download' -RetryInterval 60" $BitsJob = Start-BitsTransfer -Source $ESD.Url -Destination $ImageFolderPath -DisplayName "$($ESD.FileName)" -Description "Windows Media Download" -RetryInterval 60 If ($BitsJob.JobState -eq "Error"){ write-Host "BITS tranfer failed: $($BitsJob.ErrorDescription)" } } #endregion Detect & Download ESD File #============================================================================ #region Extract of ESD file to create Setup Content #============================================================================ #https://www.deploymentresearch.com/how-to-really-create-a-windows-10-build-10041-iso-no-3rd-party-tools-needed/ #Using info from Johan's process to export properly #DISM commands are left for reference only. #Grab ESD File and create bootable ISO if ((!(Test-Path -Path $ImagePath)) -or (!(Test-Path -Path $MediaLocation))){ if (!(Test-Path -Path $ImagePath)){ Write-Host -ForegroundColor Red "Missing $ImagePath, double check download process" throw "Failed to find $ImagePath, double check download process" } if (!(Test-Path -Path $MediaLocation)){ Write-Host -ForegroundColor Red "Missing $MediaLocation, double check folder exist" throw "Failed to find $MediaLocation, double check folder exist" } } if ((Test-Path -Path $ImagePath) -and (Test-Path -Path $MediaLocation)){ Write-Host -ForegroundColor DarkGray "=========================================================================" Write-Host -ForegroundColor Cyan "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Starting Extract of ESD file to create Setup Content" $ApplyPath = $MediaLocation Write-Host -ForegroundColor Gray "Expanding $ImagePath Index 1 to $ApplyPath" $Expand = Expand-WindowsImage -ImagePath $ImagePath -Index 1 -ApplyPath $ApplyPath # Create empty boot.wim file with compression type set to maximum $EmptyFolder = "$($env:TEMP)\EmptyFolder" New-Item -ItemType Directory -Path $EmptyFolder -Force | Out-Null #dism.exe /Capture-Image /ImageFile:$ISOMediaFolder\sources\boot.wim /CaptureDir:$EmptyFolder /Name:EmptyIndex /Compress:max New-WindowsImage -ImagePath $ApplyPath\Sources\boot.wim -CapturePath $EmptyFolder -Name EmptyIndex -Description "Empty Index" -CompressionType Fast # Export base Windows PE to empty boot.wim file (creating a second index) #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:2 /DestinationImageFile:$ISOMediaFolder\sources\boot.wim /Compress:Recovery /Bootable Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 2 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType Fast -CheckIntegrity -Setbootable # Delete the first empty index in boot.wim #dism.exe /Delete-Image /ImageFile:$ISOMediaFolder\sources\boot.wim /Index:1 Remove-WindowsImage -ImagePath $ApplyPath\Sources\boot.wim -Index 1 # Export Windows PE with Setup to boot.wim file #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:3 /DestinationImageFile:$ISOMediaFolder\sources\boot.wim /Compress:Recovery /Bootable Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 3 -DestinationImagePath "$ApplyPath\Sources\boot.wim" -CompressionType Fast -CheckIntegrity -Setbootable # Create empty install.wim file with MDT/ConfigMgr friendly compression type (maximum) #dism.exe /Capture-Image /ImageFile:$ISOMediaFolder\sources\install.wim /CaptureDir:C:\EmptyFolder /Name:EmptyIndex /Compress:max New-WindowsImage -ImagePath $ApplyPath\Sources\install.wim -CapturePath $EmptyFolder -Name EmptyIndex -Description "Empty Index" -CompressionType Fast #Export the OS Image to the install.wim file Write-Host -ForegroundColor Gray "Expanding $ImagePath Index $OSImageIndex to $ApplyPath\Sources\install.wim" #dism.exe /Export-image /SourceImageFile:$ESDFile /SourceIndex:4 /DestinationImageFile:$ISOMediaFolder\sources\install.wim /Compress:Recovery ##Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex 5 -DestinationImagePath "$ApplyPath\Sources\install.wim" -CompressionType max -CheckIntegrity $Expand = Export-WindowsImage -SourceImagePath $ImagePath -SourceIndex $OSImageIndex -DestinationImagePath "$ApplyPath\Sources\install.wim" -CheckIntegrity -CompressionType Fast $null = $Expand # Delete the first empty index in install.wim #dism.exe /Delete-Image /ImageFile:$ISOMediaFolder\sources\install.wim /Index:1 Remove-WindowsImage -ImagePath $ApplyPath\Sources\install.wim -Index 1 } #endregion Extract of ESD file to create Setup Content if (!(Test-Path -Path "$MediaLocation\Setup.exe")){ Write-Host -ForegroundColor Red "Setup.exe not found, something went wrong" throw } if (!(Test-Path -Path "$MediaLocation\sources\install.wim")){ Write-Host -ForegroundColor Red "install.wim not found, something went wrong" throw } Write-Host -ForegroundColor DarkGray "=========================================================================" if ($CreateISO){ $PathToOscdimg = (Get-AdkPaths).oscdimgexe if (!(Test-Path -Path $PathToOscdimg)){ Write-Host -ForegroundColor Red "oscdimg.exe not found, unable to create ISO File" throw "oscdimg.exe not found, unable to create ISO File" } else { Write-Host -ForegroundColor Cyan "$((Get-Date).ToString('yyyy-MM-dd-HHmmss')) Creating ISO File" $BootData='2#p0,e,b"{0}"#pEF,e,b"{1}"' -f "$ApplyPath\boot\etfsboot.com","$ApplyPath\efi\Microsoft\boot\efisys.bin" $ISOFile = "`"$ScratchLocation\$($ESD.Version) $($ESD.ReleaseId) $($ESD.Architecture).iso`"" $ISOFilePath = "$ScratchLocation\$($ESD.Version) $($ESD.ReleaseId) $($ESD.Architecture).iso" if (Test-Path -Path $ISOFilePath){Remove-Item -Path $ISOFilePath -Force} $ISOMedia = "`"$ApplyPath`"" $Proc = Start-Process -FilePath $PathToOscdimg -ArgumentList @("-bootdata:$BootData",'-u2','-udfver102',"$ISOMedia","$ISOFile") -PassThru -Wait -NoNewWindow if($Proc.ExitCode -ne 0) { Throw "Failed to generate ISO with exitcode: $($Proc.ExitCode)" } if (Test-Path -Path $ISOFilePath){ Write-Host -ForegroundColor Green "ISO File Created: $ISOFile" } else { Write-Host -ForegroundColor Red "Failed to Create ISO File" } } Write-Host -ForegroundColor DarkGray "=========================================================================" } } |