Adds custom unattend XML file selection paths

Adds parameters for x64 and arm64 unattend XML file paths.
Updates the BuildFFUVM_UI to let the user pick specific XML files to copy or inject instead of requiring them to exist in the `unattend` folder.
Adds validation to ensure the selected unattend XML files are not empty.
Updates documentation to reflect the new functionality.
This commit is contained in:
rbalsleyMSFT
2026-04-09 16:44:39 -07:00
parent f1f1957c43
commit 7bd5decc62
10 changed files with 258 additions and 57 deletions
+72 -33
View File
@@ -70,7 +70,7 @@ When set to $true, enables adding WinPE drivers. By default copies drivers from
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. 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.
.PARAMETER CopyUnattend .PARAMETER CopyUnattend
When set to $true, will copy the $FFUDevelopmentPath\Unattend folder to the Deployment partition of the USB drive. Default is $false. When set to $true, stages the selected architecture-specific unattend XML file as Unattend.xml on the deployment partition of the USB drive. Default is $false.
.PARAMETER DeviceNamingMode .PARAMETER DeviceNamingMode
Controls how device naming is handled when unattend content is copied to USB media or injected into the FFU. Supported values are Legacy, None, Prompt, Template, and Prefixes. Controls how device naming is handled when unattend content is copied to USB media or injected into the FFU. Supported values are Legacy, None, Prompt, Template, and Prefixes.
@@ -84,6 +84,12 @@ Sets the prefixes used when DeviceNamingMode is Prefixes. Each entry becomes a l
.PARAMETER DeviceNamePrefixesPath .PARAMETER DeviceNamePrefixesPath
Path to the source prefixes file used for legacy copy or when DeviceNamePrefixes is not supplied. Default is $FFUDevelopmentPath\Unattend\prefixes.txt. Path to the source prefixes file used for legacy copy or when DeviceNamePrefixes is not supplied. Default is $FFUDevelopmentPath\Unattend\prefixes.txt.
.PARAMETER UnattendX64FilePath
Path to the x64 unattend XML source file. Default is $FFUDevelopmentPath\Unattend\unattend_x64.xml.
.PARAMETER UnattendArm64FilePath
Path to the arm64 unattend XML source file. Default is $FFUDevelopmentPath\Unattend\unattend_arm64.xml.
.PARAMETER CreateDeploymentMedia .PARAMETER CreateDeploymentMedia
When set to $true, this will create WinPE deployment media for use when deploying to a physical device. When set to $true, this will create WinPE deployment media for use when deploying to a physical device.
@@ -118,7 +124,7 @@ Prefix for the generated FFU file. Default is _FFU.
Headers to use when downloading files. Not recommended to modify. Headers to use when downloading files. Not recommended to modify.
.PARAMETER InjectUnattend .PARAMETER InjectUnattend
When set to $true and InstallApps is also $true, copies unattend_[arch].xml from $FFUDevelopmentPath\unattend to $FFUDevelopmentPath\Apps\Unattend\Unattend.xml so sysprep can use it inside the VM. Default is $false. When set to $true and InstallApps is also $true, stages the selected architecture-specific unattend XML file to $FFUDevelopmentPath\Apps\Unattend\Unattend.xml so sysprep can use it inside the VM. Default is $false.
.PARAMETER InstallApps .PARAMETER InstallApps
When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.iso, install the apps, sysprep, and capture the VM. When set to $false, the FFU is created from a VHDX file, and no VM is created. When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.iso, install the apps, sysprep, and capture the VM. When set to $false, the FFU is created from a VHDX file, and no VM is created.
@@ -424,6 +430,8 @@ param(
[string]$DeviceNameTemplate, [string]$DeviceNameTemplate,
[string[]]$DeviceNamePrefixes, [string[]]$DeviceNamePrefixes,
[string]$DeviceNamePrefixesPath, [string]$DeviceNamePrefixesPath,
[string]$UnattendX64FilePath,
[string]$UnattendArm64FilePath,
[bool]$CopyAutopilot, [bool]$CopyAutopilot,
[bool]$CompactOS = $true, [bool]$CompactOS = $true,
[bool]$CleanupDeployISO = $true, [bool]$CleanupDeployISO = $true,
@@ -527,11 +535,31 @@ function Get-UnattendSourcePath {
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$UnattendFolder, [string]$UnattendFolder,
[Parameter(Mandatory = $true)] [Parameter(Mandatory = $true)]
[string]$WindowsArch [string]$WindowsArch,
[string]$UnattendX64FilePath,
[string]$UnattendArm64FilePath
) )
$archSuffix = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' } $resolvedArch = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
return Join-Path $UnattendFolder "unattend_$archSuffix.xml" $resolvedSourcePath = if ($resolvedArch -eq 'arm64') {
if ([string]::IsNullOrWhiteSpace($UnattendArm64FilePath)) {
Join-Path $UnattendFolder 'unattend_arm64.xml'
}
else {
$UnattendArm64FilePath
}
}
else {
if ([string]::IsNullOrWhiteSpace($UnattendX64FilePath)) {
Join-Path $UnattendFolder 'unattend_x64.xml'
}
else {
$UnattendX64FilePath
}
}
WriteLog "Resolved unattend source path for ${resolvedArch}: $resolvedSourcePath"
return $resolvedSourcePath
} }
function Initialize-UnattendComputerNamePath { function Initialize-UnattendComputerNamePath {
@@ -915,6 +943,8 @@ if (-not $EdgePath) { $EdgePath = "$AppsPath\Edge" }
if (-not $DriversFolder) { $DriversFolder = "$FFUDevelopmentPath\Drivers" } if (-not $DriversFolder) { $DriversFolder = "$FFUDevelopmentPath\Drivers" }
if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" } if (-not $PPKGFolder) { $PPKGFolder = "$FFUDevelopmentPath\PPKG" }
if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" } if (-not $UnattendFolder) { $UnattendFolder = "$FFUDevelopmentPath\Unattend" }
if ([string]::IsNullOrWhiteSpace($UnattendX64FilePath)) { $UnattendX64FilePath = Join-Path $UnattendFolder 'unattend_x64.xml' }
if ([string]::IsNullOrWhiteSpace($UnattendArm64FilePath)) { $UnattendArm64FilePath = Join-Path $UnattendFolder 'unattend_arm64.xml' }
if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" } if (-not $AutopilotFolder) { $AutopilotFolder = "$FFUDevelopmentPath\Autopilot" }
if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" } if (-not $PEDriversFolder) { $PEDriversFolder = "$FFUDevelopmentPath\PEDrivers" }
if (-not $VHDXCacheFolder) { $VHDXCacheFolder = "$FFUDevelopmentPath\VHDXCache" } if (-not $VHDXCacheFolder) { $VHDXCacheFolder = "$FFUDevelopmentPath\VHDXCache" }
@@ -4397,11 +4427,31 @@ Function New-DeploymentUSB {
function Get-LocalUnattendSourcePath { function Get-LocalUnattendSourcePath {
param( param(
[string]$UnattendFolder, [string]$UnattendFolder,
[string]$WindowsArch [string]$WindowsArch,
[string]$UnattendX64FilePath,
[string]$UnattendArm64FilePath
) )
$archSuffix = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' } $resolvedArch = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
return Join-Path $UnattendFolder "unattend_$archSuffix.xml" $resolvedSourcePath = if ($resolvedArch -eq 'arm64') {
if ([string]::IsNullOrWhiteSpace($UnattendArm64FilePath)) {
Join-Path $UnattendFolder 'unattend_arm64.xml'
}
else {
$UnattendArm64FilePath
}
}
else {
if ([string]::IsNullOrWhiteSpace($UnattendX64FilePath)) {
Join-Path $UnattendFolder 'unattend_x64.xml'
}
else {
$UnattendX64FilePath
}
}
WriteLog "Resolved unattend source path for ${resolvedArch}: $resolvedSourcePath"
return $resolvedSourcePath
} }
function Initialize-UnattendComputerNamePath { function Initialize-UnattendComputerNamePath {
@@ -4587,7 +4637,7 @@ Function New-DeploymentUSB {
$UnattendPathOnUSB = Join-Path $DeployPartitionDriveLetter "Unattend" $UnattendPathOnUSB = Join-Path $DeployPartitionDriveLetter "Unattend"
WriteLog "Copying unattend file to $UnattendPathOnUSB" WriteLog "Copying unattend file to $UnattendPathOnUSB"
New-Item -Path $UnattendPathOnUSB -ItemType Directory -ErrorAction SilentlyContinue | Out-Null New-Item -Path $UnattendPathOnUSB -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
$unattendSource = Get-LocalUnattendSourcePath -UnattendFolder $using:UnattendFolder -WindowsArch $using:WindowsArch $unattendSource = Get-LocalUnattendSourcePath -UnattendFolder $using:UnattendFolder -WindowsArch $using:WindowsArch -UnattendX64FilePath $using:UnattendX64FilePath -UnattendArm64FilePath $using:UnattendArm64FilePath
$legacyPrefixesWillBeStaged = ($using:DeviceNamingMode -eq 'Legacy') -and (Test-Path -Path $using:resolvedDeviceNamePrefixesPath -PathType Leaf) $legacyPrefixesWillBeStaged = ($using:DeviceNamingMode -eq 'Legacy') -and (Test-Path -Path $using:resolvedDeviceNamePrefixesPath -PathType Leaf)
Save-LocalStagedUnattendFile -SourcePath $unattendSource -DestinationPath (Join-Path $UnattendPathOnUSB 'Unattend.xml') -DeviceNamingMode $using:DeviceNamingMode -DeviceNameTemplate $using:normalizedDeviceNameTemplate -WindowsArch $using:WindowsArch -LegacyPrefixesWillBeStaged $legacyPrefixesWillBeStaged Save-LocalStagedUnattendFile -SourcePath $unattendSource -DestinationPath (Join-Path $UnattendPathOnUSB 'Unattend.xml') -DeviceNamingMode $using:DeviceNamingMode -DeviceNameTemplate $using:normalizedDeviceNameTemplate -WindowsArch $using:WindowsArch -LegacyPrefixesWillBeStaged $legacyPrefixesWillBeStaged
if ($using:DeviceNamingMode -eq 'Prefixes') { if ($using:DeviceNamingMode -eq 'Prefixes') {
@@ -5850,21 +5900,23 @@ if ($CopyAutopilot) {
WriteLog 'Autopilot validation complete' WriteLog 'Autopilot validation complete'
} }
#Validate Unattend folder # Validate unattend source file
if ($CopyUnattend) { if ($CopyUnattend -or $InjectUnattend) {
WriteLog 'Doing Unattend validation' WriteLog 'Doing Unattend validation'
if (!(Test-Path -Path $UnattendFolder)) { $selectedUnattendMode = if ($CopyUnattend) { 'CopyUnattend' } else { 'InjectUnattend' }
WriteLog "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing" $unattendSourcePath = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch -UnattendX64FilePath $UnattendX64FilePath -UnattendArm64FilePath $UnattendArm64FilePath
throw "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing" if (!(Test-Path -Path $unattendSourcePath -PathType Leaf)) {
WriteLog "-$selectedUnattendMode is set to `$true, but the selected unattend XML file is missing: $unattendSourcePath"
throw "-$selectedUnattendMode is set to `$true, but the selected unattend XML file is missing: $unattendSourcePath"
} }
#Check for .XML file
if (!(Get-ChildItem -Path $UnattendFolder -Filter unattend_*.xml)) { $selectedUnattendFile = Get-Item -Path $unattendSourcePath -ErrorAction SilentlyContinue
WriteLog "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file" if (($null -eq $selectedUnattendFile) -or ($selectedUnattendFile.Length -le 0)) {
throw "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file" WriteLog "-$selectedUnattendMode is set to `$true, but the selected unattend XML file is empty: $unattendSourcePath"
throw "-$selectedUnattendMode is set to `$true, but the selected unattend XML file is empty: $unattendSourcePath"
} }
if ($DeviceNamingMode -ne 'None') { if ($DeviceNamingMode -ne 'None') {
$unattendSourcePath = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch
try { try {
[xml]$validationUnattendXml = Get-Content -Path $unattendSourcePath [xml]$validationUnattendXml = Get-Content -Path $unattendSourcePath
$null = Initialize-UnattendComputerNamePath -UnattendXml $validationUnattendXml -WindowsArch $WindowsArch $null = Initialize-UnattendComputerNamePath -UnattendXml $validationUnattendXml -WindowsArch $WindowsArch
@@ -5877,19 +5929,6 @@ if ($CopyUnattend) {
WriteLog 'Unattend validation complete' WriteLog 'Unattend validation complete'
} }
if ($InjectUnattend -and ($DeviceNamingMode -ne 'None')) {
$injectUnattendSourcePath = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch
if (Test-Path -Path $injectUnattendSourcePath -PathType Leaf) {
try {
[xml]$validationUnattendXml = Get-Content -Path $injectUnattendSourcePath
$null = Initialize-UnattendComputerNamePath -UnattendXml $validationUnattendXml -WindowsArch $WindowsArch
}
catch {
throw "DeviceNamingMode $DeviceNamingMode requires a valid specialize/Microsoft-Windows-Shell-Setup/ComputerName path in $injectUnattendSourcePath. $($_.Exception.Message)"
}
}
}
#Override $InstallApps value if using ESD to build FFU. This is due to a strange issue where building the FFU #Override $InstallApps value if using ESD to build FFU. This is due to a strange issue where building the FFU
#from vhdx doesn't work (you get an older style OOBE screen and get stuck in an OOBE reboot loop when hitting next). #from vhdx doesn't work (you get an older style OOBE screen and get stuck in an OOBE reboot loop when hitting next).
#This behavior doesn't happen with WIM files. #This behavior doesn't happen with WIM files.
@@ -6787,7 +6826,7 @@ if ($InstallApps) {
#Create Apps ISO #Create Apps ISO
# Inject Unattend.xml into Apps if requested and applicable # Inject Unattend.xml into Apps if requested and applicable
if ($InstallApps -and $InjectUnattend) { if ($InstallApps -and $InjectUnattend) {
$unattendSource = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch $unattendSource = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch -UnattendX64FilePath $UnattendX64FilePath -UnattendArm64FilePath $UnattendArm64FilePath
# Ensure target folder exists under Apps # Ensure target folder exists under Apps
$targetFolder = Join-Path $AppsPath 'Unattend' $targetFolder = Join-Path $AppsPath 'Unattend'
+32
View File
@@ -439,6 +439,38 @@ $script:uiState.Controls.btnRun.Add_Click({
return return
} }
if ($config.CopyUnattend -or $config.InjectUnattend) {
$selectedUnattendArch = if ($config.WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
$selectedUnattendSourcePath = if ($selectedUnattendArch -eq 'arm64') {
[string]$config.UnattendArm64FilePath
}
else {
[string]$config.UnattendX64FilePath
}
if ([string]::IsNullOrWhiteSpace($selectedUnattendSourcePath)) {
[System.Windows.MessageBox]::Show("Select a valid $selectedUnattendArch unattend XML file before using Copy Unattend.xml or Inject Unattend.xml.", "Unattend File Required", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: unattend file path required."
return
}
if (-not (Test-Path -Path $selectedUnattendSourcePath -PathType Leaf)) {
[System.Windows.MessageBox]::Show("The selected $selectedUnattendArch unattend XML file was not found:`n$selectedUnattendSourcePath", "Unattend File Missing", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: unattend file missing."
return
}
$selectedUnattendFileInfo = Get-Item -Path $selectedUnattendSourcePath -ErrorAction SilentlyContinue
if (($null -eq $selectedUnattendFileInfo) -or ($selectedUnattendFileInfo.Length -le 0)) {
[System.Windows.MessageBox]::Show("The selected $selectedUnattendArch unattend XML file is empty:`n$selectedUnattendSourcePath", "Unattend File Empty", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: unattend file empty."
return
}
}
if ($config.DeviceNamingMode -eq 'Prompt') { if ($config.DeviceNamingMode -eq 'Prompt') {
if (-not $config.CopyUnattend) { if (-not $config.CopyUnattend) {
[System.Windows.MessageBox]::Show("Select Copy Unattend.xml before using 'Prompt for Device Name'.", "Copy Unattend Required", "OK", "Warning") | Out-Null [System.Windows.MessageBox]::Show("Select Copy Unattend.xml before using 'Prompt for Device Name'.", "Copy Unattend Required", "OK", "Warning") | Out-Null
+38 -11
View File
@@ -836,11 +836,13 @@
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 9: General Build Options Checkboxes --> <!-- Row 9: General Build Options Checkboxes -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 10: Device Naming --> <!-- Row 10: Unattend.xml Options -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 11: Build USB Drive Section --> <!-- Row 11: Device Naming -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 12: Post-Build Cleanup --> <!-- Row 12: Build USB Drive Section -->
<RowDefinition Height="Auto"/>
<!-- Row 13: Post-Build Cleanup -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -900,13 +902,39 @@
<CheckBox x:Name="chkOptimize" Content="Optimize" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will optimize the OS when building the FFU."/> <CheckBox x:Name="chkOptimize" Content="Optimize" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will optimize the OS when building the FFU."/>
<CheckBox x:Name="chkAllowVHDXCaching" Content="Allow VHDX Caching" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will cache the VHDX file to cache folder and create a config json file to track Windows build information."/> <CheckBox x:Name="chkAllowVHDXCaching" Content="Allow VHDX Caching" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will cache the VHDX file to cache folder and create a config json file to track Windows build information."/>
<CheckBox x:Name="chkCreateDeploymentMedia" Content="Create Deployment Media" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, this will create WinPE deployment media for use when deploying to a physical device."/> <CheckBox x:Name="chkCreateDeploymentMedia" Content="Create Deployment Media" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, this will create WinPE deployment media for use when deploying to a physical device."/>
<CheckBox x:Name="chkInjectUnattend" Content="Inject Unattend.xml" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true and Install Apps is enabled, copies unattend_[arch].xml from $FFUDevelopmentPath\unattend into Apps\Unattend\Unattend.xml to be used by sysprep."/>
<CheckBox x:Name="chkVerbose" Content="Verbose" Margin="0" VerticalAlignment="Center" Tag="When set to $true, will enable write-verbose output to the console for the build script."/> <CheckBox x:Name="chkVerbose" Content="Verbose" Margin="0" VerticalAlignment="Center" Tag="When set to $true, will enable write-verbose output to the console for the build script."/>
</StackPanel> </StackPanel>
</Expander> </Expander>
<!-- Row 10: Device Naming --> <!-- Row 10: Unattend.xml Options -->
<Expander Grid.Row="10" x:Name="deviceNamingSection" Header="Device Naming" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"> <Expander Grid.Row="10" Header="Unattend.xml Options" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<StackPanel Margin="0,8,0,0">
<TextBlock Text="Choose how unattend content is staged and which architecture-specific source files are used." Margin="0,0,0,12" TextWrapping="Wrap"/>
<TextBlock Text="x64 Unattend File Path" Margin="0,0,0,8" TextWrapping="Wrap"/>
<Grid Margin="0,0,0,16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtUnattendX64FilePath" Grid.Column="0" VerticalAlignment="Center" ToolTip="Path to the x64 unattend XML source file. You can use any file name."/>
<Button x:Name="btnBrowseUnattendX64FilePath" Grid.Column="1" Content="Browse..." Padding="12,4" Margin="8,0,0,0" VerticalAlignment="Center" ToolTip="Browse to the x64 unattend XML source file."/>
</Grid>
<TextBlock Text="arm64 Unattend File Path" Margin="0,0,0,8" TextWrapping="Wrap"/>
<Grid Margin="0,0,0,16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtUnattendArm64FilePath" Grid.Column="0" VerticalAlignment="Center" ToolTip="Path to the arm64 unattend XML source file. You can use any file name."/>
<Button x:Name="btnBrowseUnattendArm64FilePath" Grid.Column="1" Content="Browse..." Padding="12,4" Margin="8,0,0,0" VerticalAlignment="Center" ToolTip="Browse to the arm64 unattend XML source file."/>
</Grid>
<CheckBox x:Name="chkInjectUnattend" Content="Inject Unattend.xml" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true and Install Apps is enabled, uses the selected architecture-specific unattend XML file and copies it into Apps\Unattend\Unattend.xml to be used by sysprep."/>
<CheckBox x:Name="chkCopyUnattend" Content="Copy Unattend.xml" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, uses the selected architecture-specific unattend XML file and copies it to the USB drive as Unattend.xml."/>
</StackPanel>
</Expander>
<!-- Row 11: Device Naming -->
<Expander Grid.Row="11" x:Name="deviceNamingSection" Header="Device Naming" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<StackPanel Margin="0,8,0,0"> <StackPanel Margin="0,8,0,0">
<TextBlock Text="Choose how the device should be named when unattend is applied." Margin="0,0,0,12" TextWrapping="Wrap"/> <TextBlock Text="Choose how the device should be named when unattend is applied." Margin="0,0,0,12" TextWrapping="Wrap"/>
<RadioButton x:Name="rbDeviceNamingNone" Content="No Device Name" GroupName="DeviceNamingMode" IsChecked="True" Margin="0,0,0,8" ToolTip="Apply unattend without setting a specific computer name."/> <RadioButton x:Name="rbDeviceNamingNone" Content="No Device Name" GroupName="DeviceNamingMode" IsChecked="True" Margin="0,0,0,8" ToolTip="Apply unattend without setting a specific computer name."/>
@@ -938,8 +966,8 @@
</StackPanel> </StackPanel>
</Expander> </Expander>
<!-- Row 11: Build USB Drive Section --> <!-- Row 12: Build USB Drive Section -->
<Expander Grid.Row="11" x:Name="usbDriveSection" Header="Build USB Drive Options" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"> <Expander Grid.Row="12" x:Name="usbDriveSection" Header="Build USB Drive Options" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<StackPanel Margin="0,8,0,0"> <StackPanel Margin="0,8,0,0">
<CheckBox x:Name="chkBuildUSBDriveEnable" Content="Build USB Drive" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will partition and format a USB drive and copy the captured FFU to the drive."/> <CheckBox x:Name="chkBuildUSBDriveEnable" Content="Build USB Drive" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will partition and format a USB drive and copy the captured FFU to the drive."/>
<CheckBox x:Name="chkAllowExternalHardDiskMedia" Content="Allow External Hard Disk Media" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will allow the use of external hard disk media."/> <CheckBox x:Name="chkAllowExternalHardDiskMedia" Content="Allow External Hard Disk Media" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will allow the use of external hard disk media."/>
@@ -947,7 +975,6 @@
<CheckBox x:Name="chkSelectSpecificUSBDrives" Content="Select Specific USB Drives" Margin="0,0,0,8" VerticalAlignment="Center" Tag="Enable to select specific USB drives for building"/> <CheckBox x:Name="chkSelectSpecificUSBDrives" Content="Select Specific USB Drives" Margin="0,0,0,8" VerticalAlignment="Center" Tag="Enable to select specific USB drives for building"/>
<!-- Added Missing Checkboxes --> <!-- Added Missing Checkboxes -->
<CheckBox x:Name="chkCopyAutopilot" Content="Copy Autopilot Profile" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will copy the Autopilot profile to the USB drive."/> <CheckBox x:Name="chkCopyAutopilot" Content="Copy Autopilot Profile" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will copy the Autopilot profile to the USB drive."/>
<CheckBox x:Name="chkCopyUnattend" Content="Copy Unattend.xml" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will copy the Unattend.xml file to the USB drive."/>
<CheckBox x:Name="chkCopyPPKG" Content="Copy Provisioning Package" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will copy the provisioning package to the USB drive."/> <CheckBox x:Name="chkCopyPPKG" Content="Copy Provisioning Package" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, will copy the provisioning package to the USB drive."/>
<CheckBox x:Name="chkCopyAdditionalFFUFiles" Content="Copy Additional FFU Files" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, allows selecting existing FFU files in the capture folder to also copy to the USB drive."/> <CheckBox x:Name="chkCopyAdditionalFFUFiles" Content="Copy Additional FFU Files" Margin="0,0,0,8" VerticalAlignment="Center" Tag="When set to $true, allows selecting existing FFU files in the capture folder to also copy to the USB drive."/>
@@ -1004,8 +1031,8 @@
</StackPanel> </StackPanel>
</Expander> </Expander>
<!-- Row 12: Post-Build Cleanup --> <!-- Row 13: Post-Build Cleanup -->
<Expander Grid.Row="12" Header="Post-Build Cleanup" IsExpanded="False" Margin="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"> <Expander Grid.Row="13" Header="Post-Build Cleanup" IsExpanded="False" Margin="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<StackPanel Margin="0,8,0,0"> <StackPanel Margin="0,8,0,0">
<CheckBox x:Name="chkCleanupAppsISO" Content="Cleanup Apps ISO" Margin="0,0,0,8" VerticalAlignment="Center" Tag="Remove Apps ISO after FFU capture."/> <CheckBox x:Name="chkCleanupAppsISO" Content="Cleanup Apps ISO" Margin="0,0,0,8" VerticalAlignment="Center" Tag="Remove Apps ISO after FFU capture."/>
<CheckBox x:Name="chkCleanupDeployISO" Content="Cleanup Deploy ISO" Margin="0,0,0,8" VerticalAlignment="Center" Tag="Remove WinPE deployment ISO after FFU capture."/> <CheckBox x:Name="chkCleanupDeployISO" Content="Cleanup Deploy ISO" Margin="0,0,0,8" VerticalAlignment="Center" Tag="Remove WinPE deployment ISO after FFU capture."/>
@@ -43,6 +43,8 @@ function Get-UIConfig {
CopyAdditionalFFUFiles = $State.Controls.chkCopyAdditionalFFUFiles.IsChecked CopyAdditionalFFUFiles = $State.Controls.chkCopyAdditionalFFUFiles.IsChecked
CreateDeploymentMedia = $State.Controls.chkCreateDeploymentMedia.IsChecked CreateDeploymentMedia = $State.Controls.chkCreateDeploymentMedia.IsChecked
InjectUnattend = $State.Controls.chkInjectUnattend.IsChecked InjectUnattend = $State.Controls.chkInjectUnattend.IsChecked
UnattendX64FilePath = $State.Controls.txtUnattendX64FilePath.Text
UnattendArm64FilePath = $State.Controls.txtUnattendArm64FilePath.Text
CustomFFUNameTemplate = $State.Controls.txtCustomFFUNameTemplate.Text CustomFFUNameTemplate = $State.Controls.txtCustomFFUNameTemplate.Text
Disksize = [int64]$State.Controls.txtDiskSize.Text * 1GB Disksize = [int64]$State.Controls.txtDiskSize.Text * 1GB
DownloadDrivers = $State.Controls.chkDownloadDrivers.IsChecked DownloadDrivers = $State.Controls.chkDownloadDrivers.IsChecked
@@ -454,8 +456,18 @@ function Update-UIFromConfig {
Set-UIValue -ControlName 'chkCopyAdditionalFFUFiles' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyAdditionalFFUFiles' -State $State Set-UIValue -ControlName 'chkCopyAdditionalFFUFiles' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyAdditionalFFUFiles' -State $State
Set-UIValue -ControlName 'chkCreateDeploymentMedia' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CreateDeploymentMedia' -State $State Set-UIValue -ControlName 'chkCreateDeploymentMedia' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CreateDeploymentMedia' -State $State
Set-UIValue -ControlName 'chkInjectUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'InjectUnattend' -State $State Set-UIValue -ControlName 'chkInjectUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'InjectUnattend' -State $State
Set-UIValue -ControlName 'txtUnattendX64FilePath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'UnattendX64FilePath' -State $State
Set-UIValue -ControlName 'txtUnattendArm64FilePath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'UnattendArm64FilePath' -State $State
Set-UIValue -ControlName 'chkVerbose' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'Verbose' -State $State Set-UIValue -ControlName 'chkVerbose' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'Verbose' -State $State
if ([string]::IsNullOrWhiteSpace($State.Controls.txtUnattendX64FilePath.Text)) {
$State.Controls.txtUnattendX64FilePath.Text = Get-DefaultUnattendFilePath -FFUDevelopmentPath $State.Controls.txtFFUDevPath.Text -WindowsArch 'x64'
}
if ([string]::IsNullOrWhiteSpace($State.Controls.txtUnattendArm64FilePath.Text)) {
$State.Controls.txtUnattendArm64FilePath.Text = Get-DefaultUnattendFilePath -FFUDevelopmentPath $State.Controls.txtFFUDevPath.Text -WindowsArch 'arm64'
}
# USB Drive Modification group (Build Tab) # USB Drive Modification group (Build Tab)
Set-UIValue -ControlName 'chkCopyAutopilot' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyAutopilot' -State $State Set-UIValue -ControlName 'chkCopyAutopilot' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyAutopilot' -State $State
Set-UIValue -ControlName 'chkCopyUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyUnattend' -State $State Set-UIValue -ControlName 'chkCopyUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyUnattend' -State $State
@@ -101,6 +101,21 @@ function Get-DefaultDeviceNamePrefixesPath {
return Join-Path (Join-Path $FFUDevelopmentPath 'unattend') 'prefixes.txt' return Join-Path (Join-Path $FFUDevelopmentPath 'unattend') 'prefixes.txt'
} }
function Get-DefaultUnattendFilePath {
param(
[string]$FFUDevelopmentPath,
[ValidateSet('x64', 'arm64')]
[string]$WindowsArch
)
if ([string]::IsNullOrWhiteSpace($FFUDevelopmentPath)) {
return $null
}
$fileName = if ($WindowsArch -ieq 'arm64') { 'unattend_arm64.xml' } else { 'unattend_x64.xml' }
return Join-Path (Join-Path $FFUDevelopmentPath 'unattend') $fileName
}
function Import-DeviceNamePrefixesFromConfiguredPath { function Import-DeviceNamePrefixesFromConfiguredPath {
param( param(
[PSCustomObject]$State, [PSCustomObject]$State,
@@ -416,12 +431,24 @@ function Register-EventHandlers {
$selectedPath = Invoke-BrowseAction -Type 'Folder' -Title "Select FFU Development Path" $selectedPath = Invoke-BrowseAction -Type 'Folder' -Title "Select FFU Development Path"
if ($selectedPath) { if ($selectedPath) {
$currentPrefixesPath = $localState.Controls.txtDeviceNamePrefixesPath.Text $currentPrefixesPath = $localState.Controls.txtDeviceNamePrefixesPath.Text
$currentUnattendX64FilePath = $localState.Controls.txtUnattendX64FilePath.Text
$currentUnattendArm64FilePath = $localState.Controls.txtUnattendArm64FilePath.Text
$previousDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text $previousDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text
$previousDefaultUnattendX64FilePath = Get-DefaultUnattendFilePath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text -WindowsArch 'x64'
$previousDefaultUnattendArm64FilePath = Get-DefaultUnattendFilePath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text -WindowsArch 'arm64'
$localState.Controls.txtFFUDevPath.Text = $selectedPath $localState.Controls.txtFFUDevPath.Text = $selectedPath
$newDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $selectedPath $newDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $selectedPath
$newDefaultUnattendX64FilePath = Get-DefaultUnattendFilePath -FFUDevelopmentPath $selectedPath -WindowsArch 'x64'
$newDefaultUnattendArm64FilePath = Get-DefaultUnattendFilePath -FFUDevelopmentPath $selectedPath -WindowsArch 'arm64'
if ([string]::IsNullOrWhiteSpace($currentPrefixesPath) -or $currentPrefixesPath -ieq $previousDefaultPrefixesPath) { if ([string]::IsNullOrWhiteSpace($currentPrefixesPath) -or $currentPrefixesPath -ieq $previousDefaultPrefixesPath) {
$localState.Controls.txtDeviceNamePrefixesPath.Text = $newDefaultPrefixesPath $localState.Controls.txtDeviceNamePrefixesPath.Text = $newDefaultPrefixesPath
} }
if ([string]::IsNullOrWhiteSpace($currentUnattendX64FilePath) -or $currentUnattendX64FilePath -ieq $previousDefaultUnattendX64FilePath) {
$localState.Controls.txtUnattendX64FilePath.Text = $newDefaultUnattendX64FilePath
}
if ([string]::IsNullOrWhiteSpace($currentUnattendArm64FilePath) -or $currentUnattendArm64FilePath -ieq $previousDefaultUnattendArm64FilePath) {
$localState.Controls.txtUnattendArm64FilePath.Text = $newDefaultUnattendArm64FilePath
}
Import-DeviceNamePrefixesFromConfiguredPath -State $localState Import-DeviceNamePrefixesFromConfiguredPath -State $localState
Update-DeviceNamingControls -State $localState Update-DeviceNamingControls -State $localState
} }
@@ -484,6 +511,46 @@ function Register-EventHandlers {
Update-DeviceNamingControls -State $localState Update-DeviceNamingControls -State $localState
} }
}) })
$State.Controls.btnBrowseUnattendX64FilePath.Add_Click({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
$localState = $window.Tag
$currentUnattendX64FilePath = $localState.Controls.txtUnattendX64FilePath.Text
if ([string]::IsNullOrWhiteSpace($currentUnattendX64FilePath)) {
$currentUnattendX64FilePath = Get-DefaultUnattendFilePath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text -WindowsArch 'x64'
}
$initialDirectory = if ([string]::IsNullOrWhiteSpace($currentUnattendX64FilePath)) {
$null
}
else {
Split-Path $currentUnattendX64FilePath -Parent
}
$fileName = if ([string]::IsNullOrWhiteSpace($currentUnattendX64FilePath)) { 'unattend_x64.xml' } else { Split-Path $currentUnattendX64FilePath -Leaf }
$selectedPath = Invoke-BrowseAction -Type 'OpenFile' -Title 'Select x64 unattend XML file' -Filter 'XML files (*.xml)|*.xml|All files (*.*)|*.*' -InitialDirectory $initialDirectory -FileName $fileName
if (-not [string]::IsNullOrWhiteSpace($selectedPath)) {
$localState.Controls.txtUnattendX64FilePath.Text = $selectedPath
}
})
$State.Controls.btnBrowseUnattendArm64FilePath.Add_Click({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
$localState = $window.Tag
$currentUnattendArm64FilePath = $localState.Controls.txtUnattendArm64FilePath.Text
if ([string]::IsNullOrWhiteSpace($currentUnattendArm64FilePath)) {
$currentUnattendArm64FilePath = Get-DefaultUnattendFilePath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text -WindowsArch 'arm64'
}
$initialDirectory = if ([string]::IsNullOrWhiteSpace($currentUnattendArm64FilePath)) {
$null
}
else {
Split-Path $currentUnattendArm64FilePath -Parent
}
$fileName = if ([string]::IsNullOrWhiteSpace($currentUnattendArm64FilePath)) { 'unattend_arm64.xml' } else { Split-Path $currentUnattendArm64FilePath -Leaf }
$selectedPath = Invoke-BrowseAction -Type 'OpenFile' -Title 'Select arm64 unattend XML file' -Filter 'XML files (*.xml)|*.xml|All files (*.*)|*.*' -InitialDirectory $initialDirectory -FileName $fileName
if (-not [string]::IsNullOrWhiteSpace($selectedPath)) {
$localState.Controls.txtUnattendArm64FilePath.Text = $selectedPath
}
})
$State.Controls.btnSaveDeviceNamePrefixes.Add_Click({ $State.Controls.btnSaveDeviceNamePrefixes.Add_Click({
param($eventSource, $routedEventArgs) param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource) $window = [System.Windows.Window]::GetWindow($eventSource)
@@ -220,6 +220,10 @@ function Initialize-UIControls {
$State.Controls.chkAllowVHDXCaching = $window.FindName('chkAllowVHDXCaching') $State.Controls.chkAllowVHDXCaching = $window.FindName('chkAllowVHDXCaching')
$State.Controls.chkCreateDeploymentMedia = $window.FindName('chkCreateDeploymentMedia') $State.Controls.chkCreateDeploymentMedia = $window.FindName('chkCreateDeploymentMedia')
$State.Controls.chkInjectUnattend = $window.FindName('chkInjectUnattend') $State.Controls.chkInjectUnattend = $window.FindName('chkInjectUnattend')
$State.Controls.txtUnattendX64FilePath = $window.FindName('txtUnattendX64FilePath')
$State.Controls.btnBrowseUnattendX64FilePath = $window.FindName('btnBrowseUnattendX64FilePath')
$State.Controls.txtUnattendArm64FilePath = $window.FindName('txtUnattendArm64FilePath')
$State.Controls.btnBrowseUnattendArm64FilePath = $window.FindName('btnBrowseUnattendArm64FilePath')
$State.Controls.rbDeviceNamingNone = $window.FindName('rbDeviceNamingNone') $State.Controls.rbDeviceNamingNone = $window.FindName('rbDeviceNamingNone')
$State.Controls.rbDeviceNamingPrompt = $window.FindName('rbDeviceNamingPrompt') $State.Controls.rbDeviceNamingPrompt = $window.FindName('rbDeviceNamingPrompt')
$State.Controls.rbDeviceNamingTemplate = $window.FindName('rbDeviceNamingTemplate') $State.Controls.rbDeviceNamingTemplate = $window.FindName('rbDeviceNamingTemplate')
@@ -387,6 +391,8 @@ function Initialize-UIDefaults {
$State.Controls.chkOptimize.IsChecked = $State.Defaults.generalDefaults.Optimize $State.Controls.chkOptimize.IsChecked = $State.Defaults.generalDefaults.Optimize
$State.Controls.chkAllowVHDXCaching.IsChecked = $State.Defaults.generalDefaults.AllowVHDXCaching $State.Controls.chkAllowVHDXCaching.IsChecked = $State.Defaults.generalDefaults.AllowVHDXCaching
$State.Controls.chkInjectUnattend.IsChecked = $State.Defaults.generalDefaults.InjectUnattend $State.Controls.chkInjectUnattend.IsChecked = $State.Defaults.generalDefaults.InjectUnattend
$State.Controls.txtUnattendX64FilePath.Text = $State.Defaults.generalDefaults.UnattendX64FilePath
$State.Controls.txtUnattendArm64FilePath.Text = $State.Defaults.generalDefaults.UnattendArm64FilePath
$State.Controls.chkCreateDeploymentMedia.IsChecked = $State.Defaults.generalDefaults.CreateDeploymentMedia $State.Controls.chkCreateDeploymentMedia.IsChecked = $State.Defaults.generalDefaults.CreateDeploymentMedia
$State.Controls.chkAllowExternalHardDiskMedia.IsChecked = $State.Defaults.generalDefaults.AllowExternalHardDiskMedia $State.Controls.chkAllowExternalHardDiskMedia.IsChecked = $State.Defaults.generalDefaults.AllowExternalHardDiskMedia
$State.Controls.chkPromptExternalHardDiskMedia.IsChecked = $State.Defaults.generalDefaults.PromptExternalHardDiskMedia $State.Controls.chkPromptExternalHardDiskMedia.IsChecked = $State.Defaults.generalDefaults.PromptExternalHardDiskMedia
@@ -112,6 +112,8 @@ function Get-GeneralDefaults {
$userAppListPath = Join-Path -Path $appsPath -ChildPath "UserAppList.json" $userAppListPath = Join-Path -Path $appsPath -ChildPath "UserAppList.json"
$driversJsonPath = Join-Path -Path $driversPath -ChildPath "Drivers.json" $driversJsonPath = Join-Path -Path $driversPath -ChildPath "Drivers.json"
$deviceNamePrefixesPath = Join-Path -Path $unattendPath -ChildPath "prefixes.txt" $deviceNamePrefixesPath = Join-Path -Path $unattendPath -ChildPath "prefixes.txt"
$unattendX64FilePath = Join-Path -Path $unattendPath -ChildPath "unattend_x64.xml"
$unattendArm64FilePath = Join-Path -Path $unattendPath -ChildPath "unattend_arm64.xml"
return [PSCustomObject]@{ return [PSCustomObject]@{
# Build Tab Defaults # Build Tab Defaults
@@ -134,6 +136,8 @@ function Get-GeneralDefaults {
CopyUnattend = $false CopyUnattend = $false
CopyPPKG = $false CopyPPKG = $false
InjectUnattend = $false InjectUnattend = $false
UnattendX64FilePath = $unattendX64FilePath
UnattendArm64FilePath = $unattendArm64FilePath
DeviceNamingMode = 'None' DeviceNamingMode = 'None'
DeviceNameTemplate = '' DeviceNameTemplate = ''
DeviceNamePrefixesPath = $deviceNamePrefixesPath DeviceNamePrefixesPath = $deviceNamePrefixesPath
Binary file not shown.
+23 -11
View File
@@ -283,11 +283,23 @@ The deployment media is saved as an ISO file at `$FFUDevelopmentPath\WinPE_FFU_D
> >
> If you just need to re-create deployment media, you can use the `Create-PEMedia.ps1` script to regenerate the deploy ISO without running a full build. > If you just need to re-create deployment media, you can use the `Create-PEMedia.ps1` script to regenerate the deploy ISO without running a full build.
## Unattend.xml Options Expander
Use the **Unattend.xml Options** expander to choose how unattend content is staged and which source XML file FFU Builder should use for x64 and arm64 builds.
### x64 Unattend File Path
Use **x64 Unattend File Path** to browse to the source XML file for x64 builds. The default path is `.\FFUDevelopment\unattend\unattend_x64.xml`.
### arm64 Unattend File Path
Use **arm64 Unattend File Path** to browse to the source XML file for arm64 builds. The default path is `.\FFUDevelopment\unattend\unattend_arm64.xml`.
### Inject Unattend.xml ### Inject Unattend.xml
Controls the `-InjectUnattend` parameter. When checked, copies the architecture-specific unattend XML file from `.\FFUDevelopment\unattend` into the Apps ISO so it's baked into the FFU during the VM build process. The default is **unchecked**. Controls the `-InjectUnattend` parameter. When checked, stages the XML file selected for the current architecture in **Unattend.xml Options** into the Apps ISO so it's baked into the FFU during the VM build process. The default is **unchecked**.
This option is only available when **Install Apps** is checked. This option is used only when **Install Apps** is checked.
`Copy Unattend.xml` and `Inject Unattend.xml` are mutually exclusive. Select only one. `Copy Unattend.xml` and `Inject Unattend.xml` are mutually exclusive. Select only one.
@@ -295,18 +307,16 @@ This option is only available when **Install Apps** is checked.
When enabled, the build process: When enabled, the build process:
1. Determines the correct unattend file based on the target architecture: 1. Uses the x64 or arm64 source file selected in **Unattend.xml Options** for the current build architecture
* **unattend_x64.xml** for x64 builds
* **unattend_arm64.xml** for arm64 builds
2. Creates an `Unattend` folder inside `.\FFUDevelopment\Apps` if it doesn't exist 2. Creates an `Unattend` folder inside `.\FFUDevelopment\Apps` if it doesn't exist
3. Copies the architecture-specific unattend file to `.\FFUDevelopment\Apps\Unattend\Unattend.xml` 3. Copies that file to `.\FFUDevelopment\Apps\Unattend\Unattend.xml`
4. Includes the unattend file in the Apps ISO, making it available to sysprep during the VM build 4. Includes the unattend file in the Apps ISO, making it available to sysprep during the VM build
The unattend file is then used by sysprep during the specialize phase and/or other OOBE phases when the FFU is deployed. The unattend file is then used by sysprep during the specialize phase and/or other OOBE phases when the FFU is deployed.
#### Creating Your Unattend Files #### Creating Your Unattend Files
Modify the architecture-specific unattend file in the `.\FFUDevelopment\unattend` folder: You can keep the default architecture-specific files in the `.\FFUDevelopment\unattend` folder or browse to another XML file in the UI:
| File | Description | | File | Description |
| ---------------------------- | ----------------------------------- | | ---------------------------- | ----------------------------------- |
@@ -317,7 +327,7 @@ Modify the architecture-specific unattend file in the `.\FFUDevelopment\unattend
> Important > Important
> >
> Keep the file names with the architecture suffix (e.g., unattend_x64.xml). The script handles renaming the file to `Unattend.xml` when copying it to the Apps folder. > The default paths use the architecture suffix file names shown above. FFU Builder still renames the selected file to `Unattend.xml` when it stages it into the Apps folder.
#### When to Use This Option #### When to Use This Option
@@ -516,15 +526,17 @@ This leverages the Autopilot for existing devices json file. It's not recommende
### Copy Unattend.xml ### Copy Unattend.xml
Controls the `-CopyUnattend` parameter. When checked, copies the architecture-appropriate unattend XML file from `.\FFUDevelopment\Unattend` to an `Unattend` folder on the Deployment partition of the USB drive. The default is **unchecked**. Controls the `-CopyUnattend` parameter. When checked, stages the XML file selected for the current architecture in **Unattend.xml Options** to an `Unattend` folder on the Deployment partition of the USB drive. The default is **unchecked**.
This option is only available when **Build USB Drive** is checked. Use this option when you plan to build deployment USB media.
When enabled, the build process copies: When enabled, the build process copies:
- **unattend_x64.xml** (for x64 builds) or **unattend_arm64.xml** (for arm64 builds) → renamed to **Unattend.xml** on the USB drive - The selected x64 or arm64 unattend XML file → renamed to **Unattend.xml** on the USB drive
- **prefixes.txt** → created from the **Device Naming** prefixes list when that mode is selected - **prefixes.txt** → created from the **Device Naming** prefixes list when that mode is selected
If you keep the default file paths in place, FFU Builder uses `unattend_x64.xml` for x64 builds and `unattend_arm64.xml` for arm64 builds.
During deployment, `ApplyFFU.ps1` applies `Unattend.xml` whenever it is present. Device naming only happens when the **Device Naming** setting requires it, or when older media still uses the legacy prompt-based workflow. During deployment, `ApplyFFU.ps1` applies `Unattend.xml` whenever it is present. Device naming only happens when the **Device Naming** setting requires it, or when older media still uses the legacy prompt-based workflow.
See **Device Naming Expander** above for the available computer-name modes and prefixes-file behavior. See **Device Naming Expander** above for the available computer-name modes and prefixes-file behavior.
+4 -2
View File
@@ -38,12 +38,14 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
| -CopyDrivers | bool | Copy Drivers to USB drive | When set to $true, will copy the drivers from the $FFUDevelopmentPath\Drivers folder to the Drivers folder on the deploy partition of the USB drive. Default is $false. | | -CopyDrivers | bool | Copy Drivers to USB drive | When set to $true, will copy the drivers from the $FFUDevelopmentPath\Drivers folder to the Drivers folder on the deploy partition of the USB drive. Default is $false. |
| -CopyPEDrivers | bool | Copy PE Drivers | When set to $true, enables adding WinPE drivers. By default copies drivers from $FFUDevelopmentPath\PEDrivers to the WinPE deployment media unless -UseDriversAsPEDrivers is also $true. | | -CopyPEDrivers | bool | Copy PE Drivers | When set to $true, enables adding WinPE drivers. By default copies drivers from $FFUDevelopmentPath\PEDrivers to the WinPE deployment media unless -UseDriversAsPEDrivers is also $true. |
| -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. | | -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, will copy the $FFUDevelopmentPath\Unattend folder to the Deployment partition of the USB drive. Cannot be used together with -InjectUnattend. 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. | | -CreateDeploymentMedia | bool | Create Deployment Media | When set to $true, this will create WinPE deployment media for use when deploying to a physical device. |
| -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}. | | -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}. |
| -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, and Prefixes. The UI uses None, Prompt, Template, and Prefixes. Prompt rewrites the staged deployment unattend to the existing manual prompt placeholder and requires -CopyUnattend. | | -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, and Prefixes. The UI uses None, Prompt, Template, and Prefixes. Prompt rewrites the staged deployment unattend to the existing manual prompt placeholder and requires -CopyUnattend. |
| -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. | | -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. |
| -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. | | -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. |
| -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. |
| -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. |
| -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. | | -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. |
| -Disksize | uint64 | Disk Size (GB) | Size of the virtual hard disk for the virtual machine. Default is a 50GB dynamic disk. | | -Disksize | uint64 | 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. | | -DriversFolder | string | Drivers Folder | Path to the drivers folder. Default is $FFUDevelopmentPath\Drivers. |
@@ -54,7 +56,7 @@ This table lists all top-level parameters in BuildFFUVM.ps1.
| -FFUDevelopmentPath | string | FFU Development Path | Path to the FFU development folder. Default is $PSScriptRoot. | | -FFUDevelopmentPath | string | FFU Development Path | Path to the FFU development folder. Default is $PSScriptRoot. |
| -FFUPrefix | string | VM Name Prefix | Prefix for the generated FFU file. Default is _FFU. | | -FFUPrefix | string | VM Name Prefix | Prefix for the generated FFU file. Default is _FFU. |
| -Headers | hashtable | CLI only (no UI control) | Headers to use when downloading files. Not recommended to modify. | | -Headers | hashtable | CLI only (no UI control) | Headers to use when downloading files. Not recommended to modify. |
| -InjectUnattend | bool | Inject Unattend.xml | When set to $true and InstallApps is also $true, copies unattend_[arch].xml from $FFUDevelopmentPath\unattend to $FFUDevelopmentPath\Apps\Unattend\Unattend.xml so sysprep can use it inside the VM. Cannot be used together with -CopyUnattend. Default is $false. | | -InjectUnattend | bool | Inject Unattend.xml | When set to $true and InstallApps is also $true, stages the selected architecture-specific unattend XML file to $FFUDevelopmentPath\Apps\Unattend\Unattend.xml so sysprep can use it inside the VM. Cannot be used together with -CopyUnattend. Default is $false. |
| -InstallApps | bool | Install Applications | When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.iso, install the apps, sysprep, and capture the VM. When set to $false, the FFU is created from a VHDX file, and no VM is created. | | -InstallApps | bool | Install Applications | When set to $true, the script will create an Apps.iso file from the $FFUDevelopmentPath\Apps folder. It will also create a VM, mount the Apps.iso, install the apps, sysprep, and capture the VM. When set to $false, the FFU is created from a VHDX file, and no VM is created. |
| -InstallDrivers | bool | Install Drivers to FFU | Install device drivers from the specified $FFUDevelopmentPath\Drivers folder if set to $true. Download the drivers and put them in the Drivers folder. The script will recurse the drivers folder and add the drivers to the FFU. | | -InstallDrivers | bool | Install Drivers to FFU | Install device drivers from the specified $FFUDevelopmentPath\Drivers folder if set to $true. Download the drivers and put them in the Drivers folder. The script will recurse the drivers folder and add the drivers to the FFU. |
| -InstallOffice | bool | Install Office | Install Microsoft Office if set to $true. The script will download the latest ODT and Office files in the $FFUDevelopmentPath\Apps\Office folder and install Office in the FFU via VM. | | -InstallOffice | bool | Install Office | Install Microsoft Office if set to $true. The script will download the latest ODT and Office files in the $FFUDevelopmentPath\Apps\Office folder and install Office in the FFU via VM. |