mirror of
https://github.com/rbalsleyMSFT/FFU.git
synced 2026-06-13 18:07:20 -06:00
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:
+213
-11
@@ -72,6 +72,18 @@ When set to $true, will copy the provisioning package from the $FFUDevelopmentPa
|
||||
.PARAMETER CopyUnattend
|
||||
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
|
||||
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]$CopyPPKG,
|
||||
[bool]$CopyUnattend,
|
||||
[ValidateSet('Legacy', 'None', 'Template', 'Prefixes')]
|
||||
[string]$DeviceNamingMode = 'Legacy',
|
||||
[string]$DeviceNameTemplate,
|
||||
[string[]]$DeviceNamePrefixes,
|
||||
[string]$DeviceNamePrefixesPath,
|
||||
[bool]$CopyAutopilot,
|
||||
[bool]$CompactOS = $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')
|
||||
$enableVmNetworkingWasExplicitlyBound = $PSBoundParameters.ContainsKey('EnableVMNetworking')
|
||||
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.'
|
||||
}
|
||||
|
||||
$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
|
||||
$clientSKUs = @(
|
||||
'Home',
|
||||
@@ -4184,6 +4320,57 @@ Function New-DeploymentUSB {
|
||||
Import-Module "$($using:PSScriptRoot)\FFU.Common" -Force
|
||||
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", "")
|
||||
WriteLog "Thread $([System.Threading.Thread]::CurrentThread.ManagedThreadId) processing DiskNumber $DiskNumber ($($USBDrive.Model))"
|
||||
|
||||
@@ -4244,15 +4431,15 @@ Function New-DeploymentUSB {
|
||||
$UnattendPathOnUSB = Join-Path $DeployPartitionDriveLetter "Unattend"
|
||||
WriteLog "Copying unattend file to $UnattendPathOnUSB"
|
||||
New-Item -Path $UnattendPathOnUSB -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
|
||||
if ($using:WindowsArch -eq 'x64') {
|
||||
Copy-Item -Path (Join-Path $using:UnattendFolder 'unattend_x64.xml') -Destination (Join-Path $UnattendPathOnUSB 'Unattend.xml') -Force | Out-Null
|
||||
$unattendSource = Get-LocalUnattendSourcePath -UnattendFolder $using:UnattendFolder -WindowsArch $using:WindowsArch
|
||||
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') {
|
||||
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')) {
|
||||
elseif (($using:DeviceNamingMode -eq 'Legacy') -and (Test-Path -Path $using:resolvedDeviceNamePrefixesPath -PathType Leaf)) {
|
||||
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'
|
||||
}
|
||||
@@ -5518,9 +5705,26 @@ if ($CopyUnattend) {
|
||||
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"
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
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
|
||||
#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.
|
||||
@@ -6418,9 +6622,7 @@ if ($InstallApps) {
|
||||
#Create Apps ISO
|
||||
# Inject Unattend.xml into Apps if requested and applicable
|
||||
if ($InstallApps -and $InjectUnattend) {
|
||||
# Determine source unattend.xml based on architecture
|
||||
$archSuffix = if ($WindowsArch -ieq 'arm64') { 'arm64' } else { 'x64' }
|
||||
$unattendSource = Join-Path $UnattendFolder "unattend_$archSuffix.xml"
|
||||
$unattendSource = Get-UnattendSourcePath -UnattendFolder $UnattendFolder -WindowsArch $WindowsArch
|
||||
|
||||
# Ensure target folder exists under Apps
|
||||
$targetFolder = Join-Path $AppsPath 'Unattend'
|
||||
@@ -6431,7 +6633,7 @@ if ($InstallApps) {
|
||||
# Copy if source exists; otherwise log and skip
|
||||
if (Test-Path -Path $unattendSource -PathType Leaf) {
|
||||
$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"
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -432,6 +432,60 @@ $script:uiState.Controls.btnRun.Add_Click({
|
||||
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"
|
||||
# Sort top-level keys alphabetically for consistent output
|
||||
$sortedConfig = [ordered]@{}
|
||||
|
||||
@@ -836,9 +836,11 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 9: General Build Options Checkboxes -->
|
||||
<RowDefinition Height="Auto"/>
|
||||
<!-- Row 10: Build USB Drive Section -->
|
||||
<!-- Row 10: Device Naming -->
|
||||
<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"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -903,8 +905,40 @@
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
<!-- Row 10: Build USB Drive Section -->
|
||||
<Expander Grid.Row="10" x:Name="usbDriveSection" Header="Build USB Drive Options" IsExpanded="False" Margin="0,0,0,20" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
|
||||
<!-- Row 10: Device Naming -->
|
||||
<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">
|
||||
<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."/>
|
||||
@@ -969,8 +1003,8 @@
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
<!-- Row 11: Post-Build Cleanup -->
|
||||
<Expander Grid.Row="11" Header="Post-Build Cleanup" IsExpanded="False" Margin="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
|
||||
<!-- Row 12: Post-Build Cleanup -->
|
||||
<Expander Grid.Row="12" Header="Post-Build Cleanup" IsExpanded="False" Margin="0" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
|
||||
<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="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
|
||||
CopyPPKG = $State.Controls.chkCopyPPKG.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
|
||||
CreateDeploymentMedia = $State.Controls.chkCreateDeploymentMedia.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 'chkCopyUnattend' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'CopyUnattend' -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)
|
||||
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 {
|
||||
param([PSCustomObject]$State)
|
||||
WriteLog "Registering UI event handlers..."
|
||||
@@ -242,7 +408,15 @@ function Register-EventHandlers {
|
||||
$localState = $window.Tag
|
||||
$selectedPath = Invoke-BrowseAction -Type 'Folder' -Title "Select FFU Development Path"
|
||||
if ($selectedPath) {
|
||||
$currentPrefixesPath = $localState.Controls.txtDeviceNamePrefixesPath.Text
|
||||
$previousDefaultPrefixesPath = Get-DefaultDeviceNamePrefixesPath -FFUDevelopmentPath $localState.Controls.txtFFUDevPath.Text
|
||||
$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
|
||||
# The USB Expander is always visible; the checkbox controls child settings only
|
||||
$State.Controls.chkBuildUSBDriveEnable.Add_Checked({
|
||||
|
||||
@@ -220,6 +220,16 @@ function Initialize-UIControls {
|
||||
$State.Controls.chkAllowVHDXCaching = $window.FindName('chkAllowVHDXCaching')
|
||||
$State.Controls.chkCreateDeploymentMedia = $window.FindName('chkCreateDeploymentMedia')
|
||||
$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.chkCopyAutopilot = $window.FindName('chkCopyAutopilot')
|
||||
$State.Controls.chkCopyUnattend = $window.FindName('chkCopyUnattend')
|
||||
@@ -383,6 +393,12 @@ function Initialize-UIDefaults {
|
||||
$State.Controls.chkCopyAutopilot.IsChecked = $State.Defaults.generalDefaults.CopyAutopilot
|
||||
$State.Controls.chkCopyUnattend.IsChecked = $State.Defaults.generalDefaults.CopyUnattend
|
||||
$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.chkCleanupDeployISO.IsChecked = $State.Defaults.generalDefaults.CleanupDeployISO
|
||||
$State.Controls.chkCleanupDrivers.IsChecked = $State.Defaults.generalDefaults.CleanupDrivers
|
||||
|
||||
@@ -1173,6 +1173,9 @@ function Invoke-BrowseAction {
|
||||
if (-not [string]::IsNullOrWhiteSpace($InitialDirectory)) {
|
||||
$dialog.InitialDirectory = $InitialDirectory
|
||||
}
|
||||
if (-not [string]::IsNullOrWhiteSpace($FileName)) {
|
||||
$dialog.FileName = $FileName
|
||||
}
|
||||
if ($dialog.ShowDialog()) {
|
||||
return $dialog.FileName
|
||||
}
|
||||
|
||||
@@ -106,10 +106,12 @@ function Get-GeneralDefaults {
|
||||
$peDriversPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "PEDrivers"
|
||||
$vmLocationPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "VM"
|
||||
$ffuCapturePath = Join-Path -Path $FFUDevelopmentPath -ChildPath "FFU"
|
||||
$unattendPath = Join-Path -Path $FFUDevelopmentPath -ChildPath "unattend"
|
||||
$officePath = Join-Path -Path $appsPath -ChildPath "Office"
|
||||
$appListJsonPath = Join-Path -Path $appsPath -ChildPath "AppList.json"
|
||||
$userAppListPath = Join-Path -Path $appsPath -ChildPath "UserAppList.json"
|
||||
$driversJsonPath = Join-Path -Path $driversPath -ChildPath "Drivers.json"
|
||||
$deviceNamePrefixesPath = Join-Path -Path $unattendPath -ChildPath "prefixes.txt"
|
||||
|
||||
return [PSCustomObject]@{
|
||||
# Build Tab Defaults
|
||||
@@ -132,6 +134,10 @@ function Get-GeneralDefaults {
|
||||
CopyUnattend = $false
|
||||
CopyPPKG = $false
|
||||
InjectUnattend = $false
|
||||
DeviceNamingMode = 'None'
|
||||
DeviceNameTemplate = ''
|
||||
DeviceNamePrefixesPath = $deviceNamePrefixesPath
|
||||
DeviceNamePrefixes = @()
|
||||
CleanupAppsISO = $true
|
||||
CleanupDeployISO = $true
|
||||
CleanupDrivers = $false
|
||||
|
||||
@@ -64,6 +64,68 @@ function Set-Computername($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 {
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param
|
||||
@@ -1023,8 +1085,19 @@ If (Test-Path -Path $UnattendComputerNamePath) {
|
||||
}
|
||||
}
|
||||
|
||||
#Ask for device name if unattend exists
|
||||
If ($Unattend -or $UnattendPrefix -or $UnattendComputerName) {
|
||||
$UnattendConfiguredComputerName = $null
|
||||
$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'
|
||||
if ($Unattend -and $UnattendPrefix) {
|
||||
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"
|
||||
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()
|
||||
#Combine prefix with 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"
|
||||
$computername = Set-ConfiguredComputerName($PrefixToUse + $serial)
|
||||
}
|
||||
elseif ($Unattend -and $UnattendComputerName) {
|
||||
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 }
|
||||
|
||||
If ($SCName) {
|
||||
[string]$computername = $SCName.ComputerName
|
||||
$computername = Set-Computername($computername)
|
||||
Writelog "Computer name will be set to $computername"
|
||||
Write-Host "Computer name will be set to $computername"
|
||||
[string]$computername = Set-ConfiguredComputerName($SCName.ComputerName)
|
||||
}
|
||||
else {
|
||||
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.'
|
||||
[string]$computername = ("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"
|
||||
[string]$computername = Set-ConfiguredComputerName(("FFU-" + ( -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 11 | ForEach-Object { [char]$_ }))))
|
||||
}
|
||||
}
|
||||
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'
|
||||
Write-Host 'Unattend file found but no prefixes.txt. Please enter a device name.'
|
||||
[string]$computername = 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"
|
||||
[string]$computername = Set-ConfiguredComputerName((Read-Host 'Enter device name'))
|
||||
}
|
||||
else {
|
||||
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 {
|
||||
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
|
||||
If ($computername) {
|
||||
Write-SectionHeader -Title 'Applying Computer Name and Unattend Configuration'
|
||||
If ($Unattend) {
|
||||
$unattendSectionTitle = if ($computername) { 'Applying Computer Name and Unattend Configuration' } else { 'Applying Unattend Configuration' }
|
||||
Write-SectionHeader -Title $unattendSectionTitle
|
||||
try {
|
||||
$PantherDir = 'w:\windows\panther'
|
||||
If (Test-Path -Path $PantherDir) {
|
||||
@@ -1590,8 +1654,8 @@ If ($computername) {
|
||||
}
|
||||
}
|
||||
catch {
|
||||
WriteLog "Copying Unattend.xml to name device failed"
|
||||
Stop-Script -Message "Copying Unattend.xml to name device failed with error: $_"
|
||||
WriteLog 'Copying Unattend.xml to Panther failed'
|
||||
Stop-Script -Message "Copying Unattend.xml to Panther failed with error: $_"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user