Add flexible device naming options to Unattend delivery

Introduces new parameters and UI controls to give users more choice over device naming when applying an Unattend.xml file.
Users can now specify a device name, use a static or template-based name with the `%serial%` variable, or continue using a list of prefixes.
The UI is updated with a new Device Naming expander to guide the user through the options and clearly indicate the requirements for each mode, ensuring that mutually exclusive options like Copy Unattend and Inject Unattend are not selected together.
Documentation is updated to reflect the new functionality.
This commit is contained in:
rbalsleyMSFT
2026-04-07 10:48:34 -07:00
parent 78212f06d7
commit 4a2d8e63ea
13 changed files with 764 additions and 61 deletions
+213 -11
View File
@@ -72,6 +72,18 @@ When set to $true, will copy the provisioning package from the $FFUDevelopmentPa
.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, will copy the $FFUDevelopmentPath\Unattend folder to the Deployment partition of the USB drive. Default is $false.
.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, Template, and Prefixes.
.PARAMETER DeviceNameTemplate
Sets the device name used when DeviceNamingMode is Template. Supports a static name or the %serial% token when CopyUnattend is used.
.PARAMETER DeviceNamePrefixes
Sets the prefixes used when DeviceNamingMode is Prefixes. Each entry becomes a line in prefixes.txt on the deployment media.
.PARAMETER DeviceNamePrefixesPath
Path to the source prefixes file used for legacy copy or when DeviceNamePrefixes is not supplied. Default is $FFUDevelopmentPath\Unattend\prefixes.txt.
.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.
@@ -407,6 +419,11 @@ param(
[bool]$AllowVHDXCaching, [bool]$AllowVHDXCaching,
[bool]$CopyPPKG, [bool]$CopyPPKG,
[bool]$CopyUnattend, [bool]$CopyUnattend,
[ValidateSet('Legacy', 'None', 'Template', 'Prefixes')]
[string]$DeviceNamingMode = 'Legacy',
[string]$DeviceNameTemplate,
[string[]]$DeviceNamePrefixes,
[string]$DeviceNamePrefixesPath,
[bool]$CopyAutopilot, [bool]$CopyAutopilot,
[bool]$CompactOS = $true, [bool]$CompactOS = $true,
[bool]$CleanupDeployISO = $true, [bool]$CleanupDeployISO = $true,
@@ -505,6 +522,79 @@ if ($ConfigFile -and (Test-Path -Path $ConfigFile)) {
} }
} }
function Get-UnattendSourcePath {
param(
[Parameter(Mandatory = $true)]
[string]$UnattendFolder,
[Parameter(Mandatory = $true)]
[string]$WindowsArch
)
$archSuffix = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
return Join-Path $UnattendFolder "unattend_$archSuffix.xml"
}
function Test-UnattendHasComputerNameElement {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
[xml]$unattendXml = Get-Content -Path $Path
foreach ($component in $unattendXml.unattend.settings.component) {
if ($component.ComputerName) {
return $true
}
}
return $false
}
function Save-StagedUnattendFile {
param(
[Parameter(Mandatory = $true)]
[string]$SourcePath,
[Parameter(Mandatory = $true)]
[string]$DestinationPath,
[Parameter(Mandatory = $true)]
[ValidateSet('Legacy', 'None', 'Template', 'Prefixes')]
[string]$DeviceNamingMode,
[string]$DeviceNameTemplate
)
if ($DeviceNamingMode -in @('Legacy', 'Prefixes')) {
Copy-Item -Path $SourcePath -Destination $DestinationPath -Force | Out-Null
return
}
[xml]$unattendXml = Get-Content -Path $SourcePath
$computerNameComponent = $null
foreach ($component in $unattendXml.unattend.settings.component) {
if ($component.ComputerName) {
$computerNameComponent = $component
break
}
}
if ($null -eq $computerNameComponent) {
if ($DeviceNamingMode -eq 'None') {
Copy-Item -Path $SourcePath -Destination $DestinationPath -Force | Out-Null
return
}
throw "ComputerName element not found in unattend source file: $SourcePath"
}
if ($DeviceNamingMode -eq 'None') {
$computerNameComponent.ComputerName = '*'
}
elseif ($DeviceNamingMode -eq 'Template') {
$computerNameComponent.ComputerName = $DeviceNameTemplate
}
$unattendXml.Save($DestinationPath)
}
$vmSwitchWasExplicitlyBound = $PSBoundParameters.ContainsKey('VMSwitchName') $vmSwitchWasExplicitlyBound = $PSBoundParameters.ContainsKey('VMSwitchName')
$enableVmNetworkingWasExplicitlyBound = $PSBoundParameters.ContainsKey('EnableVMNetworking') $enableVmNetworkingWasExplicitlyBound = $PSBoundParameters.ContainsKey('EnableVMNetworking')
if (-not $EnableVMNetworking -and $vmSwitchWasExplicitlyBound -and -not $enableVmNetworkingWasExplicitlyBound) { if (-not $EnableVMNetworking -and $vmSwitchWasExplicitlyBound -and -not $enableVmNetworkingWasExplicitlyBound) {
@@ -512,6 +602,52 @@ if (-not $EnableVMNetworking -and $vmSwitchWasExplicitlyBound -and -not $enableV
WriteLog 'EnableVMNetworking not explicitly set. Enabling VM networking because -VMSwitchName was supplied on the command line.' WriteLog 'EnableVMNetworking not explicitly set. Enabling VM networking because -VMSwitchName was supplied on the command line.'
} }
$normalizedDeviceNameTemplate = if ($null -ne $DeviceNameTemplate) { $DeviceNameTemplate.Trim() } else { $null }
$effectiveDeviceNamePrefixes = @($DeviceNamePrefixes | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim() })
$resolvedDeviceNamePrefixesPath = if ([string]::IsNullOrWhiteSpace($DeviceNamePrefixesPath)) {
Join-Path (Join-Path $FFUDevelopmentPath 'Unattend') 'prefixes.txt'
}
else {
$DeviceNamePrefixesPath
}
if (($DeviceNamingMode -eq 'Prefixes') -and ($effectiveDeviceNamePrefixes.Count -eq 0) -and (Test-Path -Path $resolvedDeviceNamePrefixesPath -PathType Leaf)) {
$effectiveDeviceNamePrefixes = @(Get-Content -Path $resolvedDeviceNamePrefixesPath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim() })
WriteLog "Loaded device name prefixes from $resolvedDeviceNamePrefixesPath"
}
if ($CopyUnattend -and $InjectUnattend) {
throw 'CopyUnattend and InjectUnattend cannot both be set to `$true. Select only one unattend delivery method.'
}
if ($DeviceNamingMode -eq 'Template') {
if ([string]::IsNullOrWhiteSpace($normalizedDeviceNameTemplate)) {
throw 'DeviceNamingMode Template requires DeviceNameTemplate.'
}
$templateWithoutSupportedVariables = $normalizedDeviceNameTemplate -replace '(?i)%serial%', ''
if ($templateWithoutSupportedVariables -match '%') {
throw 'Only the %serial% device name variable is supported.'
}
if (-not ($CopyUnattend -or $InjectUnattend)) {
throw 'DeviceNamingMode Template requires either CopyUnattend or InjectUnattend.'
}
if ($InjectUnattend -and (-not $CopyUnattend) -and $normalizedDeviceNameTemplate -match '(?i)%serial%') {
throw 'The %serial% device name variable is only supported when CopyUnattend is used.'
}
}
elseif ($DeviceNamingMode -eq 'Prefixes') {
if (-not $CopyUnattend) {
throw 'DeviceNamingMode Prefixes requires CopyUnattend. Prefix-based naming is not supported with InjectUnattend.'
}
if ($effectiveDeviceNamePrefixes.Count -eq 0) {
throw 'DeviceNamingMode Prefixes requires at least one DeviceNamePrefixes entry or a valid DeviceNamePrefixesPath.'
}
}
# Validate that the selected Windows SKU is compatible with the chosen Windows release and ensure an ISO is provided for unsupported releases # Validate that the selected Windows SKU is compatible with the chosen Windows release and ensure an ISO is provided for unsupported releases
$clientSKUs = @( $clientSKUs = @(
'Home', 'Home',
@@ -4184,6 +4320,57 @@ Function New-DeploymentUSB {
Import-Module "$($using:PSScriptRoot)\FFU.Common" -Force Import-Module "$($using:PSScriptRoot)\FFU.Common" -Force
Set-CommonCoreLogPath -Path $using:LogFile Set-CommonCoreLogPath -Path $using:LogFile
function Get-LocalUnattendSourcePath {
param(
[string]$UnattendFolder,
[string]$WindowsArch
)
$archSuffix = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
return Join-Path $UnattendFolder "unattend_$archSuffix.xml"
}
function Save-LocalStagedUnattendFile {
param(
[string]$SourcePath,
[string]$DestinationPath,
[string]$DeviceNamingMode,
[string]$DeviceNameTemplate
)
if ($DeviceNamingMode -in @('Legacy', 'Prefixes')) {
Copy-Item -Path $SourcePath -Destination $DestinationPath -Force | Out-Null
return
}
[xml]$unattendXml = Get-Content -Path $SourcePath
$computerNameComponent = $null
foreach ($component in $unattendXml.unattend.settings.component) {
if ($component.ComputerName) {
$computerNameComponent = $component
break
}
}
if ($null -eq $computerNameComponent) {
if ($DeviceNamingMode -eq 'None') {
Copy-Item -Path $SourcePath -Destination $DestinationPath -Force | Out-Null
return
}
throw "ComputerName element not found in unattend source file: $SourcePath"
}
if ($DeviceNamingMode -eq 'None') {
$computerNameComponent.ComputerName = '*'
}
elseif ($DeviceNamingMode -eq 'Template') {
$computerNameComponent.ComputerName = $DeviceNameTemplate
}
$unattendXml.Save($DestinationPath)
}
$DiskNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "") $DiskNumber = $USBDrive.DeviceID.Replace("\\.\PHYSICALDRIVE", "")
WriteLog "Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId) processing DiskNumber $DiskNumber ($($USBDrive.Model))" WriteLog "Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId) processing DiskNumber $DiskNumber ($($USBDrive.Model))"
@@ -4244,15 +4431,15 @@ 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
if ($using:WindowsArch -eq 'x64') { $unattendSource = Get-LocalUnattendSourcePath -UnattendFolder $using:UnattendFolder -WindowsArch $using:WindowsArch
Copy-Item -Path (Join-Path $using:UnattendFolder 'unattend_x64.xml') -Destination (Join-Path $UnattendPathOnUSB 'Unattend.xml') -Force | Out-Null Save-LocalStagedUnattendFile -SourcePath $unattendSource -DestinationPath (Join-Path $UnattendPathOnUSB 'Unattend.xml') -DeviceNamingMode $using:DeviceNamingMode -DeviceNameTemplate $using:normalizedDeviceNameTemplate
if ($using:DeviceNamingMode -eq 'Prefixes') {
WriteLog "Writing prefixes.txt file to $UnattendPathOnUSB"
$using:effectiveDeviceNamePrefixes | Set-Content -Path (Join-Path $UnattendPathOnUSB 'prefixes.txt') -Encoding UTF8
} }
elseif ($using:WindowsArch -eq 'arm64') { elseif (($using:DeviceNamingMode -eq 'Legacy') -and (Test-Path -Path $using:resolvedDeviceNamePrefixesPath -PathType Leaf)) {
Copy-Item -Path (Join-Path $using:UnattendFolder 'unattend_arm64.xml') -Destination (Join-Path $UnattendPathOnUSB 'Unattend.xml') -Force | Out-Null
}
if (Test-Path (Join-Path $using:UnattendFolder 'prefixes.txt')) {
WriteLog "Copying prefixes.txt file to $UnattendPathOnUSB" WriteLog "Copying prefixes.txt file to $UnattendPathOnUSB"
Copy-Item -Path (Join-Path $using:UnattendFolder 'prefixes.txt') -Destination (Join-Path $UnattendPathOnUSB 'prefixes.txt') -Force | Out-Null Copy-Item -Path $using:resolvedDeviceNamePrefixesPath -Destination (Join-Path $UnattendPathOnUSB 'prefixes.txt') -Force | Out-Null
} }
WriteLog 'Copy completed' WriteLog 'Copy completed'
} }
@@ -5518,9 +5705,26 @@ if ($CopyUnattend) {
WriteLog "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file" WriteLog "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file"
throw "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file" throw "-CopyUnattend is set to `$true, but the $UnattendFolder folder is missing a .XML file"
} }
if ($DeviceNamingMode -eq 'Prefixes') {
$unattendSourcePath = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch
if (-not (Test-UnattendHasComputerNameElement -Path $unattendSourcePath)) {
throw "DeviceNamingMode Prefixes requires a ComputerName element in $unattendSourcePath"
}
}
WriteLog 'Unattend validation complete' WriteLog 'Unattend validation complete'
} }
if ($InjectUnattend -and $DeviceNamingMode -eq 'Template') {
$injectUnattendSourcePath = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch
if (Test-Path -Path $injectUnattendSourcePath -PathType Leaf) {
if (-not (Test-UnattendHasComputerNameElement -Path $injectUnattendSourcePath)) {
throw "DeviceNamingMode Template requires a ComputerName element in $injectUnattendSourcePath"
}
}
}
#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.
@@ -6418,9 +6622,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) {
# Determine source unattend.xml based on architecture $unattendSource = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch
$archSuffix = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
$unattendSource = Join-Path $UnattendFolder "unattend_$archSuffix.xml"
# Ensure target folder exists under Apps # Ensure target folder exists under Apps
$targetFolder = Join-Path $AppsPath 'Unattend' $targetFolder = Join-Path $AppsPath 'Unattend'
@@ -6431,7 +6633,7 @@ if ($InstallApps) {
# Copy if source exists; otherwise log and skip # Copy if source exists; otherwise log and skip
if (Test-Path -Path $unattendSource -PathType Leaf) { if (Test-Path -Path $unattendSource -PathType Leaf) {
$destination = Join-Path $targetFolder 'Unattend.xml' $destination = Join-Path $targetFolder 'Unattend.xml'
Copy-Item -Path $unattendSource -Destination $destination -Force | Out-Null Save-StagedUnattendFile -SourcePath $unattendSource -DestinationPath $destination -DeviceNamingMode $DeviceNamingMode -DeviceNameTemplate $normalizedDeviceNameTemplate
WriteLog "Injected unattend file into Apps: $unattendSource -> $destination" WriteLog "Injected unattend file into Apps: $unattendSource -> $destination"
} }
else { else {
+54
View File
@@ -432,6 +432,60 @@ $script:uiState.Controls.btnRun.Add_Click({
return return
} }
if ($config.CopyUnattend -and $config.InjectUnattend) {
[System.Windows.MessageBox]::Show("Copy Unattend.xml and Inject Unattend.xml cannot both be selected. Choose only one unattend delivery method.", "Unattend Selection Required", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: choose only one unattend delivery method."
return
}
if ($config.DeviceNamingMode -eq 'Template') {
if ([string]::IsNullOrWhiteSpace([string]$config.DeviceNameTemplate)) {
[System.Windows.MessageBox]::Show("Specify a device name before using 'Specify Device Name'.", "Device Name Required", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: device name required."
return
}
if (-not ($config.CopyUnattend -or $config.InjectUnattend)) {
[System.Windows.MessageBox]::Show("Select Copy Unattend.xml or Inject Unattend.xml before using 'Specify Device Name'.", "Unattend Selection Required", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: unattend delivery method required for device naming."
return
}
$templateWithoutSupportedVariables = ([string]$config.DeviceNameTemplate) -replace '(?i)%serial%', ''
if ($templateWithoutSupportedVariables -match '%') {
[System.Windows.MessageBox]::Show("Only the %serial% device name variable is supported.", "Unsupported Device Name Variable", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: unsupported device name variable."
return
}
if ($config.InjectUnattend -and (-not $config.CopyUnattend) -and ([string]$config.DeviceNameTemplate -match '(?i)%serial%')) {
[System.Windows.MessageBox]::Show("The %serial% device name variable is only supported when Copy Unattend.xml is selected.", "Unsupported Inject Unattend Setting", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: %serial% requires Copy Unattend.xml."
return
}
}
elseif ($config.DeviceNamingMode -eq 'Prefixes') {
if (-not $config.CopyUnattend) {
[System.Windows.MessageBox]::Show("Select Copy Unattend.xml before using 'Specify a list of Prefixes'.", "Copy Unattend Required", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: prefixes require Copy Unattend.xml."
return
}
$hasSavedPrefixesPath = -not [string]::IsNullOrWhiteSpace([string]$config.DeviceNamePrefixesPath) -and (Test-Path -Path $config.DeviceNamePrefixesPath -PathType Leaf)
if ((($null -eq $config.DeviceNamePrefixes) -or ($config.DeviceNamePrefixes.Count -eq 0)) -and -not $hasSavedPrefixesPath) {
[System.Windows.MessageBox]::Show("Enter at least one prefix or choose a valid prefixes file before using 'Specify a list of Prefixes'.", "Prefixes Required", "OK", "Warning") | Out-Null
$btnRun.IsEnabled = $true
$script:uiState.Controls.txtStatus.Text = "Build canceled: prefixes required."
return
}
}
$configFilePath = Join-Path $config.FFUDevelopmentPath "\config\FFUConfig.json" $configFilePath = Join-Path $config.FFUDevelopmentPath "\config\FFUConfig.json"
# Sort top-level keys alphabetically for consistent output # Sort top-level keys alphabetically for consistent output
$sortedConfig = [ordered]@{} $sortedConfig = [ordered]@{}
+40 -6
View File
@@ -836,9 +836,11 @@
<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: Build USB Drive Section --> <!-- Row 10: Device Naming -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<!-- Row 11: Post-Build Cleanup --> <!-- Row 11: Build USB Drive Section -->
<RowDefinition Height="Auto"/>
<!-- Row 12: Post-Build Cleanup -->
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -903,8 +905,40 @@
</StackPanel> </StackPanel>
</Expander> </Expander>
<!-- Row 10: Build USB Drive Section --> <!-- Row 10: Device Naming -->
<Expander Grid.Row="10" x:Name="usbDriveSection" Header="Build USB Drive Options" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"> <Expander Grid.Row="10" x:Name="deviceNamingSection" Header="Device Naming" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<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"/>
<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="rbDeviceNamingTemplate" Content="Specify Device Name" GroupName="DeviceNamingMode" Margin="0,0,0,8" ToolTip="Use a static device name or the %serial% variable when Copy Unattend.xml is selected."/>
<StackPanel x:Name="deviceNameTemplatePanel" Margin="32,0,0,16" Visibility="Collapsed">
<TextBlock Text="Use static text, %serial%, or both together, for example Comp-%serial%." Margin="0,0,0,4" TextWrapping="Wrap"/>
<TextBlock Text="Choose Copy Unattend.xml or Inject Unattend.xml below for static names. %serial% requires Copy Unattend.xml." Margin="0,0,0,8" TextWrapping="Wrap" Opacity="0.75"/>
<TextBox x:Name="txtDeviceNameTemplate" VerticalAlignment="Center" ToolTip="Examples: KIOSK-01 or Comp-%serial%"/>
</StackPanel>
<RadioButton x:Name="rbDeviceNamingPrefixes" Content="Specify a list of Prefixes" GroupName="DeviceNamingMode" Margin="0,0,0,8" ToolTip="Enter one prefix per line or import an existing prefixes file. This option requires Copy Unattend.xml."/>
<StackPanel x:Name="deviceNamePrefixesPanel" Margin="32,0,0,0" Visibility="Collapsed">
<TextBlock Text="Prefixes File Path" Margin="0,0,0,8" TextWrapping="Wrap"/>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtDeviceNamePrefixesPath" Grid.Column="0" VerticalAlignment="Center" ToolTip="Path to the prefixes source file. You can use any file name."/>
<Button x:Name="btnBrowseDeviceNamePrefixesPath" Grid.Column="1" Content="Browse..." Padding="12,4" Margin="8,0,0,0" VerticalAlignment="Center" ToolTip="Browse to a prefixes source file path."/>
</Grid>
<TextBlock Text="Enter one prefix per line. Each prefix is combined with the device serial number during deployment." Margin="0,0,0,4" TextWrapping="Wrap"/>
<TextBlock Text="If you enter a single prefix, it is used automatically. If you enter multiple prefixes, the technician is prompted to choose one during deployment." Margin="0,0,0,8" TextWrapping="Wrap" Opacity="0.75"/>
<TextBox x:Name="txtDeviceNamePrefixes" MinHeight="120" AcceptsReturn="True" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" ToolTip="Each line becomes a prefix option in prefixes.txt."/>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0" HorizontalAlignment="Left">
<Button x:Name="btnSaveDeviceNamePrefixes" Content="Save Prefixes" Padding="12,4" ToolTip="Save the current prefixes list to the Prefixes File Path."/>
</StackPanel>
</StackPanel>
</StackPanel>
</Expander>
<!-- Row 11: 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">
<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."/>
@@ -969,8 +1003,8 @@
</StackPanel> </StackPanel>
</Expander> </Expander>
<!-- Row 11: Post-Build Cleanup --> <!-- Row 12: Post-Build Cleanup -->
<Expander Grid.Row="11" Header="Post-Build Cleanup" IsExpanded="False" Margin="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"> <Expander Grid.Row="12" 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."/>
@@ -36,6 +36,10 @@ function Get-UIConfig {
UseDriversAsPEDrivers = $State.Controls.chkUseDriversAsPEDrivers.IsChecked UseDriversAsPEDrivers = $State.Controls.chkUseDriversAsPEDrivers.IsChecked
CopyPPKG = $State.Controls.chkCopyPPKG.IsChecked CopyPPKG = $State.Controls.chkCopyPPKG.IsChecked
CopyUnattend = $State.Controls.chkCopyUnattend.IsChecked CopyUnattend = $State.Controls.chkCopyUnattend.IsChecked
DeviceNamingMode = Get-SelectedDeviceNamingMode -State $State
DeviceNameTemplate = $State.Controls.txtDeviceNameTemplate.Text
DeviceNamePrefixesPath = $State.Controls.txtDeviceNamePrefixesPath.Text
DeviceNamePrefixes = @(Get-DeviceNamePrefixes -State $State)
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
@@ -456,6 +460,24 @@ function Update-UIFromConfig {
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
Set-UIValue -ControlName 'chkCopyPPKG' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyPPKG' -State $State Set-UIValue -ControlName 'chkCopyPPKG' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyPPKG' -State $State
Set-UIValue -ControlName 'txtDeviceNamePrefixesPath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'DeviceNamePrefixesPath' -State $State
Set-UIValue -ControlName 'txtDeviceNameTemplate' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'DeviceNameTemplate' -State $State
Set-UIValue -ControlName 'txtDeviceNamePrefixes' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'DeviceNamePrefixes' -TransformValue { param($val) if ($val -is [System.Array]) { $val -join [System.Environment]::NewLine } else { [string]$val } } -State $State
if ([string]::IsNullOrWhiteSpace($State.Controls.txtDeviceNamePrefixesPath.Text)) {
$State.Controls.txtDeviceNamePrefixesPath.Text = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $State.Controls.txtFFUDevPath.Text
}
$deviceNamingMode = 'None'
if ($ConfigContent.PSObject.Properties.Name -contains 'DeviceNamingMode') {
$deviceNamingMode = [string]$ConfigContent.DeviceNamingMode
}
if ($deviceNamingMode -notin @('None', 'Template', 'Prefixes')) {
$deviceNamingMode = 'None'
}
Set-DeviceNamingMode -State $State -Mode $deviceNamingMode
Import-DeviceNamePrefixesFromConfiguredPath -State $State
Update-DeviceNamingControls -State $State
# Post Build Cleanup group (Build Tab) # Post Build Cleanup group (Build Tab)
Set-UIValue -ControlName 'chkCleanupAppsISO' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CleanupAppsISO' -State $State Set-UIValue -ControlName 'chkCleanupAppsISO' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CleanupAppsISO' -State $State
@@ -27,6 +27,172 @@ function Update-VMNetworkingControls {
} }
} }
function Get-SelectedDeviceNamingMode {
param([PSCustomObject]$State)
if ($true -eq $State.Controls.rbDeviceNamingTemplate.IsChecked) {
return 'Template'
}
if ($true -eq $State.Controls.rbDeviceNamingPrefixes.IsChecked) {
return 'Prefixes'
}
return 'None'
}
function Set-DeviceNamingMode {
param(
[PSCustomObject]$State,
[ValidateSet('None', 'Template', 'Prefixes')]
[string]$Mode
)
$State.Controls.rbDeviceNamingNone.IsChecked = $Mode -eq 'None'
$State.Controls.rbDeviceNamingTemplate.IsChecked = $Mode -eq 'Template'
$State.Controls.rbDeviceNamingPrefixes.IsChecked = $Mode -eq 'Prefixes'
}
function Get-DeviceNamePrefixes {
param([PSCustomObject]$State)
if ($null -eq $State.Controls.txtDeviceNamePrefixes) {
return @()
}
return @(
$State.Controls.txtDeviceNamePrefixes.Text -split "\r?\n" |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) } |
ForEach-Object { $_.Trim() }
)
}
function Import-DeviceNamePrefixesFile {
param(
[PSCustomObject]$State,
[string]$FilePath
)
if ([string]::IsNullOrWhiteSpace($FilePath) -or -not (Test-Path -Path $FilePath -PathType Leaf)) {
return $false
}
$prefixLines = @(Get-Content -Path $FilePath | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { $_.Trim() })
if ($null -ne $State.Controls.txtDeviceNamePrefixesPath) {
$State.Controls.txtDeviceNamePrefixesPath.Text = $FilePath
}
$State.Controls.txtDeviceNamePrefixes.Text = $prefixLines -join [System.Environment]::NewLine
WriteLog "Imported device name prefixes from $FilePath"
return $true
}
function Get-DefaultDeviceNamePrefixesPath {
param([string]$FFUDevelopmentPath)
if ([string]::IsNullOrWhiteSpace($FFUDevelopmentPath)) {
return $null
}
return Join-Path (Join-Path $FFUDevelopmentPath 'unattend') 'prefixes.txt'
}
function Import-DeviceNamePrefixesFromConfiguredPath {
param(
[PSCustomObject]$State,
[switch]$SkipIfTextPresent
)
if ($SkipIfTextPresent -and -not [string]::IsNullOrWhiteSpace($State.Controls.txtDeviceNamePrefixes.Text)) {
return
}
$prefixFilePath = $State.Controls.txtDeviceNamePrefixesPath.Text
if ([string]::IsNullOrWhiteSpace($prefixFilePath)) {
$prefixFilePath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $State.Controls.txtFFUDevPath.Text
if (-not [string]::IsNullOrWhiteSpace($prefixFilePath) -and $null -ne $State.Controls.txtDeviceNamePrefixesPath) {
$State.Controls.txtDeviceNamePrefixesPath.Text = $prefixFilePath
}
}
if (Test-Path -Path $prefixFilePath -PathType Leaf) {
Import-DeviceNamePrefixesFile -State $State -FilePath $prefixFilePath | Out-Null
}
}
function Test-DeviceNameTemplateUsesSerialToken {
param([PSCustomObject]$State)
return ((Get-SelectedDeviceNamingMode -State $State) -eq 'Template') -and ($State.Controls.txtDeviceNameTemplate.Text -match '(?i)%serial%')
}
function Update-UnattendSelectionControls {
param([PSCustomObject]$State)
$selectedDeviceNamingMode = Get-SelectedDeviceNamingMode -State $State
$isCopyUnattendSelected = $true -eq $State.Controls.chkCopyUnattend.IsChecked
$isInjectUnattendSelected = $true -eq $State.Controls.chkInjectUnattend.IsChecked
$deviceNameTemplateUsesSerialToken = Test-DeviceNameTemplateUsesSerialToken -State $State
if ($isCopyUnattendSelected -and $isInjectUnattendSelected) {
if (($selectedDeviceNamingMode -eq 'Prefixes') -or $deviceNameTemplateUsesSerialToken) {
$State.Controls.chkInjectUnattend.IsChecked = $false
$isInjectUnattendSelected = $false
}
else {
$State.Controls.chkCopyUnattend.IsChecked = $false
$isCopyUnattendSelected = $false
}
}
if (($selectedDeviceNamingMode -eq 'Prefixes') -or $deviceNameTemplateUsesSerialToken) {
if (-not $isCopyUnattendSelected) {
$State.Controls.chkCopyUnattend.IsChecked = $true
$isCopyUnattendSelected = $true
}
if ($isInjectUnattendSelected) {
$State.Controls.chkInjectUnattend.IsChecked = $false
$isInjectUnattendSelected = $false
}
$State.Controls.chkCopyUnattend.IsEnabled = $false
$State.Controls.chkInjectUnattend.IsEnabled = $false
return
}
if ($isCopyUnattendSelected) {
$State.Controls.chkCopyUnattend.IsEnabled = $true
$State.Controls.chkInjectUnattend.IsEnabled = $false
}
elseif ($isInjectUnattendSelected) {
$State.Controls.chkCopyUnattend.IsEnabled = $false
$State.Controls.chkInjectUnattend.IsEnabled = $true
}
else {
$State.Controls.chkCopyUnattend.IsEnabled = $true
$State.Controls.chkInjectUnattend.IsEnabled = $true
}
}
function Update-DeviceNamingControls {
param([PSCustomObject]$State)
if (($true -eq $State.Controls.chkInjectUnattend.IsChecked) -and ($true -eq $State.Controls.rbDeviceNamingPrefixes.IsChecked)) {
$State.Controls.rbDeviceNamingNone.IsChecked = $true
}
$selectedDeviceNamingMode = Get-SelectedDeviceNamingMode -State $State
$State.Controls.deviceNameTemplatePanel.Visibility = if ($selectedDeviceNamingMode -eq 'Template') { 'Visible' } else { 'Collapsed' }
$State.Controls.deviceNamePrefixesPanel.Visibility = if ($selectedDeviceNamingMode -eq 'Prefixes') { 'Visible' } else { 'Collapsed' }
$State.Controls.rbDeviceNamingPrefixes.IsEnabled = -not ($true -eq $State.Controls.chkInjectUnattend.IsChecked)
if ($selectedDeviceNamingMode -eq 'Prefixes') {
Import-DeviceNamePrefixesFromConfiguredPath -State $State -SkipIfTextPresent
}
Update-UnattendSelectionControls -State $State
}
function Register-EventHandlers { function Register-EventHandlers {
param([PSCustomObject]$State) param([PSCustomObject]$State)
WriteLog "Registering UI event handlers..." WriteLog "Registering UI event handlers..."
@@ -242,7 +408,15 @@ function Register-EventHandlers {
$localState = $window.Tag $localState = $window.Tag
$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
$previousDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text
$localState.Controls.txtFFUDevPath.Text = $selectedPath $localState.Controls.txtFFUDevPath.Text = $selectedPath
$newDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $selectedPath
if ([string]::IsNullOrWhiteSpace($currentPrefixesPath) -or $currentPrefixesPath -ieq $previousDefaultPrefixesPath) {
$localState.Controls.txtDeviceNamePrefixesPath.Text = $newDefaultPrefixesPath
}
Import-DeviceNamePrefixesFromConfiguredPath -State $localState
Update-DeviceNamingControls -State $localState
} }
}) })
@@ -256,6 +430,106 @@ function Register-EventHandlers {
} }
}) })
$State.Controls.rbDeviceNamingNone.Add_Checked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
Update-DeviceNamingControls -State $window.Tag
})
$State.Controls.rbDeviceNamingTemplate.Add_Checked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
Update-DeviceNamingControls -State $window.Tag
})
$State.Controls.txtDeviceNameTemplate.Add_TextChanged({
param($eventSource, $textChangedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
if ($null -ne $window -and $null -ne $window.Tag) {
Update-DeviceNamingControls -State $window.Tag
}
})
$State.Controls.rbDeviceNamingPrefixes.Add_Checked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
Update-DeviceNamingControls -State $window.Tag
})
$State.Controls.btnBrowseDeviceNamePrefixesPath.Add_Click({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
$localState = $window.Tag
$currentPrefixesPath = $localState.Controls.txtDeviceNamePrefixesPath.Text
if ([string]::IsNullOrWhiteSpace($currentPrefixesPath)) {
$currentPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text
}
$initialDirectory = if ([string]::IsNullOrWhiteSpace($currentPrefixesPath)) {
$null
}
else {
Split-Path $currentPrefixesPath -Parent
}
$fileName = if ([string]::IsNullOrWhiteSpace($currentPrefixesPath)) { 'prefixes.txt' } else { Split-Path $currentPrefixesPath -Leaf }
$selectedPath = Invoke-BrowseAction -Type 'OpenFile' -Title 'Select prefixes file path' -Filter 'Text files (*.txt)|*.txt|All files (*.*)|*.*' -InitialDirectory $initialDirectory -FileName $fileName
if (Import-DeviceNamePrefixesFile -State $localState -FilePath $selectedPath) {
Update-DeviceNamingControls -State $localState
}
})
$State.Controls.btnSaveDeviceNamePrefixes.Add_Click({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
$localState = $window.Tag
$prefixLines = @(Get-DeviceNamePrefixes -State $localState)
if ($prefixLines.Count -eq 0) {
[System.Windows.MessageBox]::Show("Enter at least one prefix before saving the prefixes file.", "Prefixes Required", "OK", "Warning") | Out-Null
return
}
$currentPrefixesPath = $localState.Controls.txtDeviceNamePrefixesPath.Text
if ([string]::IsNullOrWhiteSpace($currentPrefixesPath)) {
$currentPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text
if (-not [string]::IsNullOrWhiteSpace($currentPrefixesPath)) {
$localState.Controls.txtDeviceNamePrefixesPath.Text = $currentPrefixesPath
}
}
if ([string]::IsNullOrWhiteSpace($currentPrefixesPath)) {
[System.Windows.MessageBox]::Show("Select a valid Prefixes File Path before saving prefixes.", "Prefixes File Path Required", "OK", "Warning") | Out-Null
return
}
try {
$prefixLines | Set-Content -Path $currentPrefixesPath -Encoding UTF8
$localState.Controls.txtDeviceNamePrefixesPath.Text = $currentPrefixesPath
WriteLog "Saved device name prefixes to $currentPrefixesPath"
}
catch {
[System.Windows.MessageBox]::Show("Saving prefixes failed for '$currentPrefixesPath'. $($_.Exception.Message)", "Save Prefixes Failed", "OK", "Error") | Out-Null
}
})
$State.Controls.chkCopyUnattend.Add_Checked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
$localState = $window.Tag
$localState.Controls.chkInjectUnattend.IsChecked = $false
Update-DeviceNamingControls -State $localState
})
$State.Controls.chkCopyUnattend.Add_Unchecked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
Update-DeviceNamingControls -State $window.Tag
})
$State.Controls.chkInjectUnattend.Add_Checked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
$localState = $window.Tag
$localState.Controls.chkCopyUnattend.IsChecked = $false
Update-DeviceNamingControls -State $localState
})
$State.Controls.chkInjectUnattend.Add_Unchecked({
param($eventSource, $routedEventArgs)
$window = [System.Windows.Window]::GetWindow($eventSource)
Update-DeviceNamingControls -State $window.Tag
})
# Build USB Drive Settings Event Handlers # Build USB Drive Settings Event Handlers
# The USB Expander is always visible; the checkbox controls child settings only # The USB Expander is always visible; the checkbox controls child settings only
$State.Controls.chkBuildUSBDriveEnable.Add_Checked({ $State.Controls.chkBuildUSBDriveEnable.Add_Checked({
@@ -220,6 +220,16 @@ 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.rbDeviceNamingNone = $window.FindName('rbDeviceNamingNone')
$State.Controls.rbDeviceNamingTemplate = $window.FindName('rbDeviceNamingTemplate')
$State.Controls.rbDeviceNamingPrefixes = $window.FindName('rbDeviceNamingPrefixes')
$State.Controls.deviceNameTemplatePanel = $window.FindName('deviceNameTemplatePanel')
$State.Controls.deviceNamePrefixesPanel = $window.FindName('deviceNamePrefixesPanel')
$State.Controls.txtDeviceNameTemplate = $window.FindName('txtDeviceNameTemplate')
$State.Controls.txtDeviceNamePrefixesPath = $window.FindName('txtDeviceNamePrefixesPath')
$State.Controls.btnBrowseDeviceNamePrefixesPath = $window.FindName('btnBrowseDeviceNamePrefixesPath')
$State.Controls.txtDeviceNamePrefixes = $window.FindName('txtDeviceNamePrefixes')
$State.Controls.btnSaveDeviceNamePrefixes = $window.FindName('btnSaveDeviceNamePrefixes')
$State.Controls.chkVerbose = $window.FindName('chkVerbose') $State.Controls.chkVerbose = $window.FindName('chkVerbose')
$State.Controls.chkCopyAutopilot = $window.FindName('chkCopyAutopilot') $State.Controls.chkCopyAutopilot = $window.FindName('chkCopyAutopilot')
$State.Controls.chkCopyUnattend = $window.FindName('chkCopyUnattend') $State.Controls.chkCopyUnattend = $window.FindName('chkCopyUnattend')
@@ -383,6 +393,12 @@ function Initialize-UIDefaults {
$State.Controls.chkCopyAutopilot.IsChecked = $State.Defaults.generalDefaults.CopyAutopilot $State.Controls.chkCopyAutopilot.IsChecked = $State.Defaults.generalDefaults.CopyAutopilot
$State.Controls.chkCopyUnattend.IsChecked = $State.Defaults.generalDefaults.CopyUnattend $State.Controls.chkCopyUnattend.IsChecked = $State.Defaults.generalDefaults.CopyUnattend
$State.Controls.chkCopyPPKG.IsChecked = $State.Defaults.generalDefaults.CopyPPKG $State.Controls.chkCopyPPKG.IsChecked = $State.Defaults.generalDefaults.CopyPPKG
Set-DeviceNamingMode -State $State -Mode $State.Defaults.generalDefaults.DeviceNamingMode
$State.Controls.txtDeviceNameTemplate.Text = $State.Defaults.generalDefaults.DeviceNameTemplate
$State.Controls.txtDeviceNamePrefixesPath.Text = $State.Defaults.generalDefaults.DeviceNamePrefixesPath
$State.Controls.txtDeviceNamePrefixes.Text = ($State.Defaults.generalDefaults.DeviceNamePrefixes -join [System.Environment]::NewLine)
Import-DeviceNamePrefixesFromConfiguredPath -State $State
Update-DeviceNamingControls -State $State
$State.Controls.chkCleanupAppsISO.IsChecked = $State.Defaults.generalDefaults.CleanupAppsISO $State.Controls.chkCleanupAppsISO.IsChecked = $State.Defaults.generalDefaults.CleanupAppsISO
$State.Controls.chkCleanupDeployISO.IsChecked = $State.Defaults.generalDefaults.CleanupDeployISO $State.Controls.chkCleanupDeployISO.IsChecked = $State.Defaults.generalDefaults.CleanupDeployISO
$State.Controls.chkCleanupDrivers.IsChecked = $State.Defaults.generalDefaults.CleanupDrivers $State.Controls.chkCleanupDrivers.IsChecked = $State.Defaults.generalDefaults.CleanupDrivers
@@ -1173,6 +1173,9 @@ function Invoke-BrowseAction {
if (-not [string]::IsNullOrWhiteSpace($InitialDirectory)) { if (-not [string]::IsNullOrWhiteSpace($InitialDirectory)) {
$dialog.InitialDirectory = $InitialDirectory $dialog.InitialDirectory = $InitialDirectory
} }
if (-not [string]::IsNullOrWhiteSpace($FileName)) {
$dialog.FileName = $FileName
}
if ($dialog.ShowDialog()) { if ($dialog.ShowDialog()) {
return $dialog.FileName return $dialog.FileName
} }
@@ -106,10 +106,12 @@ function Get-GeneralDefaults {
$peDriversPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "PEDrivers" $peDriversPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "PEDrivers"
$vmLocationPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "VM" $vmLocationPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "VM"
$ffuCapturePath = Join-Path -Path $FFUDevelopmentPath -ChildPath "FFU" $ffuCapturePath = Join-Path -Path $FFUDevelopmentPath -ChildPath "FFU"
$unattendPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "unattend"
$officePath = Join-Path -Path $appsPath -ChildPath "Office" $officePath = Join-Path -Path $appsPath -ChildPath "Office"
$appListJsonPath = Join-Path -Path $appsPath -ChildPath "AppList.json" $appListJsonPath = Join-Path -Path $appsPath -ChildPath "AppList.json"
$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"
return [PSCustomObject]@{ return [PSCustomObject]@{
# Build Tab Defaults # Build Tab Defaults
@@ -132,6 +134,10 @@ function Get-GeneralDefaults {
CopyUnattend = $false CopyUnattend = $false
CopyPPKG = $false CopyPPKG = $false
InjectUnattend = $false InjectUnattend = $false
DeviceNamingMode = 'None'
DeviceNameTemplate = ''
DeviceNamePrefixesPath = $deviceNamePrefixesPath
DeviceNamePrefixes = @()
CleanupAppsISO = $true CleanupAppsISO = $true
CleanupDeployISO = $true CleanupDeployISO = $true
CleanupDrivers = $false CleanupDrivers = $false
+93 -29
View File
@@ -64,6 +64,68 @@ function Set-Computername($computername) {
return $computername return $computername
} }
function Get-UnattendComputerNameValue {
if ($null -eq $UnattendFile) {
return $null
}
[xml]$xml = Get-Content $UnattendFile
foreach ($component in $xml.unattend.settings.component) {
if ($component.ComputerName) {
return [string]$component.ComputerName
}
}
return $null
}
function Test-LegacyPromptComputerName($computername) {
if ([string]::IsNullOrWhiteSpace($computername)) {
return $false
}
$normalizedName = $computername.Trim().ToLowerInvariant()
return $normalizedName -in @('mycomputer', 'default')
}
function Get-NormalizedComputerName($computername) {
if ([string]::IsNullOrWhiteSpace($computername)) {
throw 'Computer name cannot be empty.'
}
$normalizedName = ($computername -replace "\s", '').Trim()
if ([string]::IsNullOrWhiteSpace($normalizedName)) {
throw 'Computer name cannot be empty after removing spaces.'
}
if ($normalizedName.Length -gt 15) {
$normalizedName = $normalizedName.Substring(0, 15)
}
return $normalizedName
}
function Resolve-ComputerNameTemplate($computerNameTemplate, $serialNumber) {
if ([string]::IsNullOrWhiteSpace($computerNameTemplate)) {
throw 'Computer name template cannot be empty.'
}
$resolvedName = $computerNameTemplate -replace '(?i)%serial%', $serialNumber
if ($resolvedName -match '%') {
throw 'Unsupported device name variable found. Only %serial% is supported.'
}
return Get-NormalizedComputerName($resolvedName)
}
function Set-ConfiguredComputerName($computername) {
$normalizedName = Get-NormalizedComputerName($computername)
$normalizedName = Set-Computername($normalizedName)
Writelog "Computer name will be set to $normalizedName"
Write-Host "Computer name will be set to $normalizedName"
return $normalizedName
}
function Invoke-Process { function Invoke-Process {
[CmdletBinding(SupportsShouldProcess)] [CmdletBinding(SupportsShouldProcess)]
param param
@@ -1023,8 +1085,19 @@ If (Test-Path -Path $UnattendComputerNamePath) {
} }
} }
#Ask for device name if unattend exists $UnattendConfiguredComputerName = $null
If ($Unattend -or $UnattendPrefix -or $UnattendComputerName) { $RequiresLegacyDeviceNamePrompt = $false
$RequiresTemplateDeviceName = $false
if ($Unattend) {
$UnattendConfiguredComputerName = Get-UnattendComputerNameValue
$RequiresLegacyDeviceNamePrompt = Test-LegacyPromptComputerName($UnattendConfiguredComputerName)
if (-not [string]::IsNullOrWhiteSpace($UnattendConfiguredComputerName) -and $UnattendConfiguredComputerName -match '(?i)%serial%') {
$RequiresTemplateDeviceName = $true
}
}
#Ask for device name if naming is explicitly required
If ($UnattendPrefix -or $UnattendComputerName -or $RequiresTemplateDeviceName -or $RequiresLegacyDeviceNamePrompt) {
Write-SectionHeader 'Device Name Selection' Write-SectionHeader 'Device Name Selection'
if ($Unattend -and $UnattendPrefix) { if ($Unattend -and $UnattendPrefix) {
Writelog 'Unattend file found with prefixes.txt. Getting prefixes.' Writelog 'Unattend file found with prefixes.txt. Getting prefixes.'
@@ -1060,17 +1133,8 @@ If ($Unattend -or $UnattendPrefix -or $UnattendComputerName) {
WriteLog "Will use $PrefixToUse as device name prefix" WriteLog "Will use $PrefixToUse as device name prefix"
Write-Host "Will use $PrefixToUse as device name prefix" Write-Host "Will use $PrefixToUse as device name prefix"
} }
#Get serial number to append. This can make names longer than 15 characters. Trim any leading or trailing whitespace
$serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim() $serial = (Get-CimInstance -ClassName win32_bios).SerialNumber.Trim()
#Combine prefix with serial $computername = Set-ConfiguredComputerName($PrefixToUse + $serial)
$computername = ($PrefixToUse + $serial) -replace "\s", "" # Remove spaces because windows does not support spaces in the computer names
#If computername is longer than 15 characters, reduce to 15. Sysprep/unattend doesn't like ComputerName being longer than 15 characters even though Windows accepts it
If ($computername.Length -gt 15) {
$computername = $computername.substring(0, 15)
}
$computername = Set-Computername($computername)
Writelog "Computer name will be set to $computername"
Write-Host "Computer name will be set to $computername"
} }
elseif ($Unattend -and $UnattendComputerName) { elseif ($Unattend -and $UnattendComputerName) {
Writelog 'Unattend file found with SerialComputerNames.csv. Getting name for current computer.' Writelog 'Unattend file found with SerialComputerNames.csv. Getting name for current computer.'
@@ -1080,32 +1144,31 @@ If ($Unattend -or $UnattendPrefix -or $UnattendComputerName) {
$SCName = $SerialComputerNames | Where-Object { $_.SerialNumber -eq $SerialNumber } $SCName = $SerialComputerNames | Where-Object { $_.SerialNumber -eq $SerialNumber }
If ($SCName) { If ($SCName) {
[string]$computername = $SCName.ComputerName [string]$computername = Set-ConfiguredComputerName($SCName.ComputerName)
$computername = Set-Computername($computername)
Writelog "Computer name will be set to $computername"
Write-Host "Computer name will be set to $computername"
} }
else { else {
Writelog 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.' Writelog 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.'
Write-Host 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.' Write-Host 'No matching serial number found in SerialComputerNames.csv. Setting random computer name to complete setup.'
[string]$computername = ("FFU-" + ( -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 11 | ForEach-Object { [char]$_ }))) [string]$computername = Set-ConfiguredComputerName(("FFU-" + ( -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 11 | ForEach-Object { [char]$_ }))))
$computername = Set-Computername($computername)
Writelog "Computer name will be set to $computername"
Write-Host "Computer name will be set to $computername"
} }
} }
elseif ($Unattend) { elseif ($Unattend -and $RequiresTemplateDeviceName) {
Writelog 'Unattend file found with a %serial% computer name template. Resolving the template.'
$serialNumber = (Get-CimInstance -ClassName Win32_Bios).SerialNumber.Trim()
[string]$computername = Set-ConfiguredComputerName((Resolve-ComputerNameTemplate -computerNameTemplate $UnattendConfiguredComputerName -serialNumber $serialNumber))
}
elseif ($Unattend -and $RequiresLegacyDeviceNamePrompt) {
Writelog 'Unattend file found with no prefixes.txt, asking for name' Writelog 'Unattend file found with no prefixes.txt, asking for name'
Write-Host 'Unattend file found but no prefixes.txt. Please enter a device name.' Write-Host 'Unattend file found but no prefixes.txt. Please enter a device name.'
[string]$computername = Read-Host 'Enter device name' [string]$computername = Set-ConfiguredComputerName((Read-Host 'Enter device name'))
$computername = Set-Computername($computername)
Writelog "Computer name will be set to $computername"
Write-Host "Computer name will be set to $computername"
} }
else { else {
WriteLog 'Device naming assets detected without unattend.xml. Skipping device naming prompts.' WriteLog 'Device naming assets detected without unattend.xml. Skipping device naming prompts.'
} }
} }
elseif ($Unattend) {
WriteLog 'Unattend file found. Device naming is not required, but unattend settings will still be applied.'
}
else { else {
WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.' WriteLog 'No unattend folder found. Device name will be set via PPKG, AP JSON, or default OS name.'
} }
@@ -1568,8 +1631,9 @@ If ($PPKGFileToInstall) {
} }
} }
#Set DeviceName #Set DeviceName
If ($computername) { If ($Unattend) {
Write-SectionHeader -Title 'Applying Computer Name and Unattend Configuration' $unattendSectionTitle = if ($computername) { 'Applying Computer Name and Unattend Configuration' } else { 'Applying Unattend Configuration' }
Write-SectionHeader -Title $unattendSectionTitle
try { try {
$PantherDir = 'w:\windows\panther' $PantherDir = 'w:\windows\panther'
If (Test-Path -Path $PantherDir) { If (Test-Path -Path $PantherDir) {
@@ -1590,8 +1654,8 @@ If ($computername) {
} }
} }
catch { catch {
WriteLog "Copying Unattend.xml to name device failed" WriteLog 'Copying Unattend.xml to Panther failed'
Stop-Script -Message "Copying Unattend.xml to name device failed with error: $_" Stop-Script -Message "Copying Unattend.xml to Panther failed with error: $_"
} }
} }
Binary file not shown.
+24 -8
View File
@@ -199,24 +199,34 @@ This option is only available when **Build USB Drive** is checked.
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 - **unattend_x64.xml** (for x64 builds) or **unattend_arm64.xml** (for arm64 builds) → renamed to **Unattend.xml** on the USB drive
- **prefixes.txt** (if present) → copied alongside the unattend file - **prefixes.txt** → created from the **Device Naming** prefixes list when that mode is selected
During deployment, `ApplyFFU.ps1` detects the `Unattend` folder and uses these files to customize the device name and apply other Windows settings during OOBE. 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.
#### Device Naming #### Device Naming
Device naming can be done from PE. The way this works is by leveraging an unattend.xml file to either take input from the user at imaging time or read a list of prefix values and append the serial number of the device. There are some major benefits to doing this: Use the **Device Naming** expander to decide whether `ComputerName` should be set at deployment time. There are some major benefits to doing this:
1. Total deployment time is reduced if naming is set at FFU deployment time since there is no additional reboot done during OOBE. 1. Total deployment time is reduced if naming is set at FFU deployment time since there is no additional reboot done during OOBE.
2. Reduces the need for multiple provisioning packages or autopilot profiles. This means you can use a single PPKG or autopilot profile. 2. Reduces the need for multiple provisioning packages or autopilot profiles. This means you can use a single PPKG or autopilot profile.
#### Prompt for Device Name #### No Device Name
If you want to be prompted for the device name, simply check **Copy Unattend.xml.** This tells the build script to copy the appropriate architecture unattend_arch.xml file from the `C:\FFUDevelopment\Unattend` folder to the `.\unattend` folder on the deploy partition of the USB drive. This is the default option. The unattend file is still applied, but Windows generates a random computer name instead of forcing a prompt or a fixed name.
#### Device Naming with prefixes.txt #### Specify Device Name
If a `prefixes.txt` file exists in the `Unattend` folder and there are multiple prefixes in the file, the deployment script prompts the technician to select a prefix from the file. The prefix is combined with the device's serial number to create the computer name. If there is a single prefix, the technician is not prompted and the script will automatically select that prefix. Use this option when you want a static device name or a template such as `Comp-%serial%`.
- With **Copy Unattend.xml**, `%serial%` is resolved during deployment in PE.
- With **Inject Unattend.xml**, only static names are supported.
- **Copy Unattend.xml** and **Inject Unattend.xml** are mutually exclusive. Select only one.
#### Specify a list of Prefixes
This option writes `prefixes.txt` from the list in the UI. Enter one prefix per line or import an existing prefixes file. The source file can use any name because the UI now tracks the path separately.
If there is a single prefix, deployment uses it automatically. If there are multiple prefixes, the technician is prompted to select one. The selected prefix is combined with the device serial number to create the computer name.
For example, with a prefix of `CORP-` and a serial number of `ABC123`, the resulting computer name would be `CORP-ABC123` (truncated to 15 characters if necessary). For example, with a prefix of `CORP-` and a serial number of `ABC123`, the resulting computer name would be `CORP-ABC123` (truncated to 15 characters if necessary).
@@ -228,6 +238,10 @@ STORE-
KIOSK- KIOSK-
``` ```
#### Legacy Prompt Behavior
Older deployment media that still has an unattend file with `ComputerName` set to the legacy placeholder value and no `prefixes.txt` file will still prompt for a device name during deployment.
{: .warning-title} {: .warning-title}
> Warning > Warning
@@ -483,6 +497,8 @@ Controls the `-InjectUnattend` parameter. When checked, copies the architecture-
This option is only available when **Install Apps** is checked. This option is only available when **Install Apps** is checked.
`Copy Unattend.xml` and `Inject Unattend.xml` are mutually exclusive. Select only one.
### How It Works ### How It Works
When enabled, the build process: When enabled, the build process:
@@ -522,7 +538,7 @@ This option is primarily intended for scenarios where:
| Limitation | Description | | Limitation | Description |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **No prefixes.txt support** | Unlike the**Copy Unattend** option for USB drives, this method does not support `prefixes.txt` for dynamic device naming based on serial numbers | | **No prefixes.txt or %serial% support** | Unlike **Copy Unattend.xml**, this method does not support `prefixes.txt` or the `%serial%` variable for deployment-time device naming |
| **Fixed configuration** | The unattend settings are baked into the FFU at build time and cannot be changed at deployment time | | **Fixed configuration** | The unattend settings are baked into the FFU at build time and cannot be changed at deployment time |
| **Requires VM to be built** | This option only works when**Install Apps** is `$true` because the unattend file is included in the Apps ISO | | **Requires VM to be built** | This option only works when**Install Apps** is `$true` because the unattend file is included in the Apps ISO |
+6 -2
View File
@@ -38,9 +38,13 @@ 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. 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. |
| -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, Template, and Prefixes. The UI uses None, Template, and Prefixes. |
| -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. |
| -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. |
| -DriversJsonPath | string | Drivers.json Path | Path to a JSON file that specifies which drivers to download. | | -DriversJsonPath | string | Drivers.json Path | Path to a JSON file that specifies which drivers to download. |
@@ -50,7 +54,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. Default is $false. | | -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. |
| -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. |
+13 -5
View File
@@ -186,18 +186,26 @@ Another safety measure is **Select Specific USB Drives**. When you check **Selec
**Device Naming** **Device Naming**
Device naming can be done from PE. The way this works is by leveraging an unattend.xml file to either take input from the user at imaging time or read a list of prefix values and append the serial number of the device. There are some major benefits to doing this: Use the **Device Naming** expander on the Build page to decide whether `ComputerName` should be set during deployment. There are some major benefits to doing this:
1. Total deployment time is reduced if naming is set at FFU deployment time since there is no additional reboot done during OOBE. 1. Total deployment time is reduced if naming is set at FFU deployment time since there is no additional reboot done during OOBE.
2. Reduces the need for multiple provisioning packages or autopilot profiles. This means you can use a single PPKG or autopilot profile. 2. Reduces the need for multiple provisioning packages or autopilot profiles. This means you can use a single PPKG or autopilot profile.
**Prompt for Device Name** **No Device Name**
If you want to be prompted for the device name, simply check **Copy Unattend.xml.** This tells the build script to copy the appropriate architecture unattend_arch.xml file from the `C:\FFUDevelopment\Unattend` folder to the `.\unattend` folder on the deploy partition of the USB drive. This is the default option. The unattend file is still applied, but Windows generates a random computer name.
**Specifying Multiple Name Prefixes** **Specify Device Name**
If you have multiple device name prefixes for different locations or device use cases, or even a single prefix, you can specify a prefixes.txt file in the `C:\FFUDevelopment\unattend` folder. If the prefixes.txt file is detected and a single prefix is listed, the device will just use that prefix and append the serial number of the device. If there are multiple prefixes listed in the prefixes.txt file, you will be prompted to select which prefix you want to name the device and the serial number will be appended to that prefix. If you want a dash in the name, include the dash in the prefix (e.g. if ABCD- is in the prefixes.txt file, the device name will be ABCD-SerialNumber). Use this option when you want a static device name or a template such as `Comp-%serial%`.
- With **Copy Unattend.xml**, `%serial%` is resolved during deployment in PE.
- With **Inject Unattend.xml**, only static names are supported.
- **Copy Unattend.xml** and **Inject Unattend.xml** are mutually exclusive. Select only one.
**Specify a list of Prefixes**
This option writes `prefixes.txt` from the list in the UI. Enter one prefix per line or import an existing prefixes file. The source file can use any name because the UI tracks the prefixes path separately. If there is one prefix, deployment uses it automatically. If there are multiple prefixes, the technician is prompted to select one and the serial number is appended to that prefix.
{: .warning-title} {: .warning-title}