From 78212f06d7bb82405981ad17d0436c871c9d173e Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:37:53 -0700 Subject: [PATCH] Add experimental VM networking opt-in for Hyper-V builds The Hyper-V switch selection in the UI previously did not connect the build VM to the network during provisioning. Since internet-connected Sysprep and capture flows are still experimental, this introduces an explicit "Enable VM Networking" checkbox to the Hyper-V Settings page that defaults to off. - Adds the `-EnableVMNetworking` parameter to BuildFFUVM.ps1 to conditionally attach the Hyper-V network adapter during VM creation. - Persists the setting through FFU config files and blocks UI execution if enabled without a valid switch. - Refactors WPF event handlers in FFUUI.Core to fix dropdown scoping errors. - Updates documentation and the sample configuration file to reflect the new behavior. --- FFUDevelopment/BuildFFUVM.ps1 | 60 +++++++++++++++--- FFUDevelopment/BuildFFUVM_UI.ps1 | 7 ++ FFUDevelopment/BuildFFUVM_UI.xaml | 14 ++-- .../FFUUI.Core/FFUUI.Core.Config.psm1 | 6 ++ .../FFUUI.Core/FFUUI.Core.Handlers.psm1 | 47 +++++++++++--- .../FFUUI.Core/FFUUI.Core.Initialize.psm1 | 5 +- FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 | 1 + FFUDevelopment/config/Sample_default.json | Bin 6094 -> 6162 bytes docs/hyperv_settings.md | 8 ++- docs/parameters_reference.md | 3 +- docs/quickstart.md | 4 +- 11 files changed, 128 insertions(+), 27 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 14e4e50..5055dbb 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -90,6 +90,9 @@ Path to a JSON file that specifies which drivers to download. .PARAMETER ExportConfigFile Path to a JSON file to export the parameters used for the script. +.PARAMETER EnableVMNetworking +When set to $true, connects the build VM to the Hyper-V virtual switch named in -VMSwitchName during provisioning. Default is $false because internet-connected Sysprep is experimental. + .PARAMETER FFUCaptureLocation Path to the folder where the captured FFU will be stored. Default is $FFUDevelopmentPath\FFU. @@ -223,7 +226,7 @@ Path to a JSON file containing a list of user-defined applications to install. D Default is $FFUDevelopmentPath\VM. This is the location of the VHDX that gets created where Windows will be installed to. .PARAMETER VMSwitchName -Name of the Hyper-V virtual switch. Optional when building with InstallApps. Provide it only if the VM needs network connectivity during provisioning. +Name of the Hyper-V virtual switch used when -EnableVMNetworking is set to $true. Provide it only if the VM needs network connectivity during provisioning. .PARAMETER WindowsArch String value of 'x86', 'x64', or 'arm64'. This is used to identify which architecture of Windows to download. Default is 'x64'. @@ -317,6 +320,7 @@ param( [uint64]$Memory = 4GB, [uint64]$Disksize = 50GB, [int]$Processors = 4, + [bool]$EnableVMNetworking, [string]$VMSwitchName, [string]$VMLocation, [string]$FFUPrefix = '_FFU', @@ -501,6 +505,13 @@ if ($ConfigFile -and (Test-Path -Path $ConfigFile)) { } } +$vmSwitchWasExplicitlyBound = $PSBoundParameters.ContainsKey('VMSwitchName') +$enableVmNetworkingWasExplicitlyBound = $PSBoundParameters.ContainsKey('EnableVMNetworking') +if (-not $EnableVMNetworking -and $vmSwitchWasExplicitlyBound -and -not $enableVmNetworkingWasExplicitlyBound) { + $EnableVMNetworking = $true + WriteLog 'EnableVMNetworking not explicitly set. Enabling VM networking because -VMSwitchName was supplied on the command line.' +} + # Validate that the selected Windows SKU is compatible with the chosen Windows release and ensure an ISO is provided for unsupported releases $clientSKUs = @( 'Home', @@ -2899,6 +2910,27 @@ function New-FFUVM { $VM = New-VM -Name $VMName -Path $VMPath -MemoryStartupBytes $memory -VHDPath $VHDXPath -Generation 2 Set-VMProcessor -VMName $VMName -Count $processors + # Connect the VM to the requested switch only when the experimental networking flag is enabled. + if ($EnableVMNetworking) { + $primaryVmNetworkAdapter = Get-VMNetworkAdapter -VMName $VMName -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -ne $primaryVmNetworkAdapter) { + if ($primaryVmNetworkAdapter.SwitchName -eq $VMSwitchName) { + WriteLog "VM '$VMName' is already connected to Hyper-V switch '$VMSwitchName'." + } + else { + Connect-VMNetworkAdapter -VMNetworkAdapter $primaryVmNetworkAdapter -SwitchName $VMSwitchName -ErrorAction Stop + WriteLog "Connected VM '$VMName' to Hyper-V switch '$VMSwitchName'." + } + } + else { + Add-VMNetworkAdapter -VMName $VMName -SwitchName $VMSwitchName -Name 'FFUNetworkAdapter' -ErrorAction Stop | Out-Null + WriteLog "Added VM network adapter for '$VMName' on Hyper-V switch '$VMSwitchName'." + } + } + else { + WriteLog "VM networking is disabled for '$VMName'." + } + #Mount AppsISO Add-VMDvdDrive -VMName $VMName -Path $AppsISO @@ -5500,14 +5532,26 @@ if ($CopyUnattend) { if (($InstallOffice -eq $true) -and ($InstallApps -eq $false)) { throw "If variable InstallOffice is set to `$true, InstallApps must also be set to `$true." } -if ($VMSwitchName) { - WriteLog "Validating -VMSwitchName $VMSwitchName" - #Check $VMSwitchName by using Get-VMSwitch - $VMSwitch = Get-VMSwitch -Name $VMSwitchName -ErrorAction SilentlyContinue - if (-not $VMSwitch) { - throw "-VMSwitchName $VMSwitchName not found. Please check the -VMSwitchName parameter and try again." +if ($EnableVMNetworking) { + if ($InstallApps -eq $false) { + WriteLog 'EnableVMNetworking is set to true, but InstallApps is false. No VM will be created, so VM networking will be ignored.' } - WriteLog '-VMSwitchName validation complete' + else { + if ([string]::IsNullOrWhiteSpace($VMSwitchName)) { + throw '-EnableVMNetworking requires -VMSwitchName. Select or enter a Hyper-V switch and try again.' + } + + WriteLog "Experimental VM networking enabled. Validating -VMSwitchName $VMSwitchName" + #Check $VMSwitchName by using Get-VMSwitch + $VMSwitch = Get-VMSwitch -Name $VMSwitchName -ErrorAction SilentlyContinue + if (-not $VMSwitch) { + throw "-VMSwitchName $VMSwitchName not found. Please check the -VMSwitchName parameter and try again." + } + WriteLog '-EnableVMNetworking validation complete' + } +} +elseif ($VMSwitchName) { + WriteLog "VM networking is disabled. Stored -VMSwitchName $VMSwitchName will not be used unless -EnableVMNetworking is `$true." } if (-not ($ISOPath) -and ($OptionalFeatures -like '*netfx3*')) { diff --git a/FFUDevelopment/BuildFFUVM_UI.ps1 b/FFUDevelopment/BuildFFUVM_UI.ps1 index fc7eefb..3bf3926 100644 --- a/FFUDevelopment/BuildFFUVM_UI.ps1 +++ b/FFUDevelopment/BuildFFUVM_UI.ps1 @@ -425,6 +425,13 @@ $script:uiState.Controls.btnRun.Add_Click({ return } + if ($config.EnableVMNetworking -and $config.InstallApps -and [string]::IsNullOrWhiteSpace([string]$config.VMSwitchName)) { + [System.Windows.MessageBox]::Show("Select or enter a VM Switch Name before enabling VM networking.", "VM Switch Required", "OK", "Warning") | Out-Null + $btnRun.IsEnabled = $true + $script:uiState.Controls.txtStatus.Text = "Build canceled: VM switch required for experimental networking." + return + } + $configFilePath = Join-Path $config.FFUDevelopmentPath "\config\FFUConfig.json" # Sort top-level keys alphabetically for consistent output $sortedConfig = [ordered]@{} diff --git a/FFUDevelopment/BuildFFUVM_UI.xaml b/FFUDevelopment/BuildFFUVM_UI.xaml index f645932..de8f077 100644 --- a/FFUDevelopment/BuildFFUVM_UI.xaml +++ b/FFUDevelopment/BuildFFUVM_UI.xaml @@ -315,11 +315,15 @@ - - - - - + + + + + + + + + diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 index 982b5c5..f8791b0 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Config.psm1 @@ -44,6 +44,7 @@ function Get-UIConfig { DownloadDrivers = $State.Controls.chkDownloadDrivers.IsChecked DriversFolder = $State.Controls.txtDriversFolder.Text DriversJsonPath = $State.Controls.txtDriversJsonPath.Text + EnableVMNetworking = $State.Controls.chkEnableVMNetworking.IsChecked FFUCaptureLocation = $State.Controls.txtFFUCaptureLocation.Text FFUDevelopmentPath = $State.Controls.txtFFUDevPath.Text FFUPrefix = $State.Controls.txtVMNamePrefix.Text @@ -466,6 +467,7 @@ function Update-UIFromConfig { Set-UIValue -ControlName 'chkRemoveDownloadedESD' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'RemoveDownloadedESD' -State $State # Hyper-V Settings + Set-UIValue -ControlName 'chkEnableVMNetworking' -PropertyName 'IsChecked' -ConfigObject $ConfigContent -ConfigKey 'EnableVMNetworking' -State $State Select-VMSwitchFromConfig -State $State -ConfigContent $ConfigContent Set-UIValue -ControlName 'txtDiskSize' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Disksize' -TransformValue { param($val) $val / 1GB } -State $State Set-UIValue -ControlName 'txtMemory' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'Memory' -TransformValue { param($val) $val / 1GB } -State $State @@ -473,6 +475,10 @@ function Update-UIFromConfig { Set-UIValue -ControlName 'txtVMLocation' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'VMLocation' -State $State Set-UIValue -ControlName 'txtVMNamePrefix' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'FFUPrefix' -State $State Set-UIValue -ControlName 'cmbLogicalSectorSize' -PropertyName 'SelectedItem' -ConfigObject $ConfigContent -ConfigKey 'LogicalSectorSizeBytes' -TransformValue { param($val) $val.ToString() } -State $State + $State.Controls.spVMNetworkingSettings.IsEnabled = $true -eq $State.Controls.chkEnableVMNetworking.IsChecked + if (-not ($true -eq $State.Controls.chkEnableVMNetworking.IsChecked)) { + $State.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed' + } # Windows Settings Set-UIValue -ControlName 'txtISOPath' -PropertyName 'Text' -ConfigObject $ConfigContent -ConfigKey 'ISOPath' -State $State diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 index ef7c5cb..32a13fb 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Handlers.psm1 @@ -5,6 +5,28 @@ This module is dedicated to managing user interactions within the FFU Builder UI. It contains the Register-EventHandlers function, which connects UI controls defined in the XAML to their corresponding actions in the PowerShell backend. This includes handling button clicks, text input validation, checkbox state changes, and list view interactions across all tabs, effectively wiring up the application's front-end to its core logic. #> +function Update-VMNetworkingControls { + param([PSCustomObject]$State) + + $isVmNetworkingEnabled = $true -eq $State.Controls.chkEnableVMNetworking.IsChecked + $State.Controls.spVMNetworkingSettings.IsEnabled = $isVmNetworkingEnabled + + if (-not $isVmNetworkingEnabled) { + $State.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed' + return + } + + if ($State.Controls.cmbVMSwitchName.SelectedItem -eq 'Other') { + $State.Controls.txtCustomVMSwitchName.Visibility = 'Visible' + if ([string]::IsNullOrWhiteSpace($State.Controls.txtCustomVMSwitchName.Text) -and $null -ne $State.Data.customVMSwitchName) { + $State.Controls.txtCustomVMSwitchName.Text = $State.Data.customVMSwitchName + } + } + else { + $State.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed' + } +} + function Register-EventHandlers { param([PSCustomObject]$State) WriteLog "Registering UI event handlers..." @@ -379,22 +401,27 @@ function Register-EventHandlers { }) # Hyper-V tab event handlers + $State.Controls.chkEnableVMNetworking.Add_Checked({ + param($eventSource, $routedEventArgs) + $window = [System.Windows.Window]::GetWindow($eventSource) + $localState = $window.Tag + Update-VMNetworkingControls -State $localState + }) + + $State.Controls.chkEnableVMNetworking.Add_Unchecked({ + param($eventSource, $routedEventArgs) + $window = [System.Windows.Window]::GetWindow($eventSource) + $localState = $window.Tag + Update-VMNetworkingControls -State $localState + }) + $State.Controls.cmbVMSwitchName.Add_SelectionChanged({ param($eventSource, $selectionChangedEventArgs) # The state object is available via the parent window's Tag property $window = [System.Windows.Window]::GetWindow($eventSource) $localState = $window.Tag - $selectedItem = $eventSource.SelectedItem - if ($selectedItem -eq 'Other') { - $localState.Controls.txtCustomVMSwitchName.Visibility = 'Visible' - if ([string]::IsNullOrWhiteSpace($localState.Controls.txtCustomVMSwitchName.Text) -and $null -ne $localState.Data.customVMSwitchName) { - $localState.Controls.txtCustomVMSwitchName.Text = $localState.Data.customVMSwitchName - } - } - else { - $localState.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed' - } + Update-VMNetworkingControls -State $localState }) # Persist custom VM switch name when user edits it while 'Other' is selected diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 index c58446d..0d921af 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Initialize.psm1 @@ -205,6 +205,8 @@ function Initialize-UIControls { $State.Controls.txtStatus = $window.FindName('txtStatus') $State.Controls.pbOverallProgress = $window.FindName('progressBar') $State.Controls.txtOverallStatus = $window.FindName('txtStatus') + $State.Controls.chkEnableVMNetworking = $window.FindName('chkEnableVMNetworking') + $State.Controls.spVMNetworkingSettings = $window.FindName('spVMNetworkingSettings') $State.Controls.cmbVMSwitchName = $window.FindName('cmbVMSwitchName') $State.Controls.txtCustomVMSwitchName = $window.FindName('txtCustomVMSwitchName') $State.Controls.txtFFUDevPath = $window.FindName('txtFFUDevPath') @@ -345,7 +347,6 @@ function Initialize-VMSwitchData { $State.Controls.cmbVMSwitchName.Items.Add('Other') | Out-Null if ($State.Controls.cmbVMSwitchName.Items.Count -gt 1) { $State.Controls.cmbVMSwitchName.SelectedIndex = 0 - $firstSwitch = $State.Controls.cmbVMSwitchName.SelectedItem $State.Controls.txtCustomVMSwitchName.Visibility = 'Collapsed' } else { @@ -398,7 +399,9 @@ function Initialize-UIDefaults { Update-BitsPrioritySetting -State $State # Hyper-V Settings defaults from General Defaults + $State.Controls.chkEnableVMNetworking.IsChecked = $State.Defaults.generalDefaults.EnableVMNetworking Initialize-VMSwitchData -State $State + $State.Controls.spVMNetworkingSettings.IsEnabled = $true -eq $State.Controls.chkEnableVMNetworking.IsChecked $State.Controls.txtDiskSize.Text = $State.Defaults.generalDefaults.DiskSizeGB $State.Controls.txtMemory.Text = $State.Defaults.generalDefaults.MemoryGB $State.Controls.txtProcessors.Text = $State.Defaults.generalDefaults.Processors diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 index 4c1ecbc..6525348 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.psm1 @@ -140,6 +140,7 @@ function Get-GeneralDefaults { RemoveUpdates = $false RemoveDownloadedESD = $true # Hyper-V Settings Defaults + EnableVMNetworking = $false DiskSizeGB = 50 MemoryGB = 4 Processors = 4 diff --git a/FFUDevelopment/config/Sample_default.json b/FFUDevelopment/config/Sample_default.json index fff1a49346ba52d9a682469b35b85a93dba4a32e..689ef22c1cee97af442d0eee7acbba66f5e39a4d 100644 GIT binary patch delta 51 zcmX@7KgnRjIZ