mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-08-12 21:53:05 -06:00
Add unified FFU disk layout controls
Consolidate disk size, required partition drive letters, Recovery, and data partition settings into the Hyper-V Disk Layout UI. Add capacity validation, Fill Remaining handling, Recovery remove/restore, data partition reorder/remove controls, and persist CreateRecoveryPartition through config and VHDX cache checks. Update FFU optimization so automatic partition selection targets the Fill Remaining partition, falling back to Windows when none is selected. Refresh sample config and docs for the new Disk Layout parameters.
This commit is contained in:
@@ -111,6 +111,9 @@ Fixed size of the Windows partition in bytes. Required when AdditionalDataPartit
|
||||
.PARAMETER RecoveryPartitionSize
|
||||
Optional fixed size of the Recovery partition in bytes. Leave as 0 to calculate the Recovery partition size from winre.wim plus buffer space.
|
||||
|
||||
.PARAMETER CreateRecoveryPartition
|
||||
When set to $false, skips creating the Windows Recovery partition. Default is $true.
|
||||
|
||||
.PARAMETER AdditionalDataPartitions
|
||||
Optional data partitions to create after the Recovery partition. Each item supports Name, Label, DriveLetter, SizeBytes or SizeGB, FillRemaining, and FileSystem.
|
||||
|
||||
@@ -187,7 +190,7 @@ Path to a custom Office configuration XML file to use for installation.
|
||||
When set to $true, will optimize the FFU file. Default is $true.
|
||||
|
||||
.PARAMETER OptimizeFFUPartitionNumber
|
||||
Optional partition number to pass to DISM /Optimize-FFU /PartitionNumber. Leave as 0 to optimize with DISM defaults.
|
||||
Optional partition number to pass to DISM /Optimize-FFU /PartitionNumber. Leave as 0 to optimize the FillRemaining partition, or Windows when no partition uses FillRemaining.
|
||||
|
||||
.PARAMETER OptionalFeatures
|
||||
Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP).
|
||||
@@ -366,6 +369,7 @@ param(
|
||||
[uint64]$Disksize = 50GB,
|
||||
[uint64]$OSPartitionSize = 0,
|
||||
[uint64]$RecoveryPartitionSize = 0,
|
||||
[bool]$CreateRecoveryPartition = $true,
|
||||
[object[]]$AdditionalDataPartitions = @(),
|
||||
[int]$Processors = 4,
|
||||
[bool]$EnableVMNetworking,
|
||||
@@ -898,6 +902,7 @@ class VhdxCacheItem {
|
||||
[string]$VhdxFileName = ""
|
||||
[uint32]$LogicalSectorSizeBytes = ""
|
||||
[uint64]$Disksize = ""
|
||||
[bool]$CreateRecoveryPartition = $true
|
||||
[string]$SystemPartitionDriveLetter = ""
|
||||
[string]$WindowsPartitionDriveLetter = ""
|
||||
[string]$RecoveryPartitionDriveLetter = ""
|
||||
@@ -3089,14 +3094,17 @@ function Get-NormalizedPartitionDriveLetters {
|
||||
[string]$SystemPartitionDriveLetter,
|
||||
[string]$WindowsPartitionDriveLetter,
|
||||
[string]$RecoveryPartitionDriveLetter,
|
||||
[bool]$CreateRecoveryPartition = $true,
|
||||
[object[]]$AdditionalDataPartitions = @(),
|
||||
[switch]$ValidateAvailable
|
||||
)
|
||||
|
||||
$requestedLetters = [ordered]@{
|
||||
SystemPartitionDriveLetter = $SystemPartitionDriveLetter
|
||||
WindowsPartitionDriveLetter = $WindowsPartitionDriveLetter
|
||||
RecoveryPartitionDriveLetter = $RecoveryPartitionDriveLetter
|
||||
SystemPartitionDriveLetter = $SystemPartitionDriveLetter
|
||||
WindowsPartitionDriveLetter = $WindowsPartitionDriveLetter
|
||||
}
|
||||
if ($CreateRecoveryPartition) {
|
||||
$requestedLetters['RecoveryPartitionDriveLetter'] = $RecoveryPartitionDriveLetter
|
||||
}
|
||||
foreach ($dataPartition in @($AdditionalDataPartitions)) {
|
||||
if ($null -eq $dataPartition) { continue }
|
||||
@@ -3123,7 +3131,7 @@ function Get-NormalizedPartitionDriveLetters {
|
||||
$duplicateLetters = @($normalizedLetters.Values | Group-Object | Where-Object { $_.Count -gt 1 })
|
||||
if ($duplicateLetters.Count -gt 0) {
|
||||
$duplicateLetterList = ($duplicateLetters | ForEach-Object { $_.Name }) -join ', '
|
||||
throw "System, Windows, and Recovery partition drive letters must be unique. Duplicate value(s): $duplicateLetterList."
|
||||
throw "Build partition drive letters must be unique. Duplicate value(s): $duplicateLetterList."
|
||||
}
|
||||
|
||||
if ($ValidateAvailable) {
|
||||
@@ -3229,6 +3237,7 @@ function Get-PartitionLayoutSignature {
|
||||
param(
|
||||
[uint64]$OSPartitionSize,
|
||||
[uint64]$RecoveryPartitionSize,
|
||||
[bool]$CreateRecoveryPartition = $true,
|
||||
[object[]]$DataPartitions = @()
|
||||
)
|
||||
|
||||
@@ -3236,7 +3245,7 @@ function Get-PartitionLayoutSignature {
|
||||
"$($_.Name)|$($_.Label)|$($_.DriveLetter)|$($_.FileSystem)|$($_.SizeBytes)|$($_.FillRemaining)"
|
||||
})
|
||||
|
||||
return "OS=$OSPartitionSize;Recovery=$RecoveryPartitionSize;Data=$($dataPartitionSignatures -join ';')"
|
||||
return "OS=$OSPartitionSize;CreateRecovery=$CreateRecoveryPartition;Recovery=$RecoveryPartitionSize;Data=$($dataPartitionSignatures -join ';')"
|
||||
}
|
||||
function Get-PartitionDriveLetterCacheValue {
|
||||
param(
|
||||
@@ -3451,6 +3460,56 @@ function Get-WindowsPartitionFromDisk {
|
||||
|
||||
return $basicDataPartitions | Select-Object -First 1
|
||||
}
|
||||
function Get-FFUOptimizePartitionNumber {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ciminstance]$Disk,
|
||||
[int]$RequestedPartitionNumber = 0,
|
||||
[string]$WindowsPartitionDriveLetter,
|
||||
[object[]]$AdditionalDataPartitions = @()
|
||||
)
|
||||
|
||||
if ($RequestedPartitionNumber -gt 0) {
|
||||
return $RequestedPartitionNumber
|
||||
}
|
||||
|
||||
$fillRemainingDataPartition = $null
|
||||
foreach ($dataPartition in @($AdditionalDataPartitions)) {
|
||||
if ($null -eq $dataPartition) { continue }
|
||||
|
||||
$fillRemaining = $false
|
||||
if ($dataPartition.PSObject.Properties.Name -contains 'FillRemaining') {
|
||||
$fillRemaining = [System.Convert]::ToBoolean($dataPartition.FillRemaining)
|
||||
}
|
||||
|
||||
if ($fillRemaining) {
|
||||
$fillRemainingDataPartition = $dataPartition
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($null -ne $fillRemainingDataPartition) {
|
||||
$driveLetter = ([string]$fillRemainingDataPartition.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
if ([string]::IsNullOrWhiteSpace($driveLetter)) {
|
||||
throw "FillRemaining data partition '$($fillRemainingDataPartition.Name)' does not have a drive letter for FFU optimization."
|
||||
}
|
||||
|
||||
$dataPartition = $Disk | Get-Partition | Where-Object { ([string]$_.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant() -eq $driveLetter } | Select-Object -First 1
|
||||
if ($null -eq $dataPartition) {
|
||||
throw "Unable to resolve FillRemaining data partition '$($fillRemainingDataPartition.Name)' at drive ${driveLetter}: for FFU optimization."
|
||||
}
|
||||
|
||||
WriteLog "Using FillRemaining data partition '$($fillRemainingDataPartition.Name)' (partition number $($dataPartition.PartitionNumber)) for FFU optimization."
|
||||
return [int]$dataPartition.PartitionNumber
|
||||
}
|
||||
|
||||
$windowsPartition = Get-WindowsPartitionFromDisk -Disk $Disk -DriveLetter $WindowsPartitionDriveLetter
|
||||
if ($null -eq $windowsPartition) {
|
||||
throw 'Unable to resolve Windows partition for FFU optimization.'
|
||||
}
|
||||
|
||||
WriteLog "Using Windows partition (partition number $($windowsPartition.PartitionNumber)) for FFU optimization."
|
||||
return [int]$windowsPartition.PartitionNumber
|
||||
}
|
||||
#Add boot files
|
||||
function Add-BootFiles {
|
||||
param(
|
||||
@@ -4400,6 +4459,10 @@ function New-FFUFileName {
|
||||
function New-FFU {
|
||||
$captureContext = Get-CaptureVhdContext -VhdxPath $VHDXPath
|
||||
$captureDisk = $captureContext.Disk
|
||||
$resolvedFFUOptimizePartitionNumber = 0
|
||||
if ($Optimize -eq $true) {
|
||||
$resolvedFFUOptimizePartitionNumber = Get-FFUOptimizePartitionNumber -Disk $captureDisk -RequestedPartitionNumber $OptimizeFFUPartitionNumber -WindowsPartitionDriveLetter $WindowsPartitionDriveLetter -AdditionalDataPartitions $normalizedAdditionalDataPartitions
|
||||
}
|
||||
$ffuCaptureNamingInfo = Get-FFUCaptureNamingInfo -ShortenedWindowsSKU $shortenedWindowsSKU -WindowsRelease $WindowsRelease -WindowsVersion $WindowsVersion -InstallationType $installationType -IsWindows10LtscClient:$isWindows10LtscClient
|
||||
|
||||
try {
|
||||
@@ -4461,9 +4524,9 @@ function New-FFU {
|
||||
Set-Progress -Percentage 85 -Message "Optimizing FFU..."
|
||||
WriteLog 'Optimizing FFU - This will take a few minutes, please be patient'
|
||||
#Need to use ADK version of DISM to address bug in DISM - perhaps Windows 11 24H2 will fix this
|
||||
$optimizePartitionArgument = if ($OptimizeFFUPartitionNumber -gt 0) { " /PartitionNumber:$OptimizeFFUPartitionNumber" } else { '' }
|
||||
$optimizePartitionArgument = if ($resolvedFFUOptimizePartitionNumber -gt 0) { " /PartitionNumber:$resolvedFFUOptimizePartitionNumber" } else { '' }
|
||||
if (-not [string]::IsNullOrWhiteSpace($optimizePartitionArgument)) {
|
||||
WriteLog "Optimizing FFU with DISM partition number $OptimizeFFUPartitionNumber."
|
||||
WriteLog "Optimizing FFU with DISM partition number $resolvedFFUOptimizePartitionNumber."
|
||||
}
|
||||
Invoke-Process cmd "/c ""$DandIEnv"" && dism /optimize-ffu /imagefile:$FFUFile$optimizePartitionArgument" | Out-Null
|
||||
#Invoke-Process cmd "/c dism /optimize-ffu /imagefile:$FFUFile" | Out-Null
|
||||
@@ -6177,16 +6240,25 @@ Set-Progress -Percentage 2 -Message "Validating parameters..."
|
||||
try {
|
||||
$normalizedAdditionalDataPartitions = ConvertTo-NormalizedDataPartitions -DataPartitions $AdditionalDataPartitions
|
||||
if ($normalizedAdditionalDataPartitions.Count -gt 0 -and $OSPartitionSize -le 0) {
|
||||
throw 'OSPartitionSize must be set when AdditionalDataPartitions are configured so space remains for Recovery and data partitions.'
|
||||
$osPartitionSizeMessage = if ($CreateRecoveryPartition) {
|
||||
'OSPartitionSize must be set when AdditionalDataPartitions are configured so space remains for Recovery and data partitions.'
|
||||
}
|
||||
else {
|
||||
'OSPartitionSize must be set when AdditionalDataPartitions are configured so space remains for data partitions.'
|
||||
}
|
||||
throw $osPartitionSizeMessage
|
||||
}
|
||||
$partitionLayoutSignature = Get-PartitionLayoutSignature -OSPartitionSize $OSPartitionSize -RecoveryPartitionSize $RecoveryPartitionSize -DataPartitions $normalizedAdditionalDataPartitions
|
||||
$partitionLayoutSignature = Get-PartitionLayoutSignature -OSPartitionSize $OSPartitionSize -RecoveryPartitionSize $RecoveryPartitionSize -CreateRecoveryPartition $CreateRecoveryPartition -DataPartitions $normalizedAdditionalDataPartitions
|
||||
|
||||
$partitionDriveLetters = Get-NormalizedPartitionDriveLetters -SystemPartitionDriveLetter $SystemPartitionDriveLetter -WindowsPartitionDriveLetter $WindowsPartitionDriveLetter -RecoveryPartitionDriveLetter $RecoveryPartitionDriveLetter -AdditionalDataPartitions $normalizedAdditionalDataPartitions -ValidateAvailable
|
||||
$partitionDriveLetters = Get-NormalizedPartitionDriveLetters -SystemPartitionDriveLetter $SystemPartitionDriveLetter -WindowsPartitionDriveLetter $WindowsPartitionDriveLetter -RecoveryPartitionDriveLetter $RecoveryPartitionDriveLetter -CreateRecoveryPartition $CreateRecoveryPartition -AdditionalDataPartitions $normalizedAdditionalDataPartitions -ValidateAvailable
|
||||
$SystemPartitionDriveLetter = $partitionDriveLetters.SystemPartitionDriveLetter
|
||||
$WindowsPartitionDriveLetter = $partitionDriveLetters.WindowsPartitionDriveLetter
|
||||
$RecoveryPartitionDriveLetter = $partitionDriveLetters.RecoveryPartitionDriveLetter
|
||||
if ($CreateRecoveryPartition) {
|
||||
$RecoveryPartitionDriveLetter = $partitionDriveLetters.RecoveryPartitionDriveLetter
|
||||
}
|
||||
$dataPartitionDriveLetterLog = if ($normalizedAdditionalDataPartitions.Count -gt 0) { ', Data=' + (($normalizedAdditionalDataPartitions | ForEach-Object { "$($_.Name):$($_.DriveLetter)" }) -join ', ') } else { '' }
|
||||
WriteLog "Using build partition drive letters: System=$SystemPartitionDriveLetter, Windows=$WindowsPartitionDriveLetter, Recovery=$RecoveryPartitionDriveLetter$dataPartitionDriveLetterLog"
|
||||
$recoveryPartitionDriveLetterLog = if ($CreateRecoveryPartition) { ", Recovery=$RecoveryPartitionDriveLetter" } else { ', Recovery=disabled' }
|
||||
WriteLog "Using build partition drive letters: System=$SystemPartitionDriveLetter, Windows=$WindowsPartitionDriveLetter$recoveryPartitionDriveLetterLog$dataPartitionDriveLetterLog"
|
||||
}
|
||||
catch {
|
||||
$partitionDriveLetterValidationError = "Build validation failed: $($_.Exception.Message)"
|
||||
@@ -7486,15 +7558,19 @@ try {
|
||||
[uint64]$cachedDisksize = 0
|
||||
if (-not [uint64]::TryParse([string]$vhdxCacheItem.Disksize, [ref]$cachedDisksize)) { WriteLog "Disksize invalid in cached config ($($vhdxCacheItem.Disksize)), continuing"; continue }
|
||||
if ($cachedDisksize -ne $Disksize) { WriteLog "Disksize mismatch (cached: $cachedDisksize, current: $Disksize), continuing"; continue }
|
||||
if ($vhdxCacheItem.PSObject.Properties.Name -notcontains 'CreateRecoveryPartition') { WriteLog 'CreateRecoveryPartition missing in cached config, continuing'; continue }
|
||||
if ([bool]$vhdxCacheItem.CreateRecoveryPartition -ne $CreateRecoveryPartition) { WriteLog "CreateRecoveryPartition mismatch (cached: $($vhdxCacheItem.CreateRecoveryPartition), current: $CreateRecoveryPartition), continuing"; continue }
|
||||
if ($vhdxCacheItem.PSObject.Properties.Name -notcontains 'SystemPartitionDriveLetter') { WriteLog 'SystemPartitionDriveLetter missing in cached config, continuing'; continue }
|
||||
if ($vhdxCacheItem.PSObject.Properties.Name -notcontains 'WindowsPartitionDriveLetter') { WriteLog 'WindowsPartitionDriveLetter missing in cached config, continuing'; continue }
|
||||
if ($vhdxCacheItem.PSObject.Properties.Name -notcontains 'RecoveryPartitionDriveLetter') { WriteLog 'RecoveryPartitionDriveLetter missing in cached config, continuing'; continue }
|
||||
if ($CreateRecoveryPartition -and $vhdxCacheItem.PSObject.Properties.Name -notcontains 'RecoveryPartitionDriveLetter') { WriteLog 'RecoveryPartitionDriveLetter missing in cached config, continuing'; continue }
|
||||
$cachedSystemPartitionDriveLetter = Get-PartitionDriveLetterCacheValue -DriveLetterValue $vhdxCacheItem.SystemPartitionDriveLetter
|
||||
$cachedWindowsPartitionDriveLetter = Get-PartitionDriveLetterCacheValue -DriveLetterValue $vhdxCacheItem.WindowsPartitionDriveLetter
|
||||
$cachedRecoveryPartitionDriveLetter = Get-PartitionDriveLetterCacheValue -DriveLetterValue $vhdxCacheItem.RecoveryPartitionDriveLetter
|
||||
if ($cachedSystemPartitionDriveLetter -ne $SystemPartitionDriveLetter) { WriteLog "SystemPartitionDriveLetter mismatch (cached: $($vhdxCacheItem.SystemPartitionDriveLetter), current: $SystemPartitionDriveLetter), continuing"; continue }
|
||||
if ($cachedWindowsPartitionDriveLetter -ne $WindowsPartitionDriveLetter) { WriteLog "WindowsPartitionDriveLetter mismatch (cached: $($vhdxCacheItem.WindowsPartitionDriveLetter), current: $WindowsPartitionDriveLetter), continuing"; continue }
|
||||
if ($cachedRecoveryPartitionDriveLetter -ne $RecoveryPartitionDriveLetter) { WriteLog "RecoveryPartitionDriveLetter mismatch (cached: $($vhdxCacheItem.RecoveryPartitionDriveLetter), current: $RecoveryPartitionDriveLetter), continuing"; continue }
|
||||
if ($CreateRecoveryPartition) {
|
||||
$cachedRecoveryPartitionDriveLetter = Get-PartitionDriveLetterCacheValue -DriveLetterValue $vhdxCacheItem.RecoveryPartitionDriveLetter
|
||||
if ($cachedRecoveryPartitionDriveLetter -ne $RecoveryPartitionDriveLetter) { WriteLog "RecoveryPartitionDriveLetter mismatch (cached: $($vhdxCacheItem.RecoveryPartitionDriveLetter), current: $RecoveryPartitionDriveLetter), continuing"; continue }
|
||||
}
|
||||
if ($vhdxCacheItem.PSObject.Properties.Name -notcontains 'PartitionLayoutSignature') { WriteLog 'PartitionLayoutSignature missing in cached config, continuing'; continue }
|
||||
if ($vhdxCacheItem.PartitionLayoutSignature -ne $partitionLayoutSignature) { WriteLog 'PartitionLayoutSignature mismatch, continuing'; continue }
|
||||
|
||||
@@ -7823,7 +7899,12 @@ try {
|
||||
$osPartitionDriveLetter = $osPartition[1].DriveLetter
|
||||
$WindowsPartition = $osPartitionDriveLetter + ':\'
|
||||
|
||||
$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DriveLetter $RecoveryPartitionDriveLetter -OsPartitionUsesMaximumSize ($OSPartitionSize -le 0)
|
||||
if ($CreateRecoveryPartition) {
|
||||
$recoveryPartition = New-RecoveryPartition -VhdxDisk $vhdxDisk -OsPartition $osPartition[1] -RecoveryPartitionSize $RecoveryPartitionSize -DriveLetter $RecoveryPartitionDriveLetter -OsPartitionUsesMaximumSize ($OSPartitionSize -le 0)
|
||||
}
|
||||
else {
|
||||
WriteLog 'CreateRecoveryPartition is false. Skipping Windows Recovery partition creation.'
|
||||
}
|
||||
|
||||
foreach ($additionalDataPartition in $normalizedAdditionalDataPartitions) {
|
||||
New-DataPartition -VhdxDisk $vhdxDisk -DataPartition $additionalDataPartition | Out-Null
|
||||
@@ -8002,6 +8083,7 @@ try {
|
||||
$cachedVHDXInfo.VhdxFileName = $("$VMName.vhdx")
|
||||
$cachedVHDXInfo.LogicalSectorSizeBytes = $LogicalSectorSizeBytes
|
||||
$cachedVHDXInfo.Disksize = $Disksize
|
||||
$cachedVHDXInfo.CreateRecoveryPartition = $CreateRecoveryPartition
|
||||
$cachedVHDXInfo.SystemPartitionDriveLetter = [string]$SystemPartitionDriveLetter
|
||||
$cachedVHDXInfo.WindowsPartitionDriveLetter = [string]$WindowsPartitionDriveLetter
|
||||
$cachedVHDXInfo.RecoveryPartitionDriveLetter = [string]$RecoveryPartitionDriveLetter
|
||||
|
||||
@@ -48,7 +48,8 @@ $script:uiState = [PSCustomObject]@{
|
||||
pollTimer = $null;
|
||||
currentBuildProcess = $null;
|
||||
lastConfigFilePath = $null;
|
||||
loadedDeviceNamingMode = $null
|
||||
loadedDeviceNamingMode = $null;
|
||||
createRecoveryPartition = $true
|
||||
};
|
||||
Flags = @{
|
||||
installAppsForcedByUpdates = $false;
|
||||
@@ -426,6 +427,12 @@ $script:uiState.Controls.btnRun.Add_Click({
|
||||
# Gather config on the UI thread before starting the job
|
||||
$config = Get-UIConfig -State $script:uiState
|
||||
|
||||
if (-not (Test-DiskLayoutConfiguration -State $script:uiState -Config $config)) {
|
||||
$btnRun.IsEnabled = $true
|
||||
$script:uiState.Controls.txtStatus.Text = "Build canceled: disk layout validation failed."
|
||||
return
|
||||
}
|
||||
|
||||
# Validate Additional FFU selection if enabled
|
||||
if ($config.BuildUSBDrive -and $config.CopyAdditionalFFUFiles -and (($null -eq $config.AdditionalFFUFiles) -or ($config.AdditionalFFUFiles.Count -eq 0))) {
|
||||
[System.Windows.MessageBox]::Show("Please select at least one additional FFU file to copy, or uncheck 'Copy Additional FFU Files'.", "Selection Required", "OK", "Warning") | Out-Null
|
||||
|
||||
@@ -324,9 +324,6 @@
|
||||
<!-- Custom VM Switch Name -->
|
||||
<TextBox x:Name="txtCustomVMSwitchName" HorizontalAlignment="Stretch" Visibility="Collapsed" Margin="0,0,0,0" ToolTip="Enter your custom VM Switch Name if 'Other' is selected."/>
|
||||
</StackPanel>
|
||||
<!-- Disk Size (GB) -->
|
||||
<TextBlock Text="Disk Size (GB)" Margin="0,0,0,8" ToolTip="Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk."/>
|
||||
<TextBox x:Name="txtDiskSize" HorizontalAlignment="Stretch" Text="50" Margin="0,0,0,20" ToolTip="Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk."/>
|
||||
<!-- Memory (GB) -->
|
||||
<TextBlock Text="Memory (GB)" Margin="0,0,0,8" ToolTip="Amount of memory to allocate for the virtual machine. Recommended to use 8GB if possible, especially for Windows 11. Default is 4GB."/>
|
||||
<TextBox x:Name="txtMemory" HorizontalAlignment="Stretch" Text="4" Margin="0,0,0,20" ToolTip="Amount of memory to allocate for the virtual machine. Recommended to use 8GB if possible, especially for Windows 11. Default is 4GB."/>
|
||||
@@ -339,110 +336,35 @@
|
||||
<!-- VM Name Prefix -->
|
||||
<TextBlock Text="VM Name Prefix" Margin="0,0,0,8" ToolTip="Prefix for the VM Name. The default is _FFU."/>
|
||||
<TextBox x:Name="txtVMNamePrefix" HorizontalAlignment="Stretch" Margin="0,0,0,20" ToolTip="Prefix for the VM Name. The default is _FFU."/>
|
||||
<!-- System Partition Drive Letter -->
|
||||
<TextBlock Text="System Partition Drive Letter" Margin="0,0,0,8" ToolTip="Drive letter used for the System partition while building the FFU VHDX. Default is S."/>
|
||||
<ComboBox x:Name="cmbSystemPartitionDriveLetter" HorizontalAlignment="Left" Margin="0,0,0,20" ToolTip="Drive letter used for the System partition while building the FFU VHDX. Default is S.">
|
||||
<ComboBoxItem Content="A"/>
|
||||
<ComboBoxItem Content="B"/>
|
||||
<ComboBoxItem Content="C"/>
|
||||
<ComboBoxItem Content="D"/>
|
||||
<ComboBoxItem Content="E"/>
|
||||
<ComboBoxItem Content="F"/>
|
||||
<ComboBoxItem Content="G"/>
|
||||
<ComboBoxItem Content="H"/>
|
||||
<ComboBoxItem Content="I"/>
|
||||
<ComboBoxItem Content="J"/>
|
||||
<ComboBoxItem Content="K"/>
|
||||
<ComboBoxItem Content="L"/>
|
||||
<ComboBoxItem Content="M"/>
|
||||
<ComboBoxItem Content="N"/>
|
||||
<ComboBoxItem Content="O"/>
|
||||
<ComboBoxItem Content="P"/>
|
||||
<ComboBoxItem Content="Q"/>
|
||||
<ComboBoxItem Content="R"/>
|
||||
<ComboBoxItem Content="S" IsSelected="True"/>
|
||||
<ComboBoxItem Content="T"/>
|
||||
<ComboBoxItem Content="U"/>
|
||||
<ComboBoxItem Content="V"/>
|
||||
<ComboBoxItem Content="W"/>
|
||||
<ComboBoxItem Content="X"/>
|
||||
<ComboBoxItem Content="Y"/>
|
||||
<ComboBoxItem Content="Z"/>
|
||||
</ComboBox>
|
||||
<!-- Windows Partition Drive Letter -->
|
||||
<TextBlock Text="Windows Partition Drive Letter" Margin="0,0,0,8" ToolTip="Drive letter used for the Windows partition while building the FFU VHDX. Default is W."/>
|
||||
<ComboBox x:Name="cmbWindowsPartitionDriveLetter" HorizontalAlignment="Left" Margin="0,0,0,20" ToolTip="Drive letter used for the Windows partition while building the FFU VHDX. Default is W.">
|
||||
<ComboBoxItem Content="A"/>
|
||||
<ComboBoxItem Content="B"/>
|
||||
<ComboBoxItem Content="C"/>
|
||||
<ComboBoxItem Content="D"/>
|
||||
<ComboBoxItem Content="E"/>
|
||||
<ComboBoxItem Content="F"/>
|
||||
<ComboBoxItem Content="G"/>
|
||||
<ComboBoxItem Content="H"/>
|
||||
<ComboBoxItem Content="I"/>
|
||||
<ComboBoxItem Content="J"/>
|
||||
<ComboBoxItem Content="K"/>
|
||||
<ComboBoxItem Content="L"/>
|
||||
<ComboBoxItem Content="M"/>
|
||||
<ComboBoxItem Content="N"/>
|
||||
<ComboBoxItem Content="O"/>
|
||||
<ComboBoxItem Content="P"/>
|
||||
<ComboBoxItem Content="Q"/>
|
||||
<ComboBoxItem Content="R"/>
|
||||
<ComboBoxItem Content="S"/>
|
||||
<ComboBoxItem Content="T"/>
|
||||
<ComboBoxItem Content="U"/>
|
||||
<ComboBoxItem Content="V"/>
|
||||
<ComboBoxItem Content="W" IsSelected="True"/>
|
||||
<ComboBoxItem Content="X"/>
|
||||
<ComboBoxItem Content="Y"/>
|
||||
<ComboBoxItem Content="Z"/>
|
||||
</ComboBox>
|
||||
<!-- Recovery Partition Drive Letter -->
|
||||
<TextBlock Text="Recovery Partition Drive Letter" Margin="0,0,0,8" ToolTip="Drive letter used for the Recovery partition while building the FFU VHDX. Default is R."/>
|
||||
<ComboBox x:Name="cmbRecoveryPartitionDriveLetter" HorizontalAlignment="Left" Margin="0,0,0,20" ToolTip="Drive letter used for the Recovery partition while building the FFU VHDX. Default is R.">
|
||||
<ComboBoxItem Content="A"/>
|
||||
<ComboBoxItem Content="B"/>
|
||||
<ComboBoxItem Content="C"/>
|
||||
<ComboBoxItem Content="D"/>
|
||||
<ComboBoxItem Content="E"/>
|
||||
<ComboBoxItem Content="F"/>
|
||||
<ComboBoxItem Content="G"/>
|
||||
<ComboBoxItem Content="H"/>
|
||||
<ComboBoxItem Content="I"/>
|
||||
<ComboBoxItem Content="J"/>
|
||||
<ComboBoxItem Content="K"/>
|
||||
<ComboBoxItem Content="L"/>
|
||||
<ComboBoxItem Content="M"/>
|
||||
<ComboBoxItem Content="N"/>
|
||||
<ComboBoxItem Content="O"/>
|
||||
<ComboBoxItem Content="P"/>
|
||||
<ComboBoxItem Content="Q"/>
|
||||
<ComboBoxItem Content="R" IsSelected="True"/>
|
||||
<ComboBoxItem Content="S"/>
|
||||
<ComboBoxItem Content="T"/>
|
||||
<ComboBoxItem Content="U"/>
|
||||
<ComboBoxItem Content="V"/>
|
||||
<ComboBoxItem Content="W"/>
|
||||
<ComboBoxItem Content="X"/>
|
||||
<ComboBoxItem Content="Y"/>
|
||||
<ComboBoxItem Content="Z"/>
|
||||
</ComboBox>
|
||||
<!-- Additional Data Partitions -->
|
||||
<Expander Header="Additional Data Partitions" Margin="0,0,0,20" IsExpanded="False" ToolTip="Create optional data partitions after the Windows Recovery partition. Set Windows Partition Size when adding data partitions so free space remains on the VHDX.">
|
||||
<!-- Disk Layout -->
|
||||
<Expander Header="Disk Layout" Margin="0,0,0,20" IsExpanded="True">
|
||||
<StackPanel Margin="0,10,0,0">
|
||||
<TextBlock Text="Windows Partition Size (GB)" Margin="0,0,0,8" ToolTip="Required when additional data partitions are configured. Leave blank to use the default Windows partition sizing behavior."/>
|
||||
<TextBox x:Name="txtOSPartitionSizeGB" Width="120" HorizontalAlignment="Left" Margin="0,0,0,12" ToolTip="Size of the Windows partition in GB when additional data partitions are configured."/>
|
||||
<TextBlock Text="FFU Builder defaults to using a 50GB VHDX. Below you can modify the disk size, add additional data/primary partitions, change the drive letters, and set the partition sizes for the Windows, Recovery, and data partitions. The total of the partition sizes must be less than or equal to the Disk Size. The Recovery partition will auto-size based on the size of WinRE.wim from the Windows media with additional buffer as per Microsoft recommendations. You can adjust the size of the recovery partition if needed (not recommended).

You can only reorder the additional data partitions." TextWrapping="Wrap" Margin="0,0,0,16"/>
|
||||
<TextBlock Text="Disk Size (GB)" Margin="0,0,0,8" ToolTip="Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk."/>
|
||||
<TextBox x:Name="txtDiskSize" Width="160" HorizontalAlignment="Left" Text="50" Margin="0,0,0,16" ToolTip="Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk."/>
|
||||
|
||||
<TextBlock Text="Recovery Partition Size (GB)" Margin="0,0,0,8" ToolTip="Optional fixed Recovery partition size in GB. Leave blank to use the default calculated Recovery partition size."/>
|
||||
<TextBox x:Name="txtRecoveryPartitionSizeGB" Width="120" HorizontalAlignment="Left" Margin="0,0,0,16" ToolTip="Optional size of the Recovery partition in GB."/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,16">
|
||||
<Ellipse x:Name="ellipseDiskLayoutCapacityStatus" Width="14" Height="14" Fill="Gold" Margin="0,3,10,0" VerticalAlignment="Top"/>
|
||||
<StackPanel>
|
||||
<TextBlock Text="Partition capacity" FontWeight="SemiBold"/>
|
||||
<TextBlock x:Name="txtDiskLayoutCapacityStatusValue" Text="Checking disk layout capacity..." TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Visibility="Collapsed">
|
||||
<ComboBox x:Name="cmbSystemPartitionDriveLetter" SelectedIndex="18"><ComboBoxItem Content="A"/><ComboBoxItem Content="B"/><ComboBoxItem Content="C"/><ComboBoxItem Content="D"/><ComboBoxItem Content="E"/><ComboBoxItem Content="F"/><ComboBoxItem Content="G"/><ComboBoxItem Content="H"/><ComboBoxItem Content="I"/><ComboBoxItem Content="J"/><ComboBoxItem Content="K"/><ComboBoxItem Content="L"/><ComboBoxItem Content="M"/><ComboBoxItem Content="N"/><ComboBoxItem Content="O"/><ComboBoxItem Content="P"/><ComboBoxItem Content="Q"/><ComboBoxItem Content="R"/><ComboBoxItem Content="S"/><ComboBoxItem Content="T"/><ComboBoxItem Content="U"/><ComboBoxItem Content="V"/><ComboBoxItem Content="W"/><ComboBoxItem Content="X"/><ComboBoxItem Content="Y"/><ComboBoxItem Content="Z"/></ComboBox>
|
||||
<ComboBox x:Name="cmbWindowsPartitionDriveLetter" SelectedIndex="22"><ComboBoxItem Content="A"/><ComboBoxItem Content="B"/><ComboBoxItem Content="C"/><ComboBoxItem Content="D"/><ComboBoxItem Content="E"/><ComboBoxItem Content="F"/><ComboBoxItem Content="G"/><ComboBoxItem Content="H"/><ComboBoxItem Content="I"/><ComboBoxItem Content="J"/><ComboBoxItem Content="K"/><ComboBoxItem Content="L"/><ComboBoxItem Content="M"/><ComboBoxItem Content="N"/><ComboBoxItem Content="O"/><ComboBoxItem Content="P"/><ComboBoxItem Content="Q"/><ComboBoxItem Content="R"/><ComboBoxItem Content="S"/><ComboBoxItem Content="T"/><ComboBoxItem Content="U"/><ComboBoxItem Content="V"/><ComboBoxItem Content="W"/><ComboBoxItem Content="X"/><ComboBoxItem Content="Y"/><ComboBoxItem Content="Z"/></ComboBox>
|
||||
<ComboBox x:Name="cmbRecoveryPartitionDriveLetter" SelectedIndex="17"><ComboBoxItem Content="A"/><ComboBoxItem Content="B"/><ComboBoxItem Content="C"/><ComboBoxItem Content="D"/><ComboBoxItem Content="E"/><ComboBoxItem Content="F"/><ComboBoxItem Content="G"/><ComboBoxItem Content="H"/><ComboBoxItem Content="I"/><ComboBoxItem Content="J"/><ComboBoxItem Content="K"/><ComboBoxItem Content="L"/><ComboBoxItem Content="M"/><ComboBoxItem Content="N"/><ComboBoxItem Content="O"/><ComboBoxItem Content="P"/><ComboBoxItem Content="Q"/><ComboBoxItem Content="R"/><ComboBoxItem Content="S"/><ComboBoxItem Content="T"/><ComboBoxItem Content="U"/><ComboBoxItem Content="V"/><ComboBoxItem Content="W"/><ComboBoxItem Content="X"/><ComboBoxItem Content="Y"/><ComboBoxItem Content="Z"/></ComboBox>
|
||||
<TextBox x:Name="txtOSPartitionSizeGB"/>
|
||||
<TextBox x:Name="txtRecoveryPartitionSizeGB"/>
|
||||
</StackPanel>
|
||||
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="180"/>
|
||||
<ColumnDefinition Width="100"/>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="140"/>
|
||||
<ColumnDefinition Width="130"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -453,6 +375,7 @@
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="Drive Letter" Margin="0,0,8,8"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="2" Text="Size (GB)" Margin="0,0,8,8"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="3" Text="Fill Remaining" Margin="0,0,0,8"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="4" Text=" " Margin="0,0,0,8"/>
|
||||
<TextBox x:Name="txtDataPartitionName" Grid.Row="1" Grid.Column="0" Margin="0,0,8,0" ToolTip="Name and volume label for the data partition."/>
|
||||
<ComboBox x:Name="cmbDataPartitionDriveLetter" Grid.Row="1" Grid.Column="1" Margin="0,0,8,0" ToolTip="Build-time drive letter for the data partition.">
|
||||
<ComboBoxItem Content="D" IsSelected="True"/>
|
||||
@@ -478,24 +401,28 @@
|
||||
</ComboBox>
|
||||
<TextBox x:Name="txtDataPartitionSizeGB" Grid.Row="1" Grid.Column="2" Margin="0,0,8,0" ToolTip="Data partition size in GB. Leave blank only when Fill Remaining is selected."/>
|
||||
<CheckBox x:Name="chkDataPartitionFillRemaining" Grid.Row="1" Grid.Column="3" VerticalAlignment="Center" ToolTip="Use the remaining VHDX space for this data partition. Only one data partition can fill remaining space."/>
|
||||
<Button x:Name="btnAddDataPartition" Grid.Row="1" Grid.Column="4" Content="Add Data Partition" Width="160" ToolTip="Add the configured data partition to the build configuration."/>
|
||||
</Grid>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,12">
|
||||
<Button x:Name="btnAddDataPartition" Content="Add Data Partition" Width="160" Margin="0,0,8,0" ToolTip="Add the configured data partition to the build configuration."/>
|
||||
<Button x:Name="btnRemoveSelectedDataPartitions" Content="Remove Selected" Width="140" Margin="0,0,8,0" ToolTip="Remove selected data partitions from the list."/>
|
||||
<Button x:Name="btnClearDataPartitions" Content="Clear" Width="80" ToolTip="Clear all additional data partitions."/>
|
||||
</StackPanel>
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ListView x:Name="lstDataPartitions" Grid.Column="0" Height="220" Margin="0" SelectionMode="Single" BorderThickness="1" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"/>
|
||||
<StackPanel Grid.Column="1" Margin="12,0,0,0" VerticalAlignment="Top">
|
||||
<Button x:Name="btnMoveDataPartitionTop" Content="⤒" Width="40" Height="40" FontSize="28" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="0" Margin="0,0,0,8" ToolTip="Move selected data partition to the top of the data partition group" />
|
||||
<Button x:Name="btnMoveDataPartitionUp" Content="↑" Width="40" Height="40" FontSize="28" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="0" Margin="0,0,0,8" ToolTip="Move selected data partition up" />
|
||||
<Button x:Name="btnMoveDataPartitionDown" Content="↓" Width="40" Height="40" FontSize="28" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="0" Margin="0,0,0,8" ToolTip="Move selected data partition down" />
|
||||
<Button x:Name="btnMoveDataPartitionBottom" Content="⤓" Width="40" Height="40" FontSize="28" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="0" ToolTip="Move selected data partition to the bottom of the data partition group" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<ListView x:Name="lstDataPartitions" Height="150" Margin="0" SelectionMode="Extended" BorderThickness="1" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" Width="180"/>
|
||||
<GridViewColumn Header="Drive Letter" DisplayMemberBinding="{Binding DriveLetter}" Width="100"/>
|
||||
<GridViewColumn Header="Size (GB)" DisplayMemberBinding="{Binding SizeGB}" Width="100"/>
|
||||
<GridViewColumn Header="Fill Remaining" DisplayMemberBinding="{Binding FillRemaining}" Width="120"/>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
|
||||
<Button x:Name="btnRemoveSelectedDataPartitions" Content="Remove Selected" Width="140" Margin="0,0,8,0" ToolTip="Remove checked data partitions or the Recovery partition from the list."/>
|
||||
<Button x:Name="btnClearDataPartitions" Content="Clear" Width="80" ToolTip="Clear all additional data partitions."/>
|
||||
<Button x:Name="btnRestoreRecoveryPartition" Content="Restore Recovery" Width="140" Margin="8,0,0,0" Visibility="Collapsed" ToolTip="Restore the Windows Recovery partition after removing it."/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
<!-- Logical Sector Size -->
|
||||
|
||||
@@ -9,6 +9,8 @@ function Get-UIConfig {
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
Sync-DiskLayoutRowsToControls -State $State
|
||||
|
||||
# Create hash to store configuration
|
||||
$config = [ordered]@{
|
||||
AllowExternalHardDiskMedia = $State.Controls.chkAllowExternalHardDiskMedia.IsChecked
|
||||
@@ -51,6 +53,7 @@ function Get-UIConfig {
|
||||
Disksize = [int64]$State.Controls.txtDiskSize.Text * 1GB
|
||||
OSPartitionSize = ConvertTo-PartitionSizeBytesFromGBText -Text $State.Controls.txtOSPartitionSizeGB.Text -FieldName 'Windows Partition Size'
|
||||
RecoveryPartitionSize = ConvertTo-PartitionSizeBytesFromGBText -Text $State.Controls.txtRecoveryPartitionSizeGB.Text -FieldName 'Recovery Partition Size'
|
||||
CreateRecoveryPartition = [bool]$State.Data.createRecoveryPartition
|
||||
AdditionalDataPartitions = @(Get-AdditionalDataPartitionConfigRows -State $State)
|
||||
DownloadDrivers = $State.Controls.chkDownloadDrivers.IsChecked
|
||||
DriversFolder = $State.Controls.txtDriversFolder.Text
|
||||
@@ -337,6 +340,174 @@ function Get-PartitionSizeGBDisplay {
|
||||
return $sizeGb.ToString('0.##', [System.Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
|
||||
function Get-PartitionDriveLetterOptions {
|
||||
return @('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z')
|
||||
}
|
||||
|
||||
function New-DiskLayoutPartitionRow {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PartitionType,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name,
|
||||
[string]$Label,
|
||||
[string]$DriveLetter,
|
||||
[string]$SizeGB,
|
||||
[int64]$SizeBytes = 0,
|
||||
[bool]$FillRemaining,
|
||||
[string]$FileSystem = 'NTFS',
|
||||
[bool]$CanSelect,
|
||||
[bool]$CanEditDriveLetter,
|
||||
[bool]$CanEditSize,
|
||||
[bool]$CanEditFillRemaining,
|
||||
[string]$FillRemainingVisibility = 'Visible',
|
||||
[bool]$CanRemove,
|
||||
[bool]$CanReorder
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Label)) {
|
||||
$Label = $Name
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{
|
||||
IsSelected = $false
|
||||
CanSelect = $CanSelect
|
||||
PartitionType = $PartitionType
|
||||
Name = $Name
|
||||
Label = $Label
|
||||
DriveLetter = $DriveLetter
|
||||
DriveLetterOptions = @(Get-PartitionDriveLetterOptions)
|
||||
SizeGB = $SizeGB
|
||||
SizeBytes = $SizeBytes
|
||||
FillRemaining = $FillRemaining
|
||||
FillRemainingVisibility = $FillRemainingVisibility
|
||||
FileSystem = $FileSystem
|
||||
CanEditDriveLetter = $CanEditDriveLetter
|
||||
CanEditSize = $CanEditSize
|
||||
CanEditFillRemaining = $CanEditFillRemaining
|
||||
CanRemove = $CanRemove
|
||||
CanReorder = $CanReorder
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ComboBoxDriveLetterValue {
|
||||
param(
|
||||
[object]$ComboBox,
|
||||
[string]$DefaultValue
|
||||
)
|
||||
|
||||
$driveLetter = [string](Get-ComboBoxSelectedContent -ComboBox $ComboBox)
|
||||
if ([string]::IsNullOrWhiteSpace($driveLetter)) {
|
||||
$driveLetter = $DefaultValue
|
||||
}
|
||||
|
||||
return $driveLetter.Trim().TrimEnd(':').ToUpperInvariant()
|
||||
}
|
||||
|
||||
function Get-DiskLayoutPartitionRows {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
if ($null -eq $State.Data.additionalDataPartitionsDataList) {
|
||||
$State.Data.additionalDataPartitionsDataList = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
}
|
||||
if ($null -eq $State.Data.createRecoveryPartition) {
|
||||
$State.Data.createRecoveryPartition = $true
|
||||
}
|
||||
|
||||
$hasDataPartitions = @($State.Data.additionalDataPartitionsDataList).Count -gt 0
|
||||
$windowsSizeText = [string]$State.Controls.txtOSPartitionSizeGB.Text
|
||||
$windowsCanFillRemaining = -not $hasDataPartitions
|
||||
$windowsFillRemaining = $windowsCanFillRemaining -and [string]::IsNullOrWhiteSpace($windowsSizeText)
|
||||
$windowsFillRemainingVisibility = if ($windowsCanFillRemaining) { 'Visible' } else { 'Hidden' }
|
||||
|
||||
$rows = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
$rows.Add((New-DiskLayoutPartitionRow -PartitionType 'System' -Name 'System' -DriveLetter (Get-ComboBoxDriveLetterValue -ComboBox $State.Controls.cmbSystemPartitionDriveLetter -DefaultValue 'S') -SizeGB '0.26' -SizeBytes 260MB -CanEditDriveLetter $true -CanEditSize $false -CanEditFillRemaining $false -FillRemainingVisibility 'Hidden' -CanRemove $false -CanReorder $false))
|
||||
$rows.Add((New-DiskLayoutPartitionRow -PartitionType 'MSR' -Name 'MSR' -SizeGB '0.016' -SizeBytes 16MB -CanEditDriveLetter $false -CanEditSize $false -CanEditFillRemaining $false -FillRemainingVisibility 'Hidden' -CanRemove $false -CanReorder $false))
|
||||
$rows.Add((New-DiskLayoutPartitionRow -PartitionType 'Windows' -Name 'Windows' -DriveLetter (Get-ComboBoxDriveLetterValue -ComboBox $State.Controls.cmbWindowsPartitionDriveLetter -DefaultValue 'W') -SizeGB $windowsSizeText -SizeBytes (ConvertTo-PartitionSizeBytesFromGBText -Text $windowsSizeText -FieldName 'Windows Partition Size') -FillRemaining $windowsFillRemaining -CanEditDriveLetter $true -CanEditSize $true -CanEditFillRemaining $windowsCanFillRemaining -FillRemainingVisibility $windowsFillRemainingVisibility -CanRemove $false -CanReorder $false))
|
||||
|
||||
if ([bool]$State.Data.createRecoveryPartition) {
|
||||
$rows.Add((New-DiskLayoutPartitionRow -PartitionType 'Recovery' -Name 'Recovery' -DriveLetter (Get-ComboBoxDriveLetterValue -ComboBox $State.Controls.cmbRecoveryPartitionDriveLetter -DefaultValue 'R') -SizeGB ([string]$State.Controls.txtRecoveryPartitionSizeGB.Text) -SizeBytes (ConvertTo-PartitionSizeBytesFromGBText -Text $State.Controls.txtRecoveryPartitionSizeGB.Text -FieldName 'Recovery Partition Size') -CanSelect $true -CanEditDriveLetter $true -CanEditSize $true -CanEditFillRemaining $false -FillRemainingVisibility 'Hidden' -CanRemove $true -CanReorder $false))
|
||||
}
|
||||
|
||||
foreach ($dataPartition in @($State.Data.additionalDataPartitionsDataList)) {
|
||||
$rows.Add((New-DiskLayoutPartitionRow -PartitionType 'Data' -Name $dataPartition.Name -Label $dataPartition.Label -DriveLetter $dataPartition.DriveLetter -SizeGB $dataPartition.SizeGB -SizeBytes ([int64]$dataPartition.SizeBytes) -FillRemaining ([bool]$dataPartition.FillRemaining) -FileSystem $dataPartition.FileSystem -CanSelect $true -CanEditDriveLetter $true -CanEditSize $true -CanEditFillRemaining $true -CanRemove $true -CanReorder $true))
|
||||
}
|
||||
|
||||
return $rows.ToArray()
|
||||
}
|
||||
|
||||
function Sync-DiskLayoutRowsToControls {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
if ($null -eq $State.Controls.lstDataPartitions) { return }
|
||||
if ($null -eq $State.Data.createRecoveryPartition) {
|
||||
$State.Data.createRecoveryPartition = $true
|
||||
}
|
||||
|
||||
$layoutRows = @($State.Controls.lstDataPartitions.ItemsSource)
|
||||
if ($layoutRows.Count -eq 0) { return }
|
||||
|
||||
$State.Data.createRecoveryPartition = [bool](@($layoutRows | Where-Object { $_.PartitionType -eq 'Recovery' }).Count -gt 0)
|
||||
$updatedDataPartitions = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
foreach ($row in $layoutRows) {
|
||||
switch ($row.PartitionType) {
|
||||
'System' {
|
||||
$row.DriveLetter = ([string]$row.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
$State.Controls.cmbSystemPartitionDriveLetter.SelectedItem = ($State.Controls.cmbSystemPartitionDriveLetter.Items | Where-Object { $_.Content -eq $row.DriveLetter } | Select-Object -First 1)
|
||||
}
|
||||
'Windows' {
|
||||
$row.DriveLetter = ([string]$row.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
$State.Controls.cmbWindowsPartitionDriveLetter.SelectedItem = ($State.Controls.cmbWindowsPartitionDriveLetter.Items | Where-Object { $_.Content -eq $row.DriveLetter } | Select-Object -First 1)
|
||||
if ($row.FillRemaining) {
|
||||
$State.Controls.txtOSPartitionSizeGB.Clear()
|
||||
}
|
||||
else {
|
||||
$State.Controls.txtOSPartitionSizeGB.Text = [string]$row.SizeGB
|
||||
}
|
||||
}
|
||||
'Recovery' {
|
||||
$row.DriveLetter = ([string]$row.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
$State.Controls.cmbRecoveryPartitionDriveLetter.SelectedItem = ($State.Controls.cmbRecoveryPartitionDriveLetter.Items | Where-Object { $_.Content -eq $row.DriveLetter } | Select-Object -First 1)
|
||||
$State.Controls.txtRecoveryPartitionSizeGB.Text = [string]$row.SizeGB
|
||||
}
|
||||
'Data' {
|
||||
$row.DriveLetter = ([string]$row.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
if ($row.FillRemaining) {
|
||||
$row.SizeGB = ''
|
||||
$row.SizeBytes = 0
|
||||
}
|
||||
else {
|
||||
$row.SizeBytes = ConvertTo-PartitionSizeBytesFromGBText -Text $row.SizeGB -FieldName "Data Partition '$($row.Name)' Size"
|
||||
}
|
||||
$updatedDataPartitions.Add([PSCustomObject]@{
|
||||
Name = $row.Name
|
||||
Label = $row.Label
|
||||
DriveLetter = $row.DriveLetter
|
||||
SizeGB = $row.SizeGB
|
||||
SizeBytes = [int64]$row.SizeBytes
|
||||
FillRemaining = [bool]$row.FillRemaining
|
||||
FileSystem = $row.FileSystem
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $State.Data.additionalDataPartitionsDataList) {
|
||||
$State.Data.additionalDataPartitionsDataList = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
}
|
||||
$State.Data.additionalDataPartitionsDataList.Clear()
|
||||
foreach ($dataPartition in $updatedDataPartitions) {
|
||||
$State.Data.additionalDataPartitionsDataList.Add($dataPartition)
|
||||
}
|
||||
}
|
||||
|
||||
function Update-AdditionalDataPartitionsListView {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
@@ -348,12 +519,361 @@ function Update-AdditionalDataPartitionsListView {
|
||||
}
|
||||
|
||||
if ($null -ne $State.Controls.lstDataPartitions) {
|
||||
$State.Controls.lstDataPartitions.ItemsSource = $State.Data.additionalDataPartitionsDataList.ToArray()
|
||||
$State.Controls.lstDataPartitions.ItemsSource = @(Get-DiskLayoutPartitionRows -State $State)
|
||||
$State.Controls.lstDataPartitions.Items.Refresh()
|
||||
Request-ListViewColumnAutoResize -ListView $State.Controls.lstDataPartitions
|
||||
if ($null -ne $State.Controls.chkSelectAllDataPartitions) {
|
||||
Update-SelectAllHeaderCheckBoxState -ListView $State.Controls.lstDataPartitions -HeaderCheckBox $State.Controls.chkSelectAllDataPartitions
|
||||
}
|
||||
Update-DiskLayoutActionButtonsState -State $State
|
||||
Update-DiskLayoutCapacityStatus -State $State
|
||||
}
|
||||
}
|
||||
|
||||
function Get-DiskLayoutSizeDisplay {
|
||||
param(
|
||||
[int64]$SizeBytes
|
||||
)
|
||||
|
||||
$sizeGb = [decimal]$SizeBytes / 1GB
|
||||
return $sizeGb.ToString('0.##', [System.Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
|
||||
function Get-DiskLayoutCapacityStatus {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
try {
|
||||
$diskSizeBytes = ConvertTo-PartitionSizeBytesFromGBText -Text $State.Controls.txtDiskSize.Text -FieldName 'Disk Size'
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = $_.Exception.Message }
|
||||
}
|
||||
|
||||
if ($diskSizeBytes -le 0) {
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = 'Disk Size must be greater than 0 GB.' }
|
||||
}
|
||||
|
||||
$partitionRows = @($State.Controls.lstDataPartitions.ItemsSource)
|
||||
if ($partitionRows.Count -eq 0) {
|
||||
$partitionRows = @(Get-DiskLayoutPartitionRows -State $State)
|
||||
}
|
||||
|
||||
$fixedPartitionSizeBytes = [int64]0
|
||||
$fillRemainingPartitionNames = [System.Collections.Generic.List[string]]::new()
|
||||
$hasAutoSizedRecovery = $false
|
||||
$dataPartitionCount = @($partitionRows | Where-Object { $_.PartitionType -eq 'Data' }).Count
|
||||
|
||||
foreach ($partitionRow in $partitionRows) {
|
||||
if ($partitionRow.PartitionType -eq 'Recovery' -and -not [bool]$State.Data.createRecoveryPartition) { continue }
|
||||
|
||||
if ($partitionRow.FillRemaining) {
|
||||
$fillRemainingPartitionNames.Add([string]$partitionRow.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if ($partitionRow.PartitionType -eq 'Windows' -and $dataPartitionCount -gt 0 -and [string]::IsNullOrWhiteSpace([string]$partitionRow.SizeGB)) {
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = 'Set a fixed Windows partition size before adding data partitions.' }
|
||||
}
|
||||
|
||||
if ($partitionRow.PartitionType -eq 'Recovery' -and [string]::IsNullOrWhiteSpace([string]$partitionRow.SizeGB)) {
|
||||
$hasAutoSizedRecovery = $true
|
||||
continue
|
||||
}
|
||||
|
||||
if ($partitionRow.PartitionType -eq 'Data' -and [string]::IsNullOrWhiteSpace([string]$partitionRow.SizeGB)) {
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = "Data partition '$($partitionRow.Name)' must have a size or Fill Remaining selected." }
|
||||
}
|
||||
|
||||
if (-not [bool]$partitionRow.CanEditSize -and [int64]$partitionRow.SizeBytes -gt 0) {
|
||||
$fixedPartitionSizeBytes += [int64]$partitionRow.SizeBytes
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
$partitionSizeBytes = ConvertTo-PartitionSizeBytesFromGBText -Text $partitionRow.SizeGB -FieldName "$($partitionRow.Name) Partition Size"
|
||||
}
|
||||
catch {
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = $_.Exception.Message }
|
||||
}
|
||||
|
||||
if ($partitionSizeBytes -gt 0) {
|
||||
$fixedPartitionSizeBytes += $partitionSizeBytes
|
||||
}
|
||||
}
|
||||
|
||||
if ($fillRemainingPartitionNames.Count -gt 1) {
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = 'Only one partition can fill remaining disk space.' }
|
||||
}
|
||||
|
||||
if ($fixedPartitionSizeBytes -gt $diskSizeBytes) {
|
||||
$overByBytes = $fixedPartitionSizeBytes - $diskSizeBytes
|
||||
return [PSCustomObject]@{ Level = 'Red'; Message = "Partition sizes exceed Disk Size by $(Get-DiskLayoutSizeDisplay -SizeBytes $overByBytes) GB. Fixed total: $(Get-DiskLayoutSizeDisplay -SizeBytes $fixedPartitionSizeBytes) GB of $(Get-DiskLayoutSizeDisplay -SizeBytes $diskSizeBytes) GB." }
|
||||
}
|
||||
|
||||
$remainingBytes = $diskSizeBytes - $fixedPartitionSizeBytes
|
||||
$remainingPurpose = if ($fillRemainingPartitionNames.Count -gt 0 -and $hasAutoSizedRecovery) {
|
||||
"remaining for $($fillRemainingPartitionNames -join ', ') and the auto-sized Recovery partition"
|
||||
}
|
||||
elseif ($fillRemainingPartitionNames.Count -gt 0) {
|
||||
"remaining for $($fillRemainingPartitionNames -join ', ')"
|
||||
}
|
||||
elseif ($hasAutoSizedRecovery) {
|
||||
'remaining for the auto-sized Recovery partition and unallocated space'
|
||||
}
|
||||
else {
|
||||
'left unallocated'
|
||||
}
|
||||
|
||||
return [PSCustomObject]@{ Level = 'Green'; Message = "Partition sizes fit within Disk Size. $(Get-DiskLayoutSizeDisplay -SizeBytes $remainingBytes) GB $remainingPurpose." }
|
||||
}
|
||||
|
||||
function Update-DiskLayoutCapacityStatus {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
if ($null -eq $State.Controls.ellipseDiskLayoutCapacityStatus -or $null -eq $State.Controls.txtDiskLayoutCapacityStatusValue) { return }
|
||||
if ($State.Flags -is [System.Collections.IDictionary] -and $true -eq $State.Flags['updatingDiskLayoutCapacityStatus']) { return }
|
||||
|
||||
try {
|
||||
if ($State.Flags -is [System.Collections.IDictionary]) {
|
||||
$State.Flags['updatingDiskLayoutCapacityStatus'] = $true
|
||||
}
|
||||
|
||||
$capacityStatus = Get-DiskLayoutCapacityStatus -State $State
|
||||
$State.Controls.ellipseDiskLayoutCapacityStatus.Fill = switch ($capacityStatus.Level) {
|
||||
'Green' { [System.Windows.Media.Brushes]::LimeGreen }
|
||||
'Red' { [System.Windows.Media.Brushes]::IndianRed }
|
||||
default { [System.Windows.Media.Brushes]::Gold }
|
||||
}
|
||||
$State.Controls.txtDiskLayoutCapacityStatusValue.Text = $capacityStatus.Message
|
||||
}
|
||||
finally {
|
||||
if ($State.Flags -is [System.Collections.IDictionary]) {
|
||||
$State.Flags['updatingDiskLayoutCapacityStatus'] = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Update-DiskLayoutFillRemainingState {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$PartitionRow
|
||||
)
|
||||
|
||||
if ($PartitionRow.FillRemaining) {
|
||||
$PartitionRow.SizeGB = ''
|
||||
$PartitionRow.SizeBytes = 0
|
||||
if ($PartitionRow.PartitionType -eq 'Windows') {
|
||||
$State.Controls.txtOSPartitionSizeGB.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -ne $State.Controls.lstDataPartitions) {
|
||||
$State.Controls.lstDataPartitions.Items.Refresh()
|
||||
}
|
||||
Update-DiskLayoutCapacityStatus -State $State
|
||||
}
|
||||
|
||||
function Update-DiskLayoutActionButtonsState {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
if ($null -eq $State.Controls.lstDataPartitions) { return }
|
||||
|
||||
$selectedItem = $State.Controls.lstDataPartitions.SelectedItem
|
||||
$selectedDataRows = @($State.Controls.lstDataPartitions.Items | Where-Object { $_.PartitionType -eq 'Data' })
|
||||
$selectedDataIndex = -1
|
||||
for ($dataIndex = 0; $dataIndex -lt $selectedDataRows.Count; $dataIndex++) {
|
||||
if ([object]::ReferenceEquals($selectedDataRows[$dataIndex], $selectedItem)) {
|
||||
$selectedDataIndex = $dataIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
$canMove = $selectedDataIndex -ge 0
|
||||
if ($null -ne $State.Controls.btnMoveDataPartitionTop) { $State.Controls.btnMoveDataPartitionTop.IsEnabled = $canMove -and $selectedDataIndex -gt 0 }
|
||||
if ($null -ne $State.Controls.btnMoveDataPartitionUp) { $State.Controls.btnMoveDataPartitionUp.IsEnabled = $canMove -and $selectedDataIndex -gt 0 }
|
||||
if ($null -ne $State.Controls.btnMoveDataPartitionDown) { $State.Controls.btnMoveDataPartitionDown.IsEnabled = $canMove -and $selectedDataIndex -lt ($selectedDataRows.Count - 1) }
|
||||
if ($null -ne $State.Controls.btnMoveDataPartitionBottom) { $State.Controls.btnMoveDataPartitionBottom.IsEnabled = $canMove -and $selectedDataIndex -lt ($selectedDataRows.Count - 1) }
|
||||
|
||||
if ($null -ne $State.Controls.btnRestoreRecoveryPartition) {
|
||||
$State.Controls.btnRestoreRecoveryPartition.Visibility = if ([bool]$State.Data.createRecoveryPartition) { 'Collapsed' } else { 'Visible' }
|
||||
$State.Controls.btnRestoreRecoveryPartition.IsEnabled = -not [bool]$State.Data.createRecoveryPartition
|
||||
}
|
||||
}
|
||||
|
||||
function Move-DataPartitionRow {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[ValidateSet('Top', 'Up', 'Down', 'Bottom')]
|
||||
[string]$Direction
|
||||
)
|
||||
|
||||
$selectedItem = $State.Controls.lstDataPartitions.SelectedItem
|
||||
if ($null -eq $selectedItem -or $selectedItem.PartitionType -ne 'Data') { return }
|
||||
|
||||
Sync-DiskLayoutRowsToControls -State $State
|
||||
$dataRows = @($State.Controls.lstDataPartitions.Items | Where-Object { $_.PartitionType -eq 'Data' })
|
||||
$currentIndex = -1
|
||||
for ($dataIndex = 0; $dataIndex -lt $dataRows.Count; $dataIndex++) {
|
||||
if ([object]::ReferenceEquals($dataRows[$dataIndex], $selectedItem)) {
|
||||
$currentIndex = $dataIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentIndex -lt 0) { return }
|
||||
|
||||
$targetIndex = switch ($Direction) {
|
||||
'Top' { 0 }
|
||||
'Up' { [Math]::Max(0, $currentIndex - 1) }
|
||||
'Down' { [Math]::Min($State.Data.additionalDataPartitionsDataList.Count - 1, $currentIndex + 1) }
|
||||
'Bottom' { $State.Data.additionalDataPartitionsDataList.Count - 1 }
|
||||
}
|
||||
|
||||
if ($targetIndex -eq $currentIndex) { return }
|
||||
|
||||
$movingItem = $State.Data.additionalDataPartitionsDataList[$currentIndex]
|
||||
$State.Data.additionalDataPartitionsDataList.RemoveAt($currentIndex)
|
||||
$State.Data.additionalDataPartitionsDataList.Insert($targetIndex, $movingItem)
|
||||
Update-AdditionalDataPartitionsListView -State $State
|
||||
|
||||
$updatedDataRows = @($State.Controls.lstDataPartitions.Items | Where-Object { $_.PartitionType -eq 'Data' })
|
||||
if ($targetIndex -ge 0 -and $targetIndex -lt $updatedDataRows.Count) {
|
||||
$State.Controls.lstDataPartitions.SelectedItem = $updatedDataRows[$targetIndex]
|
||||
}
|
||||
|
||||
Update-DiskLayoutActionButtonsState -State $State
|
||||
}
|
||||
|
||||
function Clear-AdditionalDataPartitions {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
$result = [System.Windows.MessageBox]::Show("Are you sure you want to clear all additional data partitions?", "Clear Data Partitions", [System.Windows.MessageBoxButton]::YesNo, [System.Windows.MessageBoxImage]::Question)
|
||||
if ($result -ne [System.Windows.MessageBoxResult]::Yes) { return }
|
||||
|
||||
if ($null -eq $State.Data.additionalDataPartitionsDataList) {
|
||||
$State.Data.additionalDataPartitionsDataList = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
}
|
||||
$State.Data.additionalDataPartitionsDataList.Clear()
|
||||
Clear-AdditionalDataPartitionForm -State $State
|
||||
Update-AdditionalDataPartitionsListView -State $State
|
||||
|
||||
if ($null -ne $State.Controls.txtStatus) {
|
||||
$State.Controls.txtStatus.Text = "Additional data partitions list cleared."
|
||||
}
|
||||
}
|
||||
|
||||
function Restore-RecoveryPartition {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
$State.Data.createRecoveryPartition = $true
|
||||
Update-AdditionalDataPartitionsListView -State $State
|
||||
}
|
||||
|
||||
function Test-DiskLayoutConfiguration {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[psobject]$State,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[System.Collections.IDictionary]$Config
|
||||
)
|
||||
|
||||
$errors = [System.Collections.Generic.List[string]]::new()
|
||||
$driveLetterEntries = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
|
||||
$partitionRows = @($State.Controls.lstDataPartitions.ItemsSource)
|
||||
foreach ($partitionRow in $partitionRows) {
|
||||
if ($partitionRow.PartitionType -eq 'MSR') { continue }
|
||||
if ($partitionRow.PartitionType -eq 'Recovery' -and -not [bool]$Config.CreateRecoveryPartition) { continue }
|
||||
|
||||
$driveLetter = ([string]$partitionRow.DriveLetter).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
if ([string]::IsNullOrWhiteSpace($driveLetter) -or $driveLetter -notmatch '^[A-Z]$') {
|
||||
$errors.Add("$($partitionRow.Name) must use a single drive letter from A to Z.")
|
||||
continue
|
||||
}
|
||||
|
||||
$driveLetterEntries.Add([PSCustomObject]@{
|
||||
Name = $partitionRow.Name
|
||||
DriveLetter = $driveLetter
|
||||
})
|
||||
}
|
||||
|
||||
$duplicateDriveLetters = @($driveLetterEntries | Group-Object -Property DriveLetter | Where-Object { $_.Count -gt 1 })
|
||||
foreach ($duplicateDriveLetter in $duplicateDriveLetters) {
|
||||
$partitionNames = ($duplicateDriveLetter.Group | ForEach-Object { $_.Name }) -join ', '
|
||||
$errors.Add("Drive letter $($duplicateDriveLetter.Name) is used by multiple partitions: $partitionNames.")
|
||||
}
|
||||
|
||||
$dataPartitions = @($Config.AdditionalDataPartitions)
|
||||
if ($dataPartitions.Count -gt 0 -and $Config.OSPartitionSize -le 0) {
|
||||
$errors.Add('Windows must use a fixed size when data partitions are configured.')
|
||||
}
|
||||
|
||||
$fillRemainingCount = 0
|
||||
if ($Config.OSPartitionSize -le 0) {
|
||||
$fillRemainingCount++
|
||||
}
|
||||
$fillRemainingCount += @($dataPartitions | Where-Object { $_.FillRemaining }).Count
|
||||
if ($fillRemainingCount -gt 1) {
|
||||
$errors.Add('Only one partition can fill remaining disk space.')
|
||||
}
|
||||
|
||||
$duplicateDataNames = @($dataPartitions | Group-Object -Property Name | Where-Object { $_.Count -gt 1 })
|
||||
foreach ($duplicateDataName in $duplicateDataNames) {
|
||||
$errors.Add("Data partition name '$($duplicateDataName.Name)' is used more than once.")
|
||||
}
|
||||
|
||||
[uint64]$fixedPartitionSizeBytes = 260MB + 16MB
|
||||
if ($Config.OSPartitionSize -gt 0) {
|
||||
$fixedPartitionSizeBytes += [uint64]$Config.OSPartitionSize
|
||||
}
|
||||
if ([bool]$Config.CreateRecoveryPartition -and $Config.RecoveryPartitionSize -gt 0) {
|
||||
$fixedPartitionSizeBytes += [uint64]$Config.RecoveryPartitionSize
|
||||
}
|
||||
foreach ($dataPartition in $dataPartitions) {
|
||||
if ($dataPartition.FillRemaining) { continue }
|
||||
if ([uint64]$dataPartition.SizeBytes -le 0) {
|
||||
$errors.Add("Data partition '$($dataPartition.Name)' must have a size or Fill Remaining selected.")
|
||||
continue
|
||||
}
|
||||
$fixedPartitionSizeBytes += [uint64]$dataPartition.SizeBytes
|
||||
}
|
||||
|
||||
if ([uint64]$Config.Disksize -le 0) {
|
||||
$errors.Add('Disk Size must be greater than 0 GB.')
|
||||
}
|
||||
elseif ($fixedPartitionSizeBytes -gt [uint64]$Config.Disksize) {
|
||||
$fixedPartitionSizeGB = [Math]::Round(($fixedPartitionSizeBytes / 1GB), 2)
|
||||
$diskSizeGB = [Math]::Round(([uint64]$Config.Disksize / 1GB), 2)
|
||||
$errors.Add("Fixed partition sizes total $fixedPartitionSizeGB GB, which exceeds the configured disk size of $diskSizeGB GB.")
|
||||
}
|
||||
|
||||
if ($errors.Count -gt 0) {
|
||||
[System.Windows.MessageBox]::Show(($errors -join [System.Environment]::NewLine), "Disk Layout Validation", "OK", "Warning") | Out-Null
|
||||
return $false
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Clear-AdditionalDataPartitionForm {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
@@ -372,6 +892,14 @@ function Add-AdditionalDataPartition {
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
try {
|
||||
Sync-DiskLayoutRowsToControls -State $State
|
||||
}
|
||||
catch {
|
||||
[System.Windows.MessageBox]::Show($_.Exception.Message, "Disk Layout", "OK", "Warning") | Out-Null
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($null -eq $State.Data.additionalDataPartitionsDataList) {
|
||||
$State.Data.additionalDataPartitionsDataList = [System.Collections.Generic.List[PSCustomObject]]::new()
|
||||
}
|
||||
@@ -395,8 +923,15 @@ function Add-AdditionalDataPartition {
|
||||
$reservedDriveLetters = @(
|
||||
Get-ComboBoxSelectedContent -ComboBox $State.Controls.cmbSystemPartitionDriveLetter
|
||||
Get-ComboBoxSelectedContent -ComboBox $State.Controls.cmbWindowsPartitionDriveLetter
|
||||
Get-ComboBoxSelectedContent -ComboBox $State.Controls.cmbRecoveryPartitionDriveLetter
|
||||
) | ForEach-Object { ([string]$_).Trim().TrimEnd(':').ToUpperInvariant() } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
|
||||
if ([bool]$State.Data.createRecoveryPartition) {
|
||||
$reservedDriveLetters += ([string](Get-ComboBoxSelectedContent -ComboBox $State.Controls.cmbRecoveryPartitionDriveLetter)).Trim().TrimEnd(':').ToUpperInvariant()
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace([string]$State.Controls.txtOSPartitionSizeGB.Text)) {
|
||||
[System.Windows.MessageBox]::Show("Set a fixed Windows partition size before adding data partitions. Windows can fill remaining space only when no data partitions are configured.", "Windows Partition Size Required", "OK", "Warning") | Out-Null
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($reservedDriveLetters -contains $driveLetter) {
|
||||
[System.Windows.MessageBox]::Show("Drive letter $driveLetter is already used by a required build partition.", "Duplicate Drive Letter", "OK", "Warning") | Out-Null
|
||||
@@ -474,14 +1009,22 @@ function Remove-SelectedDataPartition {
|
||||
[psobject]$State
|
||||
)
|
||||
|
||||
$itemsToRemove = @($State.Controls.lstDataPartitions.SelectedItems)
|
||||
$itemsToRemove = @($State.Controls.lstDataPartitions.Items | Where-Object { $_.IsSelected -and $_.CanRemove })
|
||||
if ($itemsToRemove.Count -eq 0) {
|
||||
[System.Windows.MessageBox]::Show("Please select one or more data partitions to remove.", "Selection Required", "OK", "Warning") | Out-Null
|
||||
[System.Windows.MessageBox]::Show("Select one or more removable partitions to remove.", "Selection Required", "OK", "Warning") | Out-Null
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($itemToRemove in $itemsToRemove) {
|
||||
$State.Data.additionalDataPartitionsDataList.Remove($itemToRemove) | Out-Null
|
||||
if ($itemToRemove.PartitionType -eq 'Recovery') {
|
||||
$State.Data.createRecoveryPartition = $false
|
||||
}
|
||||
elseif ($itemToRemove.PartitionType -eq 'Data') {
|
||||
$dataItem = @($State.Data.additionalDataPartitionsDataList | Where-Object { $_.Name -eq $itemToRemove.Name -and $_.DriveLetter -eq $itemToRemove.DriveLetter } | Select-Object -First 1)
|
||||
if ($dataItem.Count -gt 0) {
|
||||
$State.Data.additionalDataPartitionsDataList.Remove($dataItem[0]) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Update-AdditionalDataPartitionsListView -State $State
|
||||
@@ -497,6 +1040,8 @@ function Get-AdditionalDataPartitionConfigRows {
|
||||
return @()
|
||||
}
|
||||
|
||||
Sync-DiskLayoutRowsToControls -State $State
|
||||
|
||||
return @($State.Data.additionalDataPartitionsDataList | ForEach-Object {
|
||||
[PSCustomObject]@{
|
||||
Name = $_.Name
|
||||
@@ -836,10 +1381,16 @@ function Update-UIFromConfig {
|
||||
Set-UIValue -ControlName 'txtVMNamePrefix' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'FFUPrefix' -State $State
|
||||
Set-UIValue -ControlName 'txtOSPartitionSizeGB' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'OSPartitionSize' -TransformValue { param($val) Get-PartitionSizeGBDisplay -SizeBytes $val } -State $State
|
||||
Set-UIValue -ControlName 'txtRecoveryPartitionSizeGB' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'RecoveryPartitionSize' -TransformValue { param($val) Get-PartitionSizeGBDisplay -SizeBytes $val } -State $State
|
||||
Import-AdditionalDataPartitionsFromConfig -State $State -ConfigContent $ConfigContent
|
||||
if ($ConfigContent.PSObject.Properties.Name -contains 'CreateRecoveryPartition') {
|
||||
$State.Data.createRecoveryPartition = [System.Convert]::ToBoolean($ConfigContent.CreateRecoveryPartition)
|
||||
}
|
||||
else {
|
||||
$State.Data.createRecoveryPartition = $true
|
||||
}
|
||||
Set-UIValue -ControlName 'cmbSystemPartitionDriveLetter' -PropertyName 'SelectedItem' -ConfigObject $ConfigContent -ConfigKey 'SystemPartitionDriveLetter' -TransformValue { param($val) ([string]$val).Trim().TrimEnd(':').ToUpperInvariant() } -State $State
|
||||
Set-UIValue -ControlName 'cmbWindowsPartitionDriveLetter' -PropertyName 'SelectedItem' -ConfigObject $ConfigContent -ConfigKey 'WindowsPartitionDriveLetter' -TransformValue { param($val) ([string]$val).Trim().TrimEnd(':').ToUpperInvariant() } -State $State
|
||||
Set-UIValue -ControlName 'cmbRecoveryPartitionDriveLetter' -PropertyName 'SelectedItem' -ConfigObject $ConfigContent -ConfigKey 'RecoveryPartitionDriveLetter' -TransformValue { param($val) ([string]$val).Trim().TrimEnd(':').ToUpperInvariant() } -State $State
|
||||
Import-AdditionalDataPartitionsFromConfig -State $State -ConfigContent $ConfigContent
|
||||
Set-UIValue -ControlName 'cmbLogicalSectorSize' -PropertyName 'SelectedItem' -ConfigObject $ConfigContent -ConfigKey 'LogicalSectorSizeBytes' -TransformValue { param($val) $val.ToString() } -State $State
|
||||
$State.Controls.spVMNetworkingSettings.IsEnabled = $true -eq $State.Controls.chkEnableVMNetworking.IsChecked
|
||||
if (-not ($true -eq $State.Controls.chkEnableVMNetworking.IsChecked)) {
|
||||
|
||||
@@ -995,6 +995,19 @@ function Register-EventHandlers {
|
||||
Update-VMNetworkingControls -State $localState
|
||||
})
|
||||
|
||||
$diskLayoutTextChangedHandler = {
|
||||
param($eventSource, $textChangedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Update-DiskLayoutCapacityStatus -State $localState
|
||||
}
|
||||
|
||||
foreach ($diskLayoutTextBox in @($State.Controls.txtDiskSize, $State.Controls.txtOSPartitionSizeGB, $State.Controls.txtRecoveryPartitionSizeGB)) {
|
||||
if ($null -ne $diskLayoutTextBox) {
|
||||
$diskLayoutTextBox.Add_TextChanged($diskLayoutTextChangedHandler)
|
||||
}
|
||||
}
|
||||
|
||||
$State.Controls.chkDataPartitionFillRemaining.Add_Checked({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
@@ -1028,19 +1041,98 @@ function Register-EventHandlers {
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
|
||||
Clear-ListViewContent -State $localState `
|
||||
-ListViewControl $localState.Controls.lstDataPartitions `
|
||||
-BackingDataList $localState.Data.additionalDataPartitionsDataList `
|
||||
-ConfirmationTitle "Clear Data Partitions" `
|
||||
-ConfirmationMessage "Are you sure you want to clear all additional data partitions?" `
|
||||
-StatusMessage "Additional data partitions list cleared." `
|
||||
-TextBoxesToClear @($localState.Controls.txtDataPartitionName, $localState.Controls.txtDataPartitionSizeGB)
|
||||
|
||||
$localState.Controls.chkDataPartitionFillRemaining.IsChecked = $false
|
||||
$localState.Controls.txtDataPartitionSizeGB.IsEnabled = $true
|
||||
Clear-AdditionalDataPartitions -State $localState
|
||||
})
|
||||
|
||||
$State.Controls.btnRestoreRecoveryPartition.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Restore-RecoveryPartition -State $localState
|
||||
})
|
||||
|
||||
$State.Controls.btnMoveDataPartitionTop.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Move-DataPartitionRow -State $localState -Direction Top
|
||||
})
|
||||
|
||||
$State.Controls.btnMoveDataPartitionUp.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Move-DataPartitionRow -State $localState -Direction Up
|
||||
})
|
||||
|
||||
$State.Controls.btnMoveDataPartitionDown.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Move-DataPartitionRow -State $localState -Direction Down
|
||||
})
|
||||
|
||||
$State.Controls.btnMoveDataPartitionBottom.Add_Click({
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Move-DataPartitionRow -State $localState -Direction Bottom
|
||||
})
|
||||
|
||||
$State.Controls.lstDataPartitions.Add_PreviewKeyDown({
|
||||
param($eventSource, $keyEvent)
|
||||
if ($keyEvent.Key -eq 'Space') {
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Invoke-ListViewItemToggle -ListView $eventSource -State $localState -HeaderCheckBoxKeyName 'chkSelectAllDataPartitions'
|
||||
Update-DiskLayoutActionButtonsState -State $localState
|
||||
$keyEvent.Handled = $true
|
||||
}
|
||||
})
|
||||
|
||||
$State.Controls.lstDataPartitions.Add_SelectionChanged({
|
||||
param($eventSource, $selChangeEvent)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Update-DiskLayoutActionButtonsState -State $localState
|
||||
})
|
||||
|
||||
$State.Controls.lstDataPartitions.AddHandler(
|
||||
[System.Windows.Controls.Primitives.ButtonBase]::ClickEvent,
|
||||
[System.Windows.RoutedEventHandler] {
|
||||
param($eventSource, $routedEventArgs)
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
|
||||
$originalSource = $routedEventArgs.OriginalSource
|
||||
if ($originalSource -is [System.Windows.Controls.Primitives.ToggleButton] -and -not ($originalSource -is [System.Windows.Controls.CheckBox])) { return }
|
||||
|
||||
$partitionRow = $originalSource.DataContext
|
||||
if ($originalSource -is [System.Windows.Controls.CheckBox] -and [string]$originalSource.Tag -eq 'DiskLayoutFillRemaining' -and $null -ne $partitionRow -and $null -ne $partitionRow.PSObject.Properties['PartitionType']) {
|
||||
Update-DiskLayoutFillRemainingState -State $localState -PartitionRow $partitionRow
|
||||
}
|
||||
else {
|
||||
Update-DiskLayoutCapacityStatus -State $localState
|
||||
}
|
||||
Update-DiskLayoutActionButtonsState -State $localState
|
||||
})
|
||||
|
||||
$State.Controls.lstDataPartitions.AddHandler(
|
||||
[System.Windows.UIElement]::LostKeyboardFocusEvent,
|
||||
[System.Windows.Input.KeyboardFocusChangedEventHandler] {
|
||||
param($eventSource, $focusChangedEventArgs)
|
||||
$textBox = $focusChangedEventArgs.OriginalSource
|
||||
if ($null -eq $textBox -or -not ($textBox -is [System.Windows.Controls.TextBox])) { return }
|
||||
|
||||
$partitionRow = $textBox.DataContext
|
||||
if ($null -eq $partitionRow -or $null -eq $partitionRow.PSObject.Properties['PartitionType']) { return }
|
||||
|
||||
$window = [System.Windows.Window]::GetWindow($eventSource)
|
||||
$localState = $window.Tag
|
||||
Update-DiskLayoutCapacityStatus -State $localState
|
||||
},
|
||||
$true)
|
||||
|
||||
# Persist custom VM switch name when user edits it while 'Other' is selected
|
||||
$State.Controls.txtCustomVMSwitchName.Add_LostFocus({
|
||||
param($eventSource, $routedEventArgs)
|
||||
|
||||
@@ -260,6 +260,8 @@ function Initialize-UIControls {
|
||||
$State.Controls.cmbRecoveryPartitionDriveLetter = $window.FindName('cmbRecoveryPartitionDriveLetter')
|
||||
$State.Controls.txtOSPartitionSizeGB = $window.FindName('txtOSPartitionSizeGB')
|
||||
$State.Controls.txtRecoveryPartitionSizeGB = $window.FindName('txtRecoveryPartitionSizeGB')
|
||||
$State.Controls.ellipseDiskLayoutCapacityStatus = $window.FindName('ellipseDiskLayoutCapacityStatus')
|
||||
$State.Controls.txtDiskLayoutCapacityStatusValue = $window.FindName('txtDiskLayoutCapacityStatusValue')
|
||||
$State.Controls.txtDataPartitionName = $window.FindName('txtDataPartitionName')
|
||||
$State.Controls.cmbDataPartitionDriveLetter = $window.FindName('cmbDataPartitionDriveLetter')
|
||||
$State.Controls.txtDataPartitionSizeGB = $window.FindName('txtDataPartitionSizeGB')
|
||||
@@ -267,6 +269,11 @@ function Initialize-UIControls {
|
||||
$State.Controls.btnAddDataPartition = $window.FindName('btnAddDataPartition')
|
||||
$State.Controls.btnRemoveSelectedDataPartitions = $window.FindName('btnRemoveSelectedDataPartitions')
|
||||
$State.Controls.btnClearDataPartitions = $window.FindName('btnClearDataPartitions')
|
||||
$State.Controls.btnRestoreRecoveryPartition = $window.FindName('btnRestoreRecoveryPartition')
|
||||
$State.Controls.btnMoveDataPartitionTop = $window.FindName('btnMoveDataPartitionTop')
|
||||
$State.Controls.btnMoveDataPartitionUp = $window.FindName('btnMoveDataPartitionUp')
|
||||
$State.Controls.btnMoveDataPartitionDown = $window.FindName('btnMoveDataPartitionDown')
|
||||
$State.Controls.btnMoveDataPartitionBottom = $window.FindName('btnMoveDataPartitionBottom')
|
||||
$State.Controls.lstDataPartitions = $window.FindName('lstDataPartitions')
|
||||
$State.Controls.cmbLogicalSectorSize = $window.FindName('cmbLogicalSectorSize')
|
||||
$State.Controls.txtProductKey = $window.FindName('txtProductKey')
|
||||
@@ -461,6 +468,8 @@ function Initialize-UIDefaults {
|
||||
$State.Controls.cmbSystemPartitionDriveLetter.SelectedItem = ($State.Controls.cmbSystemPartitionDriveLetter.Items | Where-Object { $_.Content -eq $State.Defaults.generalDefaults.SystemPartitionDriveLetter })
|
||||
$State.Controls.cmbWindowsPartitionDriveLetter.SelectedItem = ($State.Controls.cmbWindowsPartitionDriveLetter.Items | Where-Object { $_.Content -eq $State.Defaults.generalDefaults.WindowsPartitionDriveLetter })
|
||||
$State.Controls.cmbRecoveryPartitionDriveLetter.SelectedItem = ($State.Controls.cmbRecoveryPartitionDriveLetter.Items | Where-Object { $_.Content -eq $State.Defaults.generalDefaults.RecoveryPartitionDriveLetter })
|
||||
$State.Controls.txtOSPartitionSizeGB.Clear()
|
||||
$State.Data.createRecoveryPartition = $true
|
||||
$State.Controls.cmbLogicalSectorSize.SelectedItem = ($State.Controls.cmbLogicalSectorSize.Items | Where-Object { $_.Content -eq $State.Defaults.generalDefaults.LogicalSectorSize.ToString() })
|
||||
|
||||
# Populate Windows Release, Version, and SKU comboboxes
|
||||
@@ -823,13 +832,70 @@ function Initialize-DynamicUIElements {
|
||||
# Keep BYO application columns sized to the current visible content.
|
||||
Enable-ListViewColumnAutoResize -ListView $State.Controls.lstApplications -FixedColumnIndexes @(0)
|
||||
|
||||
# Additional Data Partitions ListView setup
|
||||
$State.Controls.lstDataPartitions.ItemsSource = $State.Data.additionalDataPartitionsDataList.ToArray()
|
||||
# Disk Layout ListView setup
|
||||
$diskLayoutGridView = New-Object System.Windows.Controls.GridView
|
||||
$State.Controls.lstDataPartitions.View = $diskLayoutGridView
|
||||
|
||||
$itemStyleDataPartitions = New-Object System.Windows.Style([System.Windows.Controls.ListViewItem])
|
||||
if ($null -ne $listViewItemBaseStyle) { $itemStyleDataPartitions.BasedOn = $listViewItemBaseStyle }
|
||||
$itemStyleDataPartitions.Setters.Add((New-Object System.Windows.Setter([System.Windows.Controls.ListViewItem]::HorizontalContentAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)))
|
||||
$State.Controls.lstDataPartitions.ItemContainerStyle = $itemStyleDataPartitions
|
||||
Request-ListViewColumnAutoResize -ListView $State.Controls.lstDataPartitions
|
||||
|
||||
Add-SelectableGridViewColumn -ListView $State.Controls.lstDataPartitions -State $State -HeaderCheckBoxKeyName "chkSelectAllDataPartitions" -ColumnWidth 60 -IsSelectablePropertyName "CanSelect" -HeaderToolTip "Select all removable partitions in the disk layout." -ItemToolTip "Select this partition for removal. System, MSR, and Windows partitions cannot be removed."
|
||||
Add-SortableColumn -gridView $diskLayoutGridView -header "Name" -binding "Name" -width 180 -headerHorizontalAlignment Left
|
||||
|
||||
$driveLetterColumn = New-Object System.Windows.Controls.GridViewColumn
|
||||
$driveLetterColumn.Header = "Drive Letter"
|
||||
$driveLetterColumn.Width = 120
|
||||
$driveLetterTemplate = New-Object System.Windows.DataTemplate
|
||||
$driveLetterFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.ComboBox])
|
||||
$driveLetterFactory.SetBinding([System.Windows.Controls.ItemsControl]::ItemsSourceProperty, (New-Object System.Windows.Data.Binding("DriveLetterOptions")))
|
||||
$driveLetterBinding = New-Object System.Windows.Data.Binding("DriveLetter")
|
||||
$driveLetterBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay
|
||||
$driveLetterBinding.UpdateSourceTrigger = [System.Windows.Data.UpdateSourceTrigger]::PropertyChanged
|
||||
$driveLetterFactory.SetBinding([System.Windows.Controls.Primitives.Selector]::SelectedItemProperty, $driveLetterBinding)
|
||||
$driveLetterFactory.SetBinding([System.Windows.Controls.Control]::IsEnabledProperty, (New-Object System.Windows.Data.Binding("CanEditDriveLetter")))
|
||||
$driveLetterTemplate.VisualTree = $driveLetterFactory
|
||||
$driveLetterColumn.CellTemplate = $driveLetterTemplate
|
||||
$diskLayoutGridView.Columns.Add($driveLetterColumn)
|
||||
|
||||
$sizeColumn = New-Object System.Windows.Controls.GridViewColumn
|
||||
$sizeColumn.Header = "Size (GB)"
|
||||
$sizeColumn.Width = 120
|
||||
$sizeTemplate = New-Object System.Windows.DataTemplate
|
||||
$sizeFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.TextBox])
|
||||
$sizeBinding = New-Object System.Windows.Data.Binding("SizeGB")
|
||||
$sizeBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay
|
||||
$sizeBinding.UpdateSourceTrigger = [System.Windows.Data.UpdateSourceTrigger]::PropertyChanged
|
||||
$sizeFactory.SetBinding([System.Windows.Controls.TextBox]::TextProperty, $sizeBinding)
|
||||
$sizeFactory.SetBinding([System.Windows.Controls.Control]::IsEnabledProperty, (New-Object System.Windows.Data.Binding("CanEditSize")))
|
||||
$sizeTemplate.VisualTree = $sizeFactory
|
||||
$sizeColumn.CellTemplate = $sizeTemplate
|
||||
$diskLayoutGridView.Columns.Add($sizeColumn)
|
||||
|
||||
$fillRemainingColumn = New-Object System.Windows.Controls.GridViewColumn
|
||||
$fillRemainingColumn.Header = "Fill Remaining"
|
||||
$fillRemainingColumn.Width = 140
|
||||
$fillRemainingTemplate = New-Object System.Windows.DataTemplate
|
||||
$fillRemainingGridFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.Grid])
|
||||
$fillRemainingGridFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Stretch)
|
||||
$fillRemainingFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.CheckBox])
|
||||
$fillRemainingFactory.SetValue([System.Windows.FrameworkElement]::TagProperty, 'DiskLayoutFillRemaining')
|
||||
$fillRemainingFactory.SetValue([System.Windows.Controls.Control]::ToolTipProperty, 'Use all remaining VHDX space for this partition. Only one partition can fill remaining space.')
|
||||
$fillRemainingFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Center)
|
||||
$fillRemainingFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
||||
$fillRemainingBinding = New-Object System.Windows.Data.Binding("FillRemaining")
|
||||
$fillRemainingBinding.Mode = [System.Windows.Data.BindingMode]::TwoWay
|
||||
$fillRemainingBinding.UpdateSourceTrigger = [System.Windows.Data.UpdateSourceTrigger]::PropertyChanged
|
||||
$fillRemainingFactory.SetBinding([System.Windows.Controls.Primitives.ToggleButton]::IsCheckedProperty, $fillRemainingBinding)
|
||||
$fillRemainingFactory.SetBinding([System.Windows.Controls.Control]::IsEnabledProperty, (New-Object System.Windows.Data.Binding("CanEditFillRemaining")))
|
||||
$fillRemainingFactory.SetBinding([System.Windows.UIElement]::VisibilityProperty, (New-Object System.Windows.Data.Binding("FillRemainingVisibility")))
|
||||
$fillRemainingGridFactory.AppendChild($fillRemainingFactory)
|
||||
$fillRemainingTemplate.VisualTree = $fillRemainingGridFactory
|
||||
$fillRemainingColumn.CellTemplate = $fillRemainingTemplate
|
||||
$diskLayoutGridView.Columns.Add($fillRemainingColumn)
|
||||
|
||||
Update-AdditionalDataPartitionsListView -State $State
|
||||
|
||||
# Apps Script Variables ListView setup
|
||||
# Bind ItemsSource to the data list
|
||||
|
||||
@@ -332,6 +332,9 @@ function Add-SelectableGridViewColumn {
|
||||
[Parameter(Mandatory)]
|
||||
[double]$ColumnWidth,
|
||||
[string]$IsSelectedPropertyName = "IsSelected",
|
||||
[string]$IsSelectablePropertyName,
|
||||
[string]$HeaderToolTip = 'Select or clear all selectable rows.',
|
||||
[string]$ItemToolTip = 'Select this row.',
|
||||
[switch]$HeaderSelectionAffectsVisibleItemsOnly
|
||||
)
|
||||
|
||||
@@ -350,10 +353,12 @@ function Add-SelectableGridViewColumn {
|
||||
# Store header metadata, including whether select-all should only affect visible rows.
|
||||
$headerTagObject = [PSCustomObject]@{
|
||||
PropertyName = $IsSelectedPropertyName
|
||||
SelectablePropertyName = $IsSelectablePropertyName
|
||||
ListViewControl = $ListView
|
||||
HeaderSelectionAffectsVisibleItemsOnly = [bool]$HeaderSelectionAffectsVisibleItemsOnly
|
||||
}
|
||||
$headerCheckBox.Tag = $headerTagObject
|
||||
$headerCheckBox.ToolTip = $HeaderToolTip
|
||||
|
||||
$headerCheckBox.Add_Checked({
|
||||
param($senderCheckBoxLocal, $eventArgsCheckedLocal)
|
||||
@@ -379,7 +384,14 @@ function Add-SelectableGridViewColumn {
|
||||
}
|
||||
|
||||
if ($collectionToUpdate.Count -gt 0) {
|
||||
foreach ($item in $collectionToUpdate) { $item.$($localPropertyName) = $true }
|
||||
foreach ($item in $collectionToUpdate) {
|
||||
$selectablePropertyName = $tagData.SelectablePropertyName
|
||||
if (-not [string]::IsNullOrWhiteSpace($selectablePropertyName) -and $null -ne $item.PSObject.Properties[$selectablePropertyName] -and -not [bool]$item.$selectablePropertyName) {
|
||||
$item.$($localPropertyName) = $false
|
||||
continue
|
||||
}
|
||||
$item.$($localPropertyName) = $true
|
||||
}
|
||||
$actualListView.Items.Refresh()
|
||||
}
|
||||
})
|
||||
@@ -451,8 +463,12 @@ function Add-SelectableGridViewColumn {
|
||||
|
||||
$checkBoxFactory = New-Object System.Windows.FrameworkElementFactory([System.Windows.Controls.CheckBox])
|
||||
$checkBoxFactory.SetBinding([System.Windows.Controls.CheckBox]::IsCheckedProperty, (New-Object System.Windows.Data.Binding($IsSelectedPropertyName)))
|
||||
if (-not [string]::IsNullOrWhiteSpace($IsSelectablePropertyName)) {
|
||||
$checkBoxFactory.SetBinding([System.Windows.Controls.CheckBox]::IsEnabledProperty, (New-Object System.Windows.Data.Binding($IsSelectablePropertyName)))
|
||||
}
|
||||
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::HorizontalAlignmentProperty, [System.Windows.HorizontalAlignment]::Center)
|
||||
$checkBoxFactory.SetValue([System.Windows.FrameworkElement]::VerticalAlignmentProperty, [System.Windows.VerticalAlignment]::Center)
|
||||
$checkBoxFactory.SetValue([System.Windows.Controls.Control]::ToolTipProperty, $ItemToolTip)
|
||||
|
||||
# MODIFICATION: Store the actual ListView object in the item checkbox's Tag
|
||||
$tagObject = [PSCustomObject]@{
|
||||
@@ -731,6 +747,14 @@ function Update-SelectAllHeaderCheckBoxState {
|
||||
$collectionToInspect = @($ListView.Items)
|
||||
}
|
||||
|
||||
$selectablePropertyName = $null
|
||||
if ($null -ne $HeaderCheckBox.Tag -and $null -ne $HeaderCheckBox.Tag.PSObject.Properties['SelectablePropertyName']) {
|
||||
$selectablePropertyName = [string]$HeaderCheckBox.Tag.SelectablePropertyName
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($selectablePropertyName)) {
|
||||
$collectionToInspect = @($collectionToInspect | Where-Object { $null -eq $_.PSObject.Properties[$selectablePropertyName] -or [bool]$_.$selectablePropertyName })
|
||||
}
|
||||
|
||||
# If no items are available in the selected scope, force unchecked.
|
||||
if ($collectionToInspect.Count -eq 0) {
|
||||
$HeaderCheckBox.IsChecked = $false
|
||||
@@ -771,6 +795,7 @@ function Invoke-ListViewItemToggle {
|
||||
|
||||
$selectedItem = $ListView.SelectedItem
|
||||
if ($null -eq $selectedItem) { return }
|
||||
if ($null -ne $selectedItem.PSObject.Properties['CanSelect'] -and -not [bool]$selectedItem.CanSelect) { return }
|
||||
|
||||
# Store the current index to restore focus later
|
||||
$currentIndex = $ListView.SelectedIndex
|
||||
|
||||
Binary file not shown.
+9
-19
@@ -23,10 +23,6 @@ Drop down of detected VM Switches. There's also an **Other** option which allows
|
||||
|
||||
This setting is only used when **Enable VM Networking (Experimental)** is turned on. VM-based builds still capture from the host-side VHDX after the VM shuts down, so you only need a switch when the VM requires network connectivity during provisioning.
|
||||
|
||||
## Disk Size (GB)
|
||||
|
||||
Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk. You may want to increase the size if you're installing many apps.
|
||||
|
||||
## Memory (GB)
|
||||
|
||||
Amount of memory to allocate for the virtual machine. Recommended to use 8GB if possible, especially for Windows 11. Default is 4GB.
|
||||
@@ -43,29 +39,23 @@ Default is `$FFUDevelopmentPath\VM`. This is the location of the VHDX that gets
|
||||
|
||||
Prefix for the generated VM. Default is _FFU.
|
||||
|
||||
## System Partition Drive Letter
|
||||
## Disk Layout
|
||||
|
||||
Drive letter used for the System partition while building the FFU VHDX. Default is `S`.
|
||||
Configures the VHDX size and the build-time partition layout used for the captured FFU.
|
||||
|
||||
## Windows Partition Drive Letter
|
||||
**Disk Size (GB)** sets the virtual hard disk size for the VM. Default is a 50GB dynamic disk. Increase this when the image needs more space for Windows, apps, updates, or data partitions.
|
||||
|
||||
Drive letter used for the Windows partition while building the FFU VHDX. Default is `W`.
|
||||
The partition list shows the build order: System, MSR, Windows, Recovery, and optional data partitions. System, Windows, and Recovery have editable build-time drive letters. Defaults are `S`, `W`, and `R`. The MSR row is display-only, fixed at 16MB, and does not use a drive letter.
|
||||
|
||||
## Recovery Partition Drive Letter
|
||||
These drive letters only affect FFU creation. The deployment script discovers the Windows and Recovery partitions from the applied disk instead of relying on fixed partition numbers.
|
||||
|
||||
Drive letter used for the Recovery partition while building the FFU VHDX. Default is `R`.
|
||||
Leave the Windows size blank to let Windows fill the remaining disk. Set a fixed Windows size before adding data partitions so the VHDX has space left for Recovery and data volumes.
|
||||
|
||||
These settings only affect FFU creation. The deployment script discovers the Windows and Recovery partitions from the applied disk instead of relying on fixed partition numbers.
|
||||
Use the Recovery size only when you need a fixed Recovery partition size. Leave it blank to let the build calculate the Recovery size from `winre.wim` plus buffer space.
|
||||
|
||||
## Additional Data Partitions
|
||||
The Recovery partition can be removed by selecting its row checkbox and using **Remove Selected**. Use **Restore Recovery** to add it back before saving or building. Removing Recovery saves `CreateRecoveryPartition` as `false` in the generated config.
|
||||
|
||||
Creates optional data partitions after the Windows Recovery partition. This keeps Recovery immediately after Windows, which leaves the normal WinRE layout intact while still allowing one or more data volumes in the captured FFU.
|
||||
|
||||
Set **Windows Partition Size (GB)** before adding data partitions. Without an explicit Windows partition size, Windows would consume the remaining VHDX space and there would be no room left for Recovery and data partitions.
|
||||
|
||||
Use **Recovery Partition Size (GB)** only when you need a fixed Recovery partition size. Leave it blank to let the build calculate the Recovery size from `winre.wim` plus buffer space.
|
||||
|
||||
Each data partition has a name, build-time drive letter, and either a size in GB or **Fill Remaining**. Only one data partition can use **Fill Remaining**. Fixed-size data partitions are created before the fill-remaining data partition so the fill-remaining volume does not consume space reserved for later fixed-size volumes.
|
||||
Each data partition has a name, build-time drive letter, and either a size in GB or **Fill Remaining**. Only one data partition can use **Fill Remaining**. Data partitions can be reordered with the arrow buttons. **Clear** removes only data partitions, not the base partition rows.
|
||||
|
||||
The Apps ISO drive letter is discovered at runtime. If you create a data partition that uses `D:`, application installs should use `%FFUAppsRoot%` for Apps ISO paths. Legacy `D:\` paths in `UserAppList.json` are still supported when they point to files on the Apps ISO.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
|
||||
| Parameter | Type | UI Control | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| -AdditionalFFUFiles | string[] | Copy Additional FFU Files + Additional FFU Files list | Array of full file paths to existing FFU files that should also be copied to the deployment USB when -CopyAdditionalFFUFiles is set to $true. |
|
||||
| -AdditionalDataPartitions | object[] | Additional Data Partitions | Creates optional data partitions after the Recovery partition. Each item supports Name, Label, DriveLetter, SizeBytes or SizeGB, FillRemaining, and FileSystem. Only one item can use FillRemaining. Fixed-size data partitions are created before the fill-remaining data partition. |
|
||||
| -AdditionalDataPartitions | object[] | Disk Layout | Creates optional data partitions after the base Windows layout. Recovery normally remains before data partitions unless -CreateRecoveryPartition is $false. Each item supports Name, Label, DriveLetter, SizeBytes or SizeGB, FillRemaining, and FileSystem. Only one item can use FillRemaining. |
|
||||
| -AllowExternalHardDiskMedia | bool | Allow External Hard Disk Media | When set to $true, will allow the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is not defined. |
|
||||
| -AllowVHDXCaching | bool | Allow VHDX Caching | When set to $true, will cache the VHDX file to the $FFUDevelopmentPath\VHDXCache folder and create a config json file that will keep track of the Windows build information, the updates installed, and the logical sector byte size information. Default is $false. |
|
||||
| -AppListPath | string | AppList.json Path | Path to a JSON file containing a list of applications to install using WinGet. Default is $FFUDevelopmentPath\Apps\AppList.json. |
|
||||
@@ -41,6 +41,7 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
|
||||
| -CopyPPKG | bool | Copy Provisioning Package | When set to $true, will copy the provisioning package from the $FFUDevelopmentPath\PPKG folder to the Deployment partition of the USB drive. Default is $false. |
|
||||
| -CopyUnattend | bool | Copy Unattend.xml | When set to $true, stages the selected architecture-specific unattend XML file as Unattend.xml on the Deployment partition of the USB drive. Cannot be used together with -InjectUnattend. Default is $false. |
|
||||
| -CreateDeploymentMedia | bool | Create Deployment Media | When set to $true, this will create WinPE deployment media for use when deploying to a physical device. |
|
||||
| -CreateRecoveryPartition | bool | Disk Layout | Controls whether the Recovery partition is created in the FFU VHDX. Default is $true. In the UI, remove the Recovery row to save $false and use Restore Recovery to add it back. |
|
||||
| -CustomFFUNameTemplate | string | Custom FFU Name Template | Sets a custom FFU output name with placeholders. Allowed placeholders are: {WindowsRelease}, {WindowsVersion}, {SKU}, {BuildDate}, {yyyy}, {MM}, {dd}, {H}, {hh}, {mm}, {tt}. |
|
||||
| -DeviceNamePrefixes | string[] | Specify a list of Prefixes | Sets the prefixes used when DeviceNamingMode is Prefixes. Each entry becomes a line in prefixes.txt on the deployment media. |
|
||||
| -DeviceNamePrefixesPath | string | Prefixes File Path | Path to the source prefixes file used for legacy copy or when -DeviceNamePrefixes is not supplied. Default is $FFUDevelopmentPath\Unattend\prefixes.txt. |
|
||||
@@ -48,7 +49,7 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
|
||||
| -DeviceNameSerialComputerNamesPath | string | Serial Computer Names CSV Mapping File Path | Path to the source CSV file used when DeviceNamingMode is SerialComputerNames and -DeviceNameSerialComputerNames is not supplied. Default is $FFUDevelopmentPath\Unattend\SerialComputerNames.csv. |
|
||||
| -DeviceNameTemplate | string | Specify Device Name | Sets the device name used when DeviceNamingMode is Template. Supports a static name or the %serial% token when -CopyUnattend is used. |
|
||||
| -DeviceNamingMode | string | Device Naming expander | Controls how device naming is handled when unattend content is copied to USB media or injected into the FFU. Accepted values are Legacy, None, Prompt, Template, Prefixes, and SerialComputerNames. The UI shows None, Prompt, Template, Prefixes, and SerialComputerNames. When device naming is left untouched in the UI, the generated config does not write DeviceNamingMode, which preserves the script default of Legacy. Prompt rewrites the staged deployment unattend to the existing manual prompt placeholder and requires -CopyUnattend. Prefixes writes prefixes.txt and requires -CopyUnattend. SerialComputerNames writes SerialComputerNames.csv and requires -CopyUnattend. |
|
||||
| -Disksize | uint64 | Disk Size (GB) | Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk. |
|
||||
| -Disksize | uint64 | Disk Layout: Disk Size (GB) | Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk. |
|
||||
| -DriversFolder | string | Drivers Folder | Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers. |
|
||||
| -DriversJsonPath | string | Drivers.json Path | Path to a JSON file that specifies which drivers to download. |
|
||||
| -EnableVMNetworking | bool | Enable VM Networking (Experimental) | When set to $true, connects the build VM to the selected Hyper-V switch during provisioning. Default is $false because internet-connected Sysprep is experimental. |
|
||||
@@ -70,21 +71,21 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
|
||||
| -Model | string | Driver Models list | Model of the device to download drivers. This is required if Make is set. |
|
||||
| -OfficeConfigXMLFile | string | Office Configuration XML File | Path to a custom Office configuration XML file to use for installation. |
|
||||
| -Optimize | bool | Optimize | When set to $true, will optimize the FFU file. Default is $true. |
|
||||
| -OptimizeFFUPartitionNumber | int | CLI/config only | Optional partition number to pass to DISM /Optimize-FFU /PartitionNumber. Leave as 0 to optimize with DISM defaults. |
|
||||
| -OSPartitionSize | uint64 | Windows Partition Size (GB) | Fixed size of the Windows partition in bytes. Required when -AdditionalDataPartitions is configured so the VHDX has room for Recovery and data partitions. The UI stores this from the GB value. |
|
||||
| -OptimizeFFUPartitionNumber | int | CLI/config only | Optional partition number to pass to DISM /Optimize-FFU /PartitionNumber. Leave as 0 to optimize the Fill Remaining partition, or Windows when no partition uses Fill Remaining. |
|
||||
| -OSPartitionSize | uint64 | Disk Layout: Windows Size (GB) | Fixed size of the Windows partition in bytes. Required when -AdditionalDataPartitions is configured so the VHDX has room for data partitions. The UI stores this from the GB value. |
|
||||
| -OptionalFeatures | string | Optional Features | Provide a semicolon-separated list of Windows optional features you want to include in the FFU (e.g., netfx3;TFTP). |
|
||||
| -OrchestrationPath | string | Application Path (derived Orchestration path) | Path to the orchestration folder containing scripts that run inside the VM. Default is $FFUDevelopmentPath\Apps\Orchestration. |
|
||||
| -PEDriversFolder | string | PE Drivers Folder | Path to the folder containing drivers to be injected into the WinPE deployment media. Default is $FFUDevelopmentPath\PEDrivers. |
|
||||
| -Processors | int | Processors | Number of virtual processors for the virtual machine. Recommended to use at least 4. |
|
||||
| -ProductKey | string | Product Key | Product key for the Windows edition specified in WindowsSKU. This will overwrite whatever SKU is entered for WindowsSKU. Recommended to use if you want to use a MAK or KMS key to activate Enterprise or Education. If using VL media instead of consumer media, you'll want to enter a MAK or KMS key here. |
|
||||
| -PromptExternalHardDiskMedia | bool | Prompt for External Hard Disk Media | When set to $true, will prompt the user to confirm the use of media identified as External Hard Disk media via WMI class Win32_DiskDrive. Default is $true. |
|
||||
| -RecoveryPartitionDriveLetter | string | Recovery Partition Drive Letter | Drive letter used for the Recovery partition while building the FFU VHDX. Default is R. |
|
||||
| -RecoveryPartitionSize | uint64 | Recovery Partition Size (GB) | Optional fixed size of the Recovery partition in bytes. Leave unset or 0 to let the build calculate the Recovery partition size from winre.wim plus buffer space. The UI stores this from the GB value. |
|
||||
| -RecoveryPartitionDriveLetter | string | Disk Layout: Recovery Drive Letter | Drive letter used for the Recovery partition while building the FFU VHDX. Default is R. Ignored when -CreateRecoveryPartition is $false. |
|
||||
| -RecoveryPartitionSize | uint64 | Disk Layout: Recovery Size (GB) | Optional fixed size of the Recovery partition in bytes. Leave unset or 0 to let the build calculate the Recovery partition size from winre.wim plus buffer space. Ignored when -CreateRecoveryPartition is $false. The UI stores this from the GB value. |
|
||||
| -RemoveApps | bool | Remove Apps Folder Content | When set to $true, will remove the application content in the Apps folder after the FFU has been captured. Default is $true. |
|
||||
| -RemoveDownloadedESD | bool | Remove Downloaded ESD file(s) | When set to $true, downloaded Windows ESD files are automatically deleted after they have been applied. Default is $true. |
|
||||
| -RemoveFFU | bool | Remove FFU | When set to $true, will remove the FFU file from the $FFUDevelopmentPath\FFU folder after it has been copied to the USB drive. Default is $false. |
|
||||
| -RemoveUpdates | bool | Remove Downloaded Update Files | When set to $true, will remove the downloaded CU, MSRT, Defender, Edge, OneDrive, and .NET files downloaded. Default is $true. |
|
||||
| -SystemPartitionDriveLetter | string | System Partition Drive Letter | Drive letter used for the System partition while building the FFU VHDX. Default is S. |
|
||||
| -SystemPartitionDriveLetter | string | Disk Layout: System Drive Letter | Drive letter used for the System partition while building the FFU VHDX. Default is S. |
|
||||
| -Threads | int | Threads | Controls the throttle applied to parallel tasks inside the script. Default is 5, matching the UI Threads field, and applies to driver downloads invoked through Invoke-ParallelProcessing. |
|
||||
| -UnattendArm64FilePath | string | arm64 Unattend File Path | Path to the arm64 unattend XML source file used by Copy Unattend.xml and Inject Unattend.xml. Default is $FFUDevelopmentPath\Unattend\unattend_arm64.xml. |
|
||||
| -UnattendX64FilePath | string | x64 Unattend File Path | Path to the x64 unattend XML source file used by Copy Unattend.xml and Inject Unattend.xml. Default is $FFUDevelopmentPath\Unattend\unattend_x64.xml. |
|
||||
@@ -104,7 +105,7 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
|
||||
| -VMLocation | string | VM Location | Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to. |
|
||||
| -VMSwitchName | string | VM Switch Name + Custom VM Switch Name (when Other selected) | Name of the Hyper-V virtual switch used when -EnableVMNetworking is set to $true. |
|
||||
| -WindowsArch | string | Windows Architecture | String value of 'x86', 'x64', or 'arm64'. This is used to identify which architecture of Windows to download. Default is 'x64'. |
|
||||
| -WindowsPartitionDriveLetter | string | Windows Partition Drive Letter | Drive letter used for the Windows partition while building the FFU VHDX. Default is W. |
|
||||
| -WindowsPartitionDriveLetter | string | Disk Layout: Windows Drive Letter | Drive letter used for the Windows partition while building the FFU VHDX. Default is W. |
|
||||
| -WindowsLang | string | Windows Language | String value in language-region format (e.g., 'en-us'). This is used to identify which language of media to download. Default is 'en-us'. |
|
||||
| -WindowsRelease | int | Windows Release | Integer value of 10, 11, 2016, 2019, 2021, 2022, 2024, or 2025. This is used to identify which Windows client/LTSC/server release to use. Default is 11. |
|
||||
| -WindowsSKU | string | Windows SKU | Edition/SKU to install. Accepted values are: 'Home', 'Home N', 'Home Single Language', 'Education', 'Education N', 'Pro', 'Pro N', 'Pro Education', 'Pro Education N', 'Pro for Workstations', 'Pro N for Workstations', 'Enterprise', 'Enterprise N', 'Enterprise 2016 LTSB', 'Enterprise N 2016 LTSB', 'Enterprise LTSC', 'Enterprise N LTSC', 'IoT Enterprise LTSC', 'IoT Enterprise N LTSC', 'Standard', 'Standard (Desktop Experience)', 'Datacenter', 'Datacenter (Desktop Experience)'. |
|
||||
|
||||
Reference in New Issue
Block a user