Files
Invoke-OPNSenseVMDeployment/Toolkit/Functions/Get-HyperVNextAvailableMACAddress.ps1
gsadmin 6e86c481a4 feat: Reserve a PXE server, float the shared rules, and soften the defaults
Credentials:
- The stock appliance password is retained by default. It is typed at a
  console before being changed, and the appliance prompts for a change on
  first sign in regardless
- GenerateRootPassword opts into a random password, now sixteen characters
  without symbols rather than twenty with them

Preboot execution environment:
- Get-HyperVNextAvailableMACAddress reads the next dynamic address the
  hypervisor will issue, treating the six bytes as one integer so an offset
  carries correctly across octets
- That address is reserved within Kea for a PXE server on the Infrastructure
  network, which allows the reservation to exist before the virtual machine
  that will carry the address does
- The reserved address populates a PXE_SERVERS_001 alias, becomes next_server
  and tftp_server_name within every scope, and is reachable through a floating
  rule from RFC1918
- The reservation is skipped with a warning on a host that does not run the
  hypervisor, so the document still generates

Rules:
- The management, jump host, name resolution and PXE rules are now floating.
  They were identical on every interface, so a sixteen network policy falls
  from 87 rules to 28
- Descriptions no longer name the network they were generated for, since a
  floating rule applies everywhere
- The RFC1918 alias now has a purpose as the source of the internal permits

Kea:
- The socket type defaults to udp and is exposed as DHCPSocketType. Raw
  remains the safer choice for a client that holds no address yet
- Option data auto collection is enabled

Elsewhere:
- Outbound network address translation moves to hybrid, with no manual rules
- Zone roles carry three digit indexes, so Servers_Zone_001

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:27:03 -04:00

128 lines
6.6 KiB
PowerShell

#region Get-HyperVNextAvailableMACAddress
Function Get-HyperVNextAvailableMACAddress
{
<#
.SYNOPSIS
Retrieves the next media access control address that the hypervisor will hand to a virtual machine.
.DESCRIPTION
The hypervisor allocates dynamic media access control addresses out of a pool and records the next value it will issue within the registry.
Reading that value allows an address to be reserved within the appliance before the virtual machine that will carry it has been created, which is how a preboot execution environment server is given a predictable address ahead of time.
.PARAMETER Offset
The number of addresses to advance beyond the next available address. Specify a value greater than zero when the next address is expected to be consumed by another virtual machine first.
.PARAMETER ContinueOnError
Ignore failures.
.EXAMPLE
$GetHyperVNextAvailableMACAddressResult = Get-HyperVNextAvailableMACAddress -Verbose
Write-Output -InputObject ($GetHyperVNextAvailableMACAddressResult.Address)
.NOTES
The registry value only exists once the hypervisor has been installed. The function returns a null address rather than throwing when the value cannot be read, so that a configuration document can still be generated on a device that does not host the hypervisor.
The address is a prediction rather than a guarantee. The hypervisor issues it to whichever virtual machine requests an address next, so a reservation that is built from it should be verified once the intended virtual machine exists.
.LINK
https://learn.microsoft.com/en-us/windows-server/virtualization/hyper-v/deploy/configure-mac-address-ranges
#>
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$False)]
[ValidateRange(0, 65535)]
[Alias('O')]
[System.Int32]$Offset,
[Parameter(Mandatory=$False)]
[Alias('COE')]
[Switch]$ContinueOnError
)
Try
{
[System.String]$CmdletName = $MyInvocation.MyCommand.Name
$WriteLogMessage.Invoke(0, @("Function `'$($CmdletName)`' is beginning. Please Wait..."))
$OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
$OutputObjectProperties.Address = $Null
$OutputObjectProperties.AddressWithoutSeparator = $Null
$OutputObjectProperties.WasResolved = $False
[System.String]$VirtualizationWorkerRegistryPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Worker'
$WriteLogMessage.Invoke(0, @("Attempting to read the next available media access control address from the hypervisor. Please Wait... [Path: $($VirtualizationWorkerRegistryPath)]"))
[Byte[]]$AddressBytes = Try {(Get-ItemProperty -Path ($VirtualizationWorkerRegistryPath) -Name 'CurrentMacAddress' -ErrorAction SilentlyContinue).CurrentMacAddress} Catch {$Null}
Switch (($Null -ine $AddressBytes) -and ($AddressBytes.Count -eq 6))
{
{($_ -eq $True)}
{
#region Advance the address by the requested offset
# The address is treated as a single unsigned integer so that a carry across the final octets is handled correctly.
[System.Int64]$AddressValue = 0
For ($AddressByteIndex = 0; $AddressByteIndex -lt $AddressBytes.Count; $AddressByteIndex++)
{
[System.Int64]$AddressValue = ($AddressValue * 256) + $AddressBytes[$AddressByteIndex]
}
[System.Int64]$AddressValue = $AddressValue + $Offset
$ResolvedAddressBytes = New-Object -TypeName 'System.Byte[]' -ArgumentList (6)
For ($AddressByteIndex = 5; $AddressByteIndex -ge 0; $AddressByteIndex--)
{
$ResolvedAddressBytes[$AddressByteIndex] = [System.Byte]($AddressValue % 256)
[System.Int64]$AddressValue = [System.Math]::Floor($AddressValue / 256)
}
#endregion
$OutputObjectProperties.Address = ([System.BitConverter]::ToString($ResolvedAddressBytes)).Replace('-', ':').ToLower()
$OutputObjectProperties.AddressWithoutSeparator = ([System.BitConverter]::ToString($ResolvedAddressBytes)).Replace('-', '').ToLower()
$OutputObjectProperties.WasResolved = $True
$WriteLogMessage.Invoke(0, @("The next available media access control address is `"$($OutputObjectProperties.Address)`". [Offset: $($Offset)]"))
}
{($_ -eq $False)}
{
$WriteLogMessage.Invoke(2, @("The next available media access control address could not be read. This is expected on a device that does not host the hypervisor."))
}
}
$OutputObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($OutputObjectProperties)
Write-Output -InputObject ($OutputObject)
}
Catch
{
$ErrorRecord = $_
Switch ($ContinueOnError.IsPresent)
{
{($_ -eq $True)}
{
$WriteLogMessage.Invoke(2, @("[Message: $($ErrorRecord.Exception.Message)] [LineNumber: $($ErrorRecord.InvocationInfo.ScriptLineNumber)] [Code: $($ErrorRecord.InvocationInfo.Line.Trim())]"))
}
{($_ -eq $False)}
{
Throw
}
}
}
Finally
{
$WriteLogMessage.Invoke(0, @("Function `'$($CmdletName)`' is completed."))
}
}
#endregion