diff --git a/functions/microwin/Invoke-Microwin.ps1 b/functions/microwin/Invoke-Microwin.ps1 index 65b032ab..c6357e8a 100644 --- a/functions/microwin/Invoke-Microwin.ps1 +++ b/functions/microwin/Invoke-Microwin.ps1 @@ -55,6 +55,8 @@ public class PowerManagement { $injectDrivers = $sync.MicrowinInjectDrivers.IsChecked $importDrivers = $sync.MicrowinImportDrivers.IsChecked + $importVirtIO = $sync.MicrowinCopyVirtIO.IsChecked + $mountDir = $sync.MicrowinMountDir.Text $scratchDir = $sync.MicrowinScratchDir.Text @@ -109,7 +111,7 @@ public class PowerManagement { Write-Host "Mounting Windows image. This may take a while." Mount-WindowsImage -ImagePath "$mountDir\sources\install.wim" -Index $index -Path "$scratchDir" if ($?) { - Write-Host "Mounting complete! Performing removal of applications..." + Write-Host "The Windows image has been mounted successfully. Continuing processing..." } else { Write-Host "Could not mount image. Exiting..." Set-WinUtilTaskbaritem -state "Error" -value 1 -overlay "warning" @@ -155,13 +157,18 @@ public class PowerManagement { } } + if ($importVirtIO) { + Write-Host "Copying VirtIO drivers..." + Microwin-CopyVirtIO + } + Write-Host "Remove Features from the image" - Microwin-RemoveFeatures + Microwin-RemoveFeatures -UseCmdlets $true Write-Host "Removing features complete!" Write-Host "Removing OS packages" - Microwin-RemovePackages + Microwin-RemovePackages -UseCmdlets $true Write-Host "Removing Appx Bloat" - Microwin-RemoveProvisionedPackages + Microwin-RemoveProvisionedPackages -UseCmdlets $true # Detect Windows 11 24H2 and add dependency to FileExp to prevent Explorer look from going back - thanks @WitherOrNot and @thecatontheceiling if ((Microwin-TestCompatibleImage $imgVersion $([System.Version]::new(10,0,26100,1))) -eq $true) { @@ -189,8 +196,6 @@ public class PowerManagement { Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Windows\DiagTrack" -Directory Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Windows\InboxApps" -Directory Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Windows\System32\LocationNotificationWindows.exe" - Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Program Files (x86)\Windows Photo Viewer" -Directory - Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Program Files\Windows Photo Viewer" -Directory Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Program Files (x86)\Windows Media Player" -Directory Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Program Files\Windows Media Player" -Directory Microwin-RemoveFileOrDirectory -pathToDelete "$($scratchDir)\Program Files (x86)\Windows Mail" -Directory @@ -280,20 +285,22 @@ public class PowerManagement { reg add "HKLM\zSYSTEM\Setup\LabConfig" /v "BypassTPMCheck" /t REG_DWORD /d 1 /f reg add "HKLM\zSYSTEM\Setup\MoSetup" /v "AllowUpgradesWithUnsupportedTPMOrCPU" /t REG_DWORD /d 1 /f - # Prevent Windows Update Installing so called Expedited Apps - @( - 'EdgeUpdate', - 'DevHomeUpdate', - 'OutlookUpdate', - 'CrossDeviceUpdate' - ) | ForEach-Object { - Write-Host "Removing Windows Expedited App: $_" + # Prevent Windows Update Installing so called Expedited Apps - 24H2 and newer + if ((Microwin-TestCompatibleImage $imgVersion $([System.Version]::new(10,0,26100,1))) -eq $true) { + @( + 'EdgeUpdate', + 'DevHomeUpdate', + 'OutlookUpdate', + 'CrossDeviceUpdate' + ) | ForEach-Object { + Write-Host "Removing Windows Expedited App: $_" - # Copied here After Installation (Online) - # reg delete "HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\$_" /f | Out-Null + # Copied here After Installation (Online) + # reg delete "HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\$_" /f | Out-Null - # When in Offline Image - reg delete "HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\$_" /f | Out-Null + # When in Offline Image + reg delete "HKLM\zSOFTWARE\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\$_" /f | Out-Null + } } reg add "HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "SearchboxTaskbarMode" /t REG_DWORD /d 0 /f diff --git a/functions/microwin/Invoke-MicrowinGetIso.ps1 b/functions/microwin/Invoke-MicrowinGetIso.ps1 index 43f4caed..01db0c18 100644 --- a/functions/microwin/Invoke-MicrowinGetIso.ps1 +++ b/functions/microwin/Invoke-MicrowinGetIso.ps1 @@ -169,7 +169,7 @@ function Invoke-MicrowinGetIso { try { Write-Host "Mounting Iso. Please wait." $mountedISO = Mount-DiskImage -PassThru "$filePath" - Write-Host "Done mounting Iso $mountedISO" + Write-Host "Done mounting Iso `"$($mountedISO.ImagePath)`"" $driveLetter = (Get-Volume -DiskImage $mountedISO).DriveLetter Write-Host "Iso mounted to '$driveLetter'" } catch { @@ -189,7 +189,7 @@ function Invoke-MicrowinGetIso { $sync.MicrowinScratchDirBox.Text ="" } - $UseISOScratchDir = $sync.WPFMicrowinISOScratchDir.IsChecked + $UseISOScratchDir = $sync.WPFMicrowinISOScratchDir.IsChecked if ($UseISOScratchDir) { $sync.MicrowinScratchDirBox.Text=$mountedISOPath @@ -220,10 +220,10 @@ function Invoke-MicrowinGetIso { $sync.BusyText.Text=" - Mounting" Write-Host "Mounting Iso. Please wait." if ($sync.MicrowinScratchDirBox.Text -eq "") { - $mountDir = Join-Path $env:TEMP $randomMicrowin - $scratchDir = Join-Path $env:TEMP $randomMicrowinScratch + $mountDir = Join-Path $env:TEMP $randomMicrowin + $scratchDir = Join-Path $env:TEMP $randomMicrowinScratch } else { - $scratchDir = $sync.MicrowinScratchDirBox.Text+"Scrach" + $scratchDir = $sync.MicrowinScratchDirBox.Text+"Scratch" $mountDir = $sync.MicrowinScratchDirBox.Text+"micro" } @@ -242,8 +242,8 @@ function Invoke-MicrowinGetIso { # xcopy we can verify files and also not copy files that already exist, but hard to measure # xcopy.exe /E /I /H /R /Y /J $DriveLetter":" $mountDir >$null - $totalTime = Measure-Command { Copy-Files "$($driveLetter):" $mountDir -Recurse -Force } - Write-Host "Copy complete! Total Time: $($totalTime.Minutes)m$($totalTime.Seconds)s" + $totalTime = Measure-Command { Copy-Files "$($driveLetter):" "$mountDir" -Recurse -Force } + Write-Host "Copy complete! Total Time: $($totalTime.Minutes) minutes, $($totalTime.Seconds) seconds" $wimFile = "$mountDir\sources\install.wim" Write-Host "Getting image information $wimFile" diff --git a/functions/microwin/Microwin-CopyToUSB.ps1 b/functions/microwin/Microwin-CopyToUSB.ps1 index 06f4219d..f1b37c95 100644 --- a/functions/microwin/Microwin-CopyToUSB.ps1 +++ b/functions/microwin/Microwin-CopyToUSB.ps1 @@ -16,6 +16,54 @@ function Microwin-CopyToUSB([string]$fileToCopy) { } Write-Host "File copied to Ventoy drive $($volume.DriveLetter)" + + # Detect if config files are present, move them if they are, and configure the Ventoy drive to not bypass the requirements + $customVentoyConfig = @' +{ + "control":[ + { "VTOY_WIN11_BYPASS_CHECK": "0" }, + { "VTOY_WIN11_BYPASS_NRO": "0" } + ], + "control_legacy":[ + { "VTOY_WIN11_BYPASS_CHECK": "0" }, + { "VTOY_WIN11_BYPASS_NRO": "0" } + ], + "control_uefi":[ + { "VTOY_WIN11_BYPASS_CHECK": "0" }, + { "VTOY_WIN11_BYPASS_NRO": "0" } + ], + "control_ia32":[ + { "VTOY_WIN11_BYPASS_CHECK": "0" }, + { "VTOY_WIN11_BYPASS_NRO": "0" } + ], + "control_aa64":[ + { "VTOY_WIN11_BYPASS_CHECK": "0" }, + { "VTOY_WIN11_BYPASS_NRO": "0" } + ], + "control_mips":[ + { "VTOY_WIN11_BYPASS_CHECK": "0" }, + { "VTOY_WIN11_BYPASS_NRO": "0" } + ] +} +'@ + + try { + Write-Host "Writing custom Ventoy configuration. Please wait..." + if (Test-Path -Path "$($volume.DriveLetter):\ventoy\ventoy.json" -PathType Leaf) { + Write-Host "A Ventoy configuration file exists. Moving it..." + Move-Item -Path "$($volume.DriveLetter):\ventoy\ventoy.json" -Destination "$($volume.DriveLetter):\ventoy\ventoy.json.old" -Force + Write-Host "Existing Ventoy configuration has been moved to `"ventoy.json.old`". Feel free to put your config back into the `"ventoy.json`" file." + } + if (-not (Test-Path -Path "$($volume.DriveLetter):\ventoy")) { + New-Item -Path "$($volume.DriveLetter):\ventoy" -ItemType Directory -Force | Out-Null + } + $customVentoyConfig | Out-File -FilePath "$($volume.DriveLetter):\ventoy\ventoy.json" -Encoding utf8 -Force + Write-Host "The Ventoy drive has been successfully configured." + } catch { + Write-Host "Could not configure Ventoy drive. Error: $($_.Exception.Message)`n" + Write-Host "Be sure to add the following configuration to the Ventoy drive by either creating a `"ventoy.json`" file in the `"ventoy`" directory (create it if it doesn't exist) or by editing an existing one: `n`n$customVentoyConfig`n" + Write-Host "Failure to do this will cause conflicts with your target ISO file." + } return } } diff --git a/functions/microwin/Microwin-CopyVirtIO.ps1 b/functions/microwin/Microwin-CopyVirtIO.ps1 new file mode 100644 index 00000000..d050d6dc --- /dev/null +++ b/functions/microwin/Microwin-CopyVirtIO.ps1 @@ -0,0 +1,40 @@ +function Microwin-CopyVirtIO { + <# + .SYNOPSIS + Downloads and copies the VirtIO Guest Tools drivers to the target MicroWin ISO + .NOTES + A network connection must be available and the servers of Fedora People must be up. Automatic driver installation will not be added yet - I want this implementation to be reliable. + #> + + try { + Write-Host "Checking existing files..." + if (Test-Path -Path "$($env:TEMP)\virtio.iso" -PathType Leaf) { + Write-Host "VirtIO ISO has been detected. Deleting..." + Remove-Item -Path "$($env:TEMP)\virtio.iso" -Force + } + Write-Host "Getting latest VirtIO drivers. Please wait. This can take some time, depending on your network connection speed and the speed of the servers..." + Start-BitsTransfer -Source "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" -Destination "$($env:TEMP)\virtio.iso" -DisplayName "Downloading VirtIO drivers..." + # Do everything else if the VirtIO ISO exists + if (Test-Path -Path "$($env:TEMP)\virtio.iso" -PathType Leaf) { + Write-Host "Mounting ISO. Please wait." + $virtIO_ISO = Mount-DiskImage -PassThru "$($env:TEMP)\virtio.iso" + $driveLetter = (Get-Volume -DiskImage $virtIO_ISO).DriveLetter + # Create new directory for VirtIO on ISO + New-Item -Path "$mountDir\VirtIO" -ItemType Directory | Out-Null + $totalTime = Measure-Command { Copy-Files "$($driveLetter):" "$mountDir\VirtIO" -Recurse -Force } + Write-Host "VirtIO contents have been successfully copied. Time taken: $($totalTime.Minutes) minutes, $($totalTime.Seconds) seconds`n" + Get-Volume $driveLetter | Get-DiskImage | Dismount-DiskImage + Remove-Item -Path "$($env:TEMP)\virtio.iso" -Force -ErrorAction SilentlyContinue + Write-Host "To proceed with installation of the MicroWin image in QEMU/Proxmox VE:" + Write-Host "1. Proceed with Setup until you reach the disk selection screen, in which you won't see any drives" + Write-Host "2. Click `"Load Driver`" and click Browse" + Write-Host "3. In the folder selection dialog, point to this path:`n`n `"D:\VirtIO\vioscsi\w11\amd64`" (replace amd64 with ARM64 if you are using Windows on ARM, and `"D:`" with the drive letter of the ISO)`n" + Write-Host "4. Select all drivers that will appear in the list box and click OK" + } else { + throw "Could not download VirtIO drivers" + } + } catch { + Write-Host "We could not download and/or prepare the VirtIO drivers. Error information: $_`n" + Write-Host "You will need to download these drivers manually. Location: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso" + } +} diff --git a/functions/microwin/Microwin-NewFirstRun.ps1 b/functions/microwin/Microwin-NewFirstRun.ps1 index 614df6bc..d6e5d4b7 100644 --- a/functions/microwin/Microwin-NewFirstRun.ps1 +++ b/functions/microwin/Microwin-NewFirstRun.ps1 @@ -63,6 +63,22 @@ function Microwin-NewFirstRun { { } + + # Get BCD entries and set bootmgr timeout accordingly + try + { + # Check if the number of occurrences of "path" is 2 - this fixes the Boot Manager screen issue (#2562) + if ((bcdedit | Select-String "path").Count -eq 2) + { + # Set bootmgr timeout to 0 + bcdedit /set `{bootmgr`} timeout 0 + } + } + catch + { + + } + '@ $firstRun | Out-File -FilePath "$env:temp\FirstStartup.ps1" -Force } diff --git a/functions/microwin/Microwin-NewUnattend.ps1 b/functions/microwin/Microwin-NewUnattend.ps1 index 87188aca..dda71bb3 100644 --- a/functions/microwin/Microwin-NewUnattend.ps1 +++ b/functions/microwin/Microwin-NewUnattend.ps1 @@ -31,7 +31,7 @@ function Microwin-NewUnattend { Administrators PW-REPLACEME - true</PlainText> + <PlainText>PT-STATUS</PlainText> </Password> </LocalAccount> </LocalAccounts> @@ -42,7 +42,7 @@ function Microwin-NewUnattend { <LogonCount>1</LogonCount> <Password> <Value>PW-REPLACEME</Value> - <PlainText>true</PlainText> + <PlainText>PT-STATUS</PlainText> </Password> </AutoLogon> <OOBE> @@ -295,15 +295,40 @@ function Microwin-NewUnattend { </settings> '@ if ((Microwin-TestCompatibleImage $imgVersion $([System.Version]::new(10,0,22000,1))) -eq $false) { - # Replace the placeholder text with an empty string to make it valid for Windows 10 Setup - $unattend = $unattend.Replace("<#REPLACEME#>", "").Trim() + # Replace the placeholder text with an empty string to make it valid for Windows 10 Setup + $unattend = $unattend.Replace("<#REPLACEME#>", "").Trim() } else { - # Replace the placeholder text with the Specialize pass - $unattend = $unattend.Replace("<#REPLACEME#>", $specPass).Trim() + # Replace the placeholder text with the Specialize pass + $unattend = $unattend.Replace("<#REPLACEME#>", $specPass).Trim() } + + # User password in Base64. According to Microsoft, this is the way you can hide this sensitive information. + # More information can be found here: https://learn.microsoft.com/en-us/windows-hardware/customize/desktop/wsim/hide-sensitive-data-in-an-answer-file + # Yeah, I know this is not the best way to protect this kind of data, but we all know how Microsoft is - "the Apple of security" (in a sense, it takes them + # an eternity to implement basic security features right. Just look at the NTLM and Kerberos situation!) + + $b64pass = "" + # Replace default User and Password values with the provided parameters $unattend = $unattend.Replace("USER-REPLACEME", $userName).Trim() - $unattend = $unattend.Replace("PW-REPLACEME", $userPassword).Trim() + try { + # I want to play it safe here - I don't want encoding mismatch problems like last time + + # NOTE: "Password" needs to be appended to the password specified by the user. Otherwise, a parse error will occur when processing oobeSystem. + # This will not be added to the actual password stored in the target system's SAM file - only the provided password + $b64pass = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes("$($userPassword)Password")) + } catch { + $b64pass = "" + } + if ($b64pass -ne "") { + # If we could encode the password with Base64, put it in the answer file and indicate that it's NOT in plain text + $unattend = $unattend.Replace("PW-REPLACEME", $b64pass).Trim() + $unattend = $unattend.Replace("PT-STATUS", "false").Trim() + $b64pass = "" + } else { + $unattend = $unattend.Replace("PW-REPLACEME", $userPassword).Trim() + $unattend = $unattend.Replace("PT-STATUS", "true").Trim() + } # Save unattended answer file with UTF-8 encoding $unattend | Out-File -FilePath "$env:temp\unattend.xml" -Force -Encoding utf8 diff --git a/functions/microwin/Microwin-RemoveFeatures.ps1 b/functions/microwin/Microwin-RemoveFeatures.ps1 index 5b37adb2..be5888dc 100644 --- a/functions/microwin/Microwin-RemoveFeatures.ps1 +++ b/functions/microwin/Microwin-RemoveFeatures.ps1 @@ -3,38 +3,80 @@ function Microwin-RemoveFeatures() { .SYNOPSIS Removes certain features from ISO image - .PARAMETER Name - No Params + .PARAMETER UseCmdlets + Determines whether or not to use the DISM cmdlets for processing. + - If true, DISM cmdlets will be used + - If false, calls to the DISM executable will be made whilst selecting bits and pieces from the output as a string (that was how MicroWin worked before + the DISM conversion to cmdlets) .EXAMPLE - Microwin-RemoveFeatures + Microwin-RemoveFeatures -UseCmdlets $true #> + param ( + [Parameter(Mandatory = $true, Position = 0)] [bool]$UseCmdlets + ) try { - $featlist = (Get-WindowsOptionalFeature -Path $scratchDir) + if ($UseCmdlets) { + $featlist = (Get-WindowsOptionalFeature -Path "$scratchDir") - $featlist = $featlist | Where-Object { - $_.FeatureName -NotLike "*Defender*" -AND - $_.FeatureName -NotLike "*Printing*" -AND - $_.FeatureName -NotLike "*TelnetClient*" -AND - $_.FeatureName -NotLike "*PowerShell*" -AND - $_.FeatureName -NotLike "*NetFx*" -AND - $_.FeatureName -NotLike "*Media*" -AND - $_.FeatureName -NotLike "*NFS*" -AND - $_.FeatureName -NotLike "*SearchEngine*" -AND - $_.FeatureName -NotLike "*RemoteDesktop*" -AND - $_.State -ne "Disabled" + $featlist = $featlist | Where-Object { + $_.FeatureName -NotLike "*Defender*" -AND + $_.FeatureName -NotLike "*Printing*" -AND + $_.FeatureName -NotLike "*TelnetClient*" -AND + $_.FeatureName -NotLike "*PowerShell*" -AND + $_.FeatureName -NotLike "*NetFx*" -AND + $_.FeatureName -NotLike "*Media*" -AND + $_.FeatureName -NotLike "*NFS*" -AND + $_.FeatureName -NotLike "*SearchEngine*" -AND + $_.FeatureName -NotLike "*RemoteDesktop*" -AND + $_.State -ne "Disabled" + } + } else { + $featList = dism /english /image="$scratchDir" /get-features | Select-String -Pattern "Feature Name : " -CaseSensitive -SimpleMatch + if ($?) { + $featList = $featList -split "Feature Name : " | Where-Object {$_} + # Exclude the same items. Note: for now, this doesn't exclude those features that are disabled. + # This will appear in the future + $featList = $featList | Where-Object { + $_ -NotLike "*Defender*" -AND + $_ -NotLike "*Printing*" -AND + $_ -NotLike "*TelnetClient*" -AND + $_ -NotLike "*PowerShell*" -AND + $_ -NotLike "*NetFx*" -AND + $_ -NotLike "*Media*" -AND + $_ -NotLike "*NFS*" -AND + $_ -NotLike "*SearchEngine*" -AND + $_ -NotLike "*RemoteDesktop*" + } + } else { + Write-Host "Features could not be obtained with DISM. MicroWin processing will continue, but features will be skipped." + return + } } - foreach($feature in $featlist) { - $status = "Removing feature $($feature.FeatureName)" - Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) - Write-Debug "Removing feature $($feature.FeatureName)" - Disable-WindowsOptionalFeature -Path "$scratchDir" -FeatureName $($feature.FeatureName) -Remove -ErrorAction SilentlyContinue -NoRestart + if ($UseCmdlets) { + foreach ($feature in $featList) { + $status = "Removing feature $($feature.FeatureName)" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) + Write-Debug "Removing feature $($feature.FeatureName)" + Disable-WindowsOptionalFeature -Path "$scratchDir" -FeatureName $($feature.FeatureName) -Remove -ErrorAction SilentlyContinue -NoRestart + } + } else { + foreach ($feature in $featList) { + $status = "Removing feature $feature" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) + Write-Debug "Removing feature $feature" + dism /english /image="$scratchDir" /disable-feature /featurename=$feature /remove /quiet /norestart | Out-Null + if ($? -eq $false) { + Write-Host "Feature $feature could not be disabled." + } + } } Write-Progress -Activity "Removing features" -Status "Ready" -Completed Write-Host "You can re-enable the disabled features at any time, using either Windows Update or the SxS folder in <installation media>\Sources." } catch { - Write-Host "Unable to get information about the features. MicroWin processing will continue, but features will not be processed" + Write-Host "Unable to get information about the features. A fallback will be used..." Write-Host "Error information: $($_.Exception.Message)" -ForegroundColor Yellow + Microwin-RemoveFeatures -UseCmdlets $false } } diff --git a/functions/microwin/Microwin-RemovePackages.ps1 b/functions/microwin/Microwin-RemovePackages.ps1 index ed53056c..837bdbf1 100644 --- a/functions/microwin/Microwin-RemovePackages.ps1 +++ b/functions/microwin/Microwin-RemovePackages.ps1 @@ -1,71 +1,103 @@ function Microwin-RemovePackages { + <# + .SYNOPSIS + Removes certain packages from ISO image + + .PARAMETER UseCmdlets + Determines whether or not to use the DISM cmdlets for processing. + - If true, DISM cmdlets will be used + - If false, calls to the DISM executable will be made whilst selecting bits and pieces from the output as a string (that was how MicroWin worked before + the DISM conversion to cmdlets) + + .EXAMPLE + Microwin-RemovePackages -UseCmdlets $true + #> + param ( + [Parameter(Mandatory = $true, Position = 0)] [bool]$UseCmdlets + ) try { - $pkglist = (Get-WindowsPackage -Path "$scratchDir").PackageName + if ($useCmdlets) { + $pkglist = (Get-WindowsPackage -Path "$scratchDir").PackageName - $pkglist = $pkglist | Where-Object { - $_ -NotLike "*ApplicationModel*" -AND - $_ -NotLike "*indows-Client-LanguagePack*" -AND - $_ -NotLike "*LanguageFeatures-Basic*" -AND - $_ -NotLike "*Package_for_ServicingStack*" -AND - $_ -NotLike "*.NET*" -AND - $_ -NotLike "*Store*" -AND - $_ -NotLike "*VCLibs*" -AND - $_ -NotLike "*AAD.BrokerPlugin", - $_ -NotLike "*LockApp*" -AND - $_ -NotLike "*Notepad*" -AND - $_ -NotLike "*immersivecontrolpanel*" -AND - $_ -NotLike "*ContentDeliveryManager*" -AND - $_ -NotLike "*PinningConfirMationDialog*" -AND - $_ -NotLike "*SecHealthUI*" -AND - $_ -NotLike "*SecureAssessmentBrowser*" -AND - $_ -NotLike "*PrintDialog*" -AND - $_ -NotLike "*AssignedAccessLockApp*" -AND - $_ -NotLike "*OOBENetworkConnectionFlow*" -AND - $_ -NotLike "*Apprep.ChxApp*" -AND - $_ -NotLike "*CBS*" -AND - $_ -NotLike "*OOBENetworkCaptivePortal*" -AND - $_ -NotLike "*PeopleExperienceHost*" -AND - $_ -NotLike "*ParentalControls*" -AND - $_ -NotLike "*Win32WebViewHost*" -AND - $_ -NotLike "*InputApp*" -AND - $_ -NotLike "*DirectPlay*" -AND - $_ -NotLike "*AccountsControl*" -AND - $_ -NotLike "*AsyncTextService*" -AND - $_ -NotLike "*CapturePicker*" -AND - $_ -NotLike "*CredDialogHost*" -AND - $_ -NotLike "*BioEnrollMent*" -AND - $_ -NotLike "*ShellExperienceHost*" -AND - $_ -NotLike "*DesktopAppInstaller*" -AND - $_ -NotLike "*WebMediaExtensions*" -AND - $_ -NotLike "*WMIC*" -AND - $_ -NotLike "*UI.XaML*" -AND - $_ -NotLike "*Ethernet*" -AND - $_ -NotLike "*Wifi*" -AND - $_ -NotLike "*FodMetadata*" -AND - $_ -NotLike "*Foundation*" -AND - $_ -NotLike "*LanguageFeatures*" -AND - $_ -NotLike "*VBSCRIPT*" -AND - $_ -NotLike "*License*" + $pkglist = $pkglist | Where-Object { + $_ -NotLike "*ApplicationModel*" -AND + $_ -NotLike "*indows-Client-LanguagePack*" -AND + $_ -NotLike "*LanguageFeatures-Basic*" -AND + $_ -NotLike "*Package_for_ServicingStack*" -AND + $_ -NotLike "*DotNet*" -AND + $_ -NotLike "*Notepad*" -AND + $_ -NotLike "*WMIC*" -AND + $_ -NotLike "*Ethernet*" -AND + $_ -NotLike "*Wifi*" -AND + $_ -NotLike "*FodMetadata*" -AND + $_ -NotLike "*Foundation*" -AND + $_ -NotLike "*LanguageFeatures*" -AND + $_ -NotLike "*VBSCRIPT*" -AND + $_ -NotLike "*License*" -AND + $_ -NotLike "*Hello-Face*" -AND + $_ -NotLike "*ISE*" -AND + $_ -NotLike "*OpenSSH*" + } + } else { + $pkgList = dism /english /image="$scratchDir" /get-packages | Select-String -Pattern "Package Identity : " -CaseSensitive -SimpleMatch + if ($?) { + $pkgList = $pkgList -split "Package Identity : " | Where-Object {$_} + # Exclude the same items. + $pkgList = $pkgList | Where-Object { + $_ -NotLike "*ApplicationModel*" -AND + $_ -NotLike "*indows-Client-LanguagePack*" -AND + $_ -NotLike "*LanguageFeatures-Basic*" -AND + $_ -NotLike "*Package_for_ServicingStack*" -AND + $_ -NotLike "*DotNet*" -AND + $_ -NotLike "*Notepad*" -AND + $_ -NotLike "*WMIC*" -AND + $_ -NotLike "*Ethernet*" -AND + $_ -NotLike "*Wifi*" -AND + $_ -NotLike "*FodMetadata*" -AND + $_ -NotLike "*Foundation*" -AND + $_ -NotLike "*LanguageFeatures*" -AND + $_ -NotLike "*VBSCRIPT*" -AND + $_ -NotLike "*License*" -AND + $_ -NotLike "*Hello-Face*" -AND + $_ -NotLike "*ISE*" -AND + $_ -NotLike "*OpenSSH*" + } + } else { + Write-Host "Packages could not be obtained with DISM. MicroWin processing will continue, but packages will be skipped." + return } + } - $failedCount = 0 + if ($UseCmdlets) { + $failedCount = 0 - $erroredPackages = [System.Collections.Generic.List[ErroredPackage]]::new() + $erroredPackages = [System.Collections.Generic.List[ErroredPackage]]::new() - foreach ($pkg in $pkglist) { - try { - $status = "Removing $pkg" - Write-Progress -Activity "Removing Packages" -Status $status -PercentComplete ($counter++/$pkglist.Count*100) - Remove-WindowsPackage -Path "$scratchDir" -PackageName $pkg -NoRestart -ErrorAction SilentlyContinue - } catch { - # This can happen if the package that is being removed is a permanent one - $erroredPackages.Add([ErroredPackage]::new($pkg, $_.Exception.Message)) - $failedCount += 1 - continue + foreach ($pkg in $pkglist) { + try { + $status = "Removing $pkg" + Write-Progress -Activity "Removing Packages" -Status $status -PercentComplete ($counter++/$pkglist.Count*100) + Remove-WindowsPackage -Path "$scratchDir" -PackageName $pkg -NoRestart -ErrorAction SilentlyContinue + } catch { + # This can happen if the package that is being removed is a permanent one + $erroredPackages.Add([ErroredPackage]::new($pkg, $_.Exception.Message)) + $failedCount += 1 + continue + } + } + } else { + foreach ($package in $pkgList) { + $status = "Removing package $package" + Write-Progress -Activity "Removing features" -Status $status -PercentComplete ($counter++/$featlist.Count*100) + Write-Debug "Removing package $package" + dism /english /image="$scratchDir" /remove-package /packagename=$package /remove /quiet /norestart | Out-Null + if ($? -eq $false) { + Write-Host "Package $package could not be removed." + } } } Write-Progress -Activity "Removing Packages" -Status "Ready" -Completed - if ($failedCount -gt 0) + if ($UseCmdlets -and $failedCount -gt 0) { Write-Host "$failedCount package(s) could not be removed. Your image will still work fine, however. Below is information on what packages failed to be removed and why." if ($erroredPackages.Count -gt 0) @@ -90,7 +122,8 @@ function Microwin-RemovePackages { } } } catch { - Write-Host "Unable to get information about the packages. MicroWin processing will continue, but packages will not be processed" + Write-Host "Unable to get information about the packages. A fallback will be used..." Write-Host "Error information: $($_.Exception.Message)" -ForegroundColor Yellow + Microwin-RemovePackages -UseCmdlets $false } } diff --git a/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 b/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 index 30bc0d74..b148c2d9 100644 --- a/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 +++ b/functions/microwin/Microwin-RemoveProvisionedPackages.ps1 @@ -3,49 +3,94 @@ function Microwin-RemoveProvisionedPackages() { .SYNOPSIS Removes AppX packages from a Windows image during MicroWin processing - .PARAMETER Name - No Params + .PARAMETER UseCmdlets + Determines whether or not to use the DISM cmdlets for processing. + - If true, DISM cmdlets will be used + - If false, calls to the DISM executable will be made whilst selecting bits and pieces from the output as a string (that was how MicroWin worked before + the DISM conversion to cmdlets) .EXAMPLE Microwin-RemoveProvisionedPackages #> + param ( + [Parameter(Mandatory = $true, Position = 0)] [bool]$UseCmdlets + ) try { - $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { - $_.PackageName -NotLike "*AppInstaller*" -AND - $_.PackageName -NotLike "*Store*" -and - $_.PackageName -NotLike "*Notepad*" -and - $_.PackageName -NotLike "*Printing*" -and - $_.PackageName -NotLike "*YourPhone*" -and - $_.PackageName -NotLike "*Xbox*" -and - $_.PackageName -NotLike "*WindowsTerminal*" -and - $_.PackageName -NotLike "*Calculator*" -and - $_.PackageName -NotLike "*Photos*" -and - $_.PackageName -NotLike "*VCLibs*" -and - $_.PackageName -NotLike "*Paint*" -and - $_.PackageName -NotLike "*Gaming*" -and - $_.PackageName -NotLike "*Extension*" -and - $_.PackageName -NotLike "*SecHealthUI*" -and - $_.PackageName -NotLike "*ScreenSketch*" + if ($UseCmdlets) { + $appxProvisionedPackages = Get-AppxProvisionedPackage -Path "$($scratchDir)" | Where-Object { + $_.PackageName -NotLike "*AppInstaller*" -AND + $_.PackageName -NotLike "*Store*" -and + $_.PackageName -NotLike "*Notepad*" -and + $_.PackageName -NotLike "*Printing*" -and + $_.PackageName -NotLike "*YourPhone*" -and + $_.PackageName -NotLike "*Xbox*" -and + $_.PackageName -NotLike "*WindowsTerminal*" -and + $_.PackageName -NotLike "*Calculator*" -and + $_.PackageName -NotLike "*Photos*" -and + $_.PackageName -NotLike "*VCLibs*" -and + $_.PackageName -NotLike "*Paint*" -and + $_.PackageName -NotLike "*Gaming*" -and + $_.PackageName -NotLike "*Extension*" -and + $_.PackageName -NotLike "*SecHealthUI*" -and + $_.PackageName -NotLike "*ScreenSketch*" + } + } else { + $appxProvisionedPackages = dism /english /image="$scratchDir" /get-provisionedappxpackages | Select-String -Pattern "PackageName : " -CaseSensitive -SimpleMatch + if ($?) { + $appxProvisionedPackages = $appxProvisionedPackages -split "PackageName : " | Where-Object {$_} + # Exclude the same items. + $appxProvisionedPackages = $appxProvisionedPackages | Where-Object { + $_ -NotLike "*AppInstaller*" -AND + $_ -NotLike "*Store*" -and + $_ -NotLike "*Notepad*" -and + $_ -NotLike "*Printing*" -and + $_ -NotLike "*YourPhone*" -and + $_ -NotLike "*Xbox*" -and + $_ -NotLike "*WindowsTerminal*" -and + $_ -NotLike "*Calculator*" -and + $_ -NotLike "*Photos*" -and + $_ -NotLike "*VCLibs*" -and + $_ -NotLike "*Paint*" -and + $_ -NotLike "*Gaming*" -and + $_ -NotLike "*Extension*" -and + $_ -NotLike "*SecHealthUI*" -and + $_ -NotLike "*ScreenSketch*" + } + } else { + Write-Host "AppX packages could not be obtained with DISM. MicroWin processing will continue, but AppX packages will be skipped." + return + } } $counter = 0 - foreach ($appx in $appxProvisionedPackages) { - $status = "Removing Provisioned $($appx.PackageName)" - Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) - try { - Remove-AppxProvisionedPackage -Path "$scratchDir" -PackageName $appx.PackageName -ErrorAction SilentlyContinue - } catch { - Write-Host "Application $($appx.PackageName) could not be removed" - continue + if ($UseCmdlets) { + foreach ($appx in $appxProvisionedPackages) { + $status = "Removing Provisioned $($appx.PackageName)" + Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) + try { + Remove-AppxProvisionedPackage -Path "$scratchDir" -PackageName $appx.PackageName -ErrorAction SilentlyContinue + } catch { + Write-Host "Application $($appx.PackageName) could not be removed" + continue + } + } + } else { + foreach ($appx in $appxProvisionedPackages) { + $status = "Removing Provisioned $appx" + Write-Progress -Activity "Removing Provisioned Apps" -Status $status -PercentComplete ($counter++/$appxProvisionedPackages.Count*100) + dism /english /image="$scratchDir" /remove-provisionedappxpackage /packagename=$appx /quiet /norestart | Out-Null + if ($? -eq $false) { + Write-Host "AppX package $appx could not be removed." + } } } Write-Progress -Activity "Removing Provisioned Apps" -Status "Ready" -Completed } catch { - # This can happen if getting AppX packages fails - Write-Host "Unable to get information about the AppX packages. MicroWin processing will continue, but AppX packages will not be processed" + Write-Host "Unable to get information about the AppX packages. A fallback will be used..." Write-Host "Error information: $($_.Exception.Message)" -ForegroundColor Yellow + Microwin-RemoveProvisionedPackages -UseCmdlets $false } } diff --git a/functions/private/Copy-Files.ps1 b/functions/private/Copy-Files.ps1 index fafb4b51..cec7869a 100644 --- a/functions/private/Copy-Files.ps1 +++ b/functions/private/Copy-Files.ps1 @@ -2,11 +2,17 @@ function Copy-Files { <# .DESCRIPTION - This function will make all modifications to the registry - + Copies the contents of a given ISO file to a given destination + .PARAMETER Path + The source of the files to copy + .PARAMETER Destination + The destination to copy the files to + .PARAMETER Recurse + Determines whether or not to copy all files of the ISO file, including those in subdirectories + .PARAMETER Force + Determines whether or not to overwrite existing files .EXAMPLE - - Set-WinUtilRegistry -Name "PublishUserActivities" -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\System" -Type "DWord" -Value "0" + Copy-Files "D:" "C:\ISOFile" -Recurse -Force #> param ( @@ -23,7 +29,7 @@ function Copy-Files { foreach ($file in $files) { $status = "Copying file {0} of {1}: {2}" -f $counter, $files.Count, $file.Name - Write-Progress -Activity "Copy Windows files" -Status $status -PercentComplete ($counter++/$files.count*100) + Write-Progress -Activity "Copy disc image files" -Status $status -PercentComplete ($counter++/$files.count*100) $restpath = $file.FullName -Replace $path, '' if ($file.PSIsContainer -eq $true) { @@ -35,7 +41,7 @@ function Copy-Files { Set-ItemProperty -Path ($destination+$restpath) -Name IsReadOnly -Value $false } } - Write-Progress -Activity "Copy Windows files" -Status "Ready" -Completed + Write-Progress -Activity "Copy disc image files" -Status "Ready" -Completed } catch { Write-Host "Unable to Copy all the files due to an unhandled exception" -ForegroundColor Yellow Write-Host "Error information: $($_.Exception.Message)`n" -ForegroundColor Yellow diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index f28d421c..39610760 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -1111,8 +1111,10 @@ Choose a Windows ISO file that you've downloaded <LineBreak/> Check the status in the console </TextBlock> + <Rectangle Fill="{DynamicResource MainForegroundColor}" Height="2" HorizontalAlignment="Stretch" Margin="0,10,0,10"/> + <TextBlock Margin="5" Padding="1" TextWrapping="Wrap" Foreground="{DynamicResource ComboBoxForegroundColor}" ToolTip="Scratch directories act as a custom destination for image files"><Bold>Scratch directory settings (optional)</Bold></TextBlock> <CheckBox x:Name="WPFMicrowinISOScratchDir" Content="Use ISO directory for ScratchDir " IsChecked="False" Margin="{DynamicResource MicrowinCheckBoxMargin}" - ToolTip="Use ISO directory for ScratchDir " /> + ToolTip="Check this to use the path of the ISO file you specify as a scratch directory" /> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <!-- Takes the remaining space --> @@ -1122,7 +1124,7 @@ Text="Scratch" Margin="2" IsReadOnly="False" - ToolTip="Alt Path For Scratch Directory" + ToolTip="Specify an alternate path for the scratch directory" Grid.Column="0" VerticalAlignment="Center" Foreground="{DynamicResource LabelboxForegroundColor}"> @@ -1138,6 +1140,7 @@ </Button.Content> </Button> </Grid> + <Rectangle Fill="{DynamicResource MainForegroundColor}" Height="2" HorizontalAlignment="Stretch" Margin="0,10,0,10"/> <TextBox Name="MicrowinFinalIsoLocation" Background="Transparent" BorderBrush="{DynamicResource MainForegroundColor}" Text="ISO location will be printed here" Margin="2" @@ -1172,6 +1175,7 @@ ToolTip="Path to unpacked drivers all sys and inf files for devices that need drivers" /> <CheckBox Name="MicrowinImportDrivers" Content="Import drivers from current system" Margin="{DynamicResource MicrowinCheckBoxMargin}" IsChecked="False" ToolTip="Export all third-party drivers from your system and inject them to the MicroWin image"/> + <CheckBox Name="MicrowinCopyVirtIO" Content="Include VirtIO drivers" Margin="{DynamicResource MicrowinCheckBoxMargin}" IsChecked="False" ToolTip="Copy VirtIO Guest Tools drivers to your ISO file. Check this only if you want to use it on QEMU/Proxmox VE"/> <Rectangle Fill="{DynamicResource MainForegroundColor}" Height="2" HorizontalAlignment="Stretch" Margin="0,10,0,10"/> <CheckBox Name="WPFMicrowinCopyToUsb" Content="Copy to Ventoy" Margin="{DynamicResource MicrowinCheckBoxMargin}" IsChecked="False" ToolTip="Copy to USB disk with a label Ventoy"/> <Rectangle Fill="{DynamicResource MainForegroundColor}" Height="2" HorizontalAlignment="Stretch" Margin="0,10,0,10"/> @@ -1262,7 +1266,13 @@ - Once complete, the target ISO file will be in the directory you have specified <LineBreak/> - Copy this image to your Ventoy USB Stick, boot to this image, gg <LineBreak/> - If you are injecting drivers ensure you put all your inf, sys, and dll files for each driver into a separate directory + If you are injecting drivers ensure you put all your inf, sys, and dll files for each driver into a separate directory <LineBreak/><LineBreak/> + <Bold>Installing VirtIO drivers</Bold><LineBreak/> + If you plan on using your ISO on QEMU/Proxmox VE, you can bundle VirtIO drivers with your ISO to automatically install drivers. Simply tick the "Include VirtIO drivers" checkbox before starting the process. Then, follow these instructions:<LineBreak/><LineBreak/> + <TextBlock TextWrapping="WrapWithOverflow" Margin="15,0,0,0" Text="1. Proceed with Setup until you reach the disk selection screen, in which you won't see any drives" Foreground="{DynamicResource ComboBoxForegroundColor}"/><LineBreak/> + <TextBlock TextWrapping="WrapWithOverflow" Margin="15,0,0,0" Text="2. Click &quot;Load Driver&quot; and click Browse" Foreground="{DynamicResource ComboBoxForegroundColor}"/><LineBreak/> + <TextBlock TextWrapping="WrapWithOverflow" Margin="15,0,0,0" Text="3. In the folder selection dialog, point to this path: &quot;D:\VirtIO\vioscsi\w11\amd64&quot; (replace amd64 with ARM64 if you are using Windows on ARM, and &quot;D:&quot; with the drive letter of the ISO)" Foreground="{DynamicResource ComboBoxForegroundColor}"/><LineBreak/> + <TextBlock TextWrapping="WrapWithOverflow" Margin="15,0,0,0" Text="4. Select all drivers that will appear in the list box and click OK" Foreground="{DynamicResource ComboBoxForegroundColor}"/><LineBreak/> </TextBlock> <TextBlock Margin="15,0,15,15" Padding = "1" @@ -1273,7 +1283,7 @@ Foreground = "{DynamicResource ComboBoxForegroundColor}" xml:space = "preserve" > -<Bold>Example:</Bold> +<Bold>Driver structure example:</Bold> C:\drivers\ |-- Driver1\ | |-- Driver1.inf @@ -1282,7 +1292,7 @@ | |-- Driver2.inf | |-- Driver2.sys |-- OtherFiles... - </TextBlock> + </TextBlock> </StackPanel> </Border> </Grid>