bc3dd6c535
Deploys a fully preconfigured OPNsense appliance into a Hyper-V lab in a single execution, re-running safely because every stage detects the current state before it acts. Main script: - Hyper-V platform detection and installation, exiting 3010 only when the hypervisor itself needs a restart - Random /20 block selection out of a private base network, divided into /24 networks whose VLAN tag is the third octet of their own network address - Zone based roles, with five server zones paired by index to five client zones, plus Management, Infrastructure, DMZ, Storage and Guest - Generated OPNsense config.xml delivered on a FAT32 VHDX at conf/config.xml - Generation 2 virtual machine with secure boot disabled and a LAN trunk carrying VLANs 1-4094 - Marker scoped teardown via RemoveExistingDeployment Toolkit functions: - Save-ToolkitModule, Install-HyperVPlatform, Test-PendingReboot - Expand-CompressedFile, Get-OPNSenseInstallationMedia - Get-HyperVStorageLocation, Get-HostUpstreamDNSConfiguration - New-RandomPassword, New-OPNSensePasswordHash - New-OPNSenseNetworkPlan, New-OPNSenseConfigurationDocument, Save-OPNSenseConfigurationDocument, New-OPNSenseConfigurationDisk - Initialize-OPNSenseVirtualSwitch, New-OPNSenseVirtualMachine, Remove-OPNSenseDeployment Configuration document covers interfaces, VLANs, Kea DHCPv4 scopes with PXE options, Unbound, outbound NAT, six firewall aliases and an ordered rule set that grants management full reach, allows the jump hosts over well known management ports, forces name resolution to approved resolvers, and pairs the client and server zones. Bundles 7-Zip, because the tar.exe included with Windows cannot read a raw bzip2 stream, and BCrypt.Net-Next for the appliance password hash. docs: Add readme with execution flow and generated per function reference docs: Add design specification under .ai/specification Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
294 lines
18 KiB
PowerShell
294 lines
18 KiB
PowerShell
#region Install-HyperVPlatform
|
|
Function Install-HyperVPlatform
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Idempotently detects and installs the Microsoft Hyper-V platform and its powershell management module.
|
|
|
|
.DESCRIPTION
|
|
The client and server operating system product types expose the hypervisor through different feature management interfaces, so the correct interface is selected automatically.
|
|
Nothing is installed when every required feature is already enabled. When an installation occurs and the operating system requests a restart, the returned object reports that a restart is required so that the calling script can exit with the appropriate exit code.
|
|
|
|
.PARAMETER SkipVirtualizationSupportCheck
|
|
Do not verify that the processor exposes the virtualization extensions that are required by the hypervisor.
|
|
|
|
.PARAMETER ContinueOnError
|
|
Ignore failures.
|
|
|
|
.EXAMPLE
|
|
$InstallHyperVPlatformResult = Install-HyperVPlatform -Verbose
|
|
|
|
Switch ($InstallHyperVPlatformResult.RebootRequired)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
[System.Environment]::ExitCode = 3010
|
|
}
|
|
}
|
|
|
|
.NOTES
|
|
The powershell management module is imported when the platform is already available so that the hypervisor cmdlets become immediately usable within the current session.
|
|
|
|
.LINK
|
|
https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/quick-start/enable-hyper-v
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
Param
|
|
(
|
|
[Parameter(Mandatory=$False)]
|
|
[Alias('SVSC')]
|
|
[Switch]$SkipVirtualizationSupportCheck,
|
|
|
|
[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.IsSupported = $True
|
|
$OutputObjectProperties.IsAvailable = $False
|
|
$OutputObjectProperties.WasInstalled = $False
|
|
$OutputObjectProperties.RebootRequired = $False
|
|
$OutputObjectProperties.FeatureList = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]'
|
|
|
|
$OperatingSystemInfo = Get-CIMInstance -Namespace 'Root\CIMv2' -ClassName 'Win32_OperatingSystem' -Property @('*')
|
|
|
|
$WriteLogMessage.Invoke(0, @("Operating System Product Type: $($OperatingSystemInfo.ProductType)"))
|
|
|
|
#region Determine whether the processor supports hardware virtualization
|
|
Switch ($SkipVirtualizationSupportCheck.IsPresent)
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("Attempting to determine whether the processor(s) support hardware virtualization. Please Wait..."))
|
|
|
|
$ProcessorInfoList = Get-CIMInstance -Namespace 'Root\CIMv2' -ClassName 'Win32_Processor' -Property @('*')
|
|
|
|
$VirtualizationCapableProcessorList = $ProcessorInfoList | Where-Object {($_.VirtualizationFirmwareEnabled -eq $True) -or ($_.SecondLevelAddressTranslationExtensions -eq $True) -or ($_.VMMonitorModeExtensions -eq $True)}
|
|
|
|
$OutputObjectProperties.IsSupported = (($VirtualizationCapableProcessorList | Measure-Object).Count -gt 0)
|
|
|
|
Switch ($OutputObjectProperties.IsSupported)
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$WriteLogMessage.Invoke(2, @("The processor(s) within this device do not report support for hardware virtualization. The hypervisor may already be present, which hides these properties from the operating system."))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Determine the required feature list based upon the operating system product type
|
|
$RequiredFeatureList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
|
|
|
Switch ($OperatingSystemInfo.ProductType)
|
|
{
|
|
{($_ -in @(1))}
|
|
{
|
|
[System.String]$FeatureManagementInterface = 'WindowsOptionalFeature'
|
|
|
|
$RequiredFeatureList.Add('Microsoft-Hyper-V-All')
|
|
$RequiredFeatureList.Add('Microsoft-Hyper-V')
|
|
$RequiredFeatureList.Add('Microsoft-Hyper-V-Management-PowerShell')
|
|
}
|
|
|
|
{($_ -in @(2, 3))}
|
|
{
|
|
[System.String]$FeatureManagementInterface = 'WindowsFeature'
|
|
|
|
$RequiredFeatureList.Add('Hyper-V')
|
|
$RequiredFeatureList.Add('Hyper-V-PowerShell')
|
|
}
|
|
}
|
|
|
|
$WriteLogMessage.Invoke(0, @("Feature Management Interface: $($FeatureManagementInterface)"))
|
|
|
|
$WriteLogMessage.Invoke(0, @("Required Feature List: $($RequiredFeatureList -Join '; ')"))
|
|
#endregion
|
|
|
|
For ($RequiredFeatureListIndex = 0; $RequiredFeatureListIndex -lt $RequiredFeatureList.Count; $RequiredFeatureListIndex++)
|
|
{
|
|
[System.String]$RequiredFeature = $RequiredFeatureList[$RequiredFeatureListIndex]
|
|
|
|
$FeatureObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$FeatureObjectProperties.Name = $RequiredFeature
|
|
$FeatureObjectProperties.WasAlreadyEnabled = $False
|
|
$FeatureObjectProperties.WasInstalled = $False
|
|
$FeatureObjectProperties.RebootRequired = $False
|
|
|
|
Switch ($FeatureManagementInterface)
|
|
{
|
|
{($_ -ieq 'WindowsOptionalFeature')}
|
|
{
|
|
$FeatureDetails = Try {Get-WindowsOptionalFeature -Online -FeatureName ($RequiredFeature) -ErrorAction SilentlyContinue -Verbose:$False} Catch {$Null}
|
|
|
|
$FeatureObjectProperties.WasAlreadyEnabled = (($Null -ine $FeatureDetails) -and ($FeatureDetails.State -iin @('Enabled', 'EnablePending')))
|
|
|
|
Switch ($FeatureObjectProperties.WasAlreadyEnabled)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("Skipping the installation of the `"$($RequiredFeature)`" feature. [Reason: The feature state is already `"$($FeatureDetails.State)`".]"))
|
|
|
|
$FeatureObjectProperties.RebootRequired = ($FeatureDetails.State -ieq 'EnablePending')
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("Attempting to install the `"$($RequiredFeature)`" feature. Please Wait..."))
|
|
|
|
$EnableWindowsOptionalFeatureParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$EnableWindowsOptionalFeatureParameters.Online = $True
|
|
$EnableWindowsOptionalFeatureParameters.FeatureName = $RequiredFeature
|
|
$EnableWindowsOptionalFeatureParameters.All = $True
|
|
$EnableWindowsOptionalFeatureParameters.NoRestart = $True
|
|
$EnableWindowsOptionalFeatureParameters.WarningAction = 'SilentlyContinue'
|
|
$EnableWindowsOptionalFeatureParameters.ErrorAction = 'Stop'
|
|
$EnableWindowsOptionalFeatureParameters.Verbose = $False
|
|
|
|
$EnableWindowsOptionalFeatureResult = Enable-WindowsOptionalFeature @EnableWindowsOptionalFeatureParameters
|
|
|
|
$FeatureObjectProperties.WasInstalled = $True
|
|
$FeatureObjectProperties.RebootRequired = ($EnableWindowsOptionalFeatureResult.RestartNeeded -eq $True)
|
|
|
|
$WriteLogMessage.Invoke(0, @("The installation of the `"$($RequiredFeature)`" feature is complete. [Restart Needed: $($FeatureObjectProperties.RebootRequired)]"))
|
|
}
|
|
}
|
|
}
|
|
|
|
{($_ -ieq 'WindowsFeature')}
|
|
{
|
|
$FeatureDetails = Try {Get-WindowsFeature -Name ($RequiredFeature) -ErrorAction SilentlyContinue -Verbose:$False} Catch {$Null}
|
|
|
|
$FeatureObjectProperties.WasAlreadyEnabled = (($Null -ine $FeatureDetails) -and ($FeatureDetails.InstallState -iin @('Installed', 'InstallPending')))
|
|
|
|
Switch ($FeatureObjectProperties.WasAlreadyEnabled)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("Skipping the installation of the `"$($RequiredFeature)`" feature. [Reason: The feature install state is already `"$($FeatureDetails.InstallState)`".]"))
|
|
|
|
$FeatureObjectProperties.RebootRequired = ($FeatureDetails.InstallState -ieq 'InstallPending')
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("Attempting to install the `"$($RequiredFeature)`" feature. Please Wait..."))
|
|
|
|
$InstallWindowsFeatureParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
|
$InstallWindowsFeatureParameters.Name = $RequiredFeature
|
|
$InstallWindowsFeatureParameters.IncludeManagementTools = $True
|
|
$InstallWindowsFeatureParameters.Restart = $False
|
|
$InstallWindowsFeatureParameters.WarningAction = 'SilentlyContinue'
|
|
$InstallWindowsFeatureParameters.ErrorAction = 'Stop'
|
|
$InstallWindowsFeatureParameters.Verbose = $False
|
|
|
|
$InstallWindowsFeatureResult = Install-WindowsFeature @InstallWindowsFeatureParameters
|
|
|
|
$FeatureObjectProperties.WasInstalled = $True
|
|
$FeatureObjectProperties.RebootRequired = ($InstallWindowsFeatureResult.RestartNeeded -iin @('Yes', 'YesPending'))
|
|
|
|
$WriteLogMessage.Invoke(0, @("The installation of the `"$($RequiredFeature)`" feature is complete. [Restart Needed: $($FeatureObjectProperties.RebootRequired)]"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Switch ($True)
|
|
{
|
|
{($FeatureObjectProperties.WasInstalled -eq $True)}
|
|
{
|
|
$OutputObjectProperties.WasInstalled = $True
|
|
}
|
|
|
|
{($FeatureObjectProperties.RebootRequired -eq $True)}
|
|
{
|
|
$OutputObjectProperties.RebootRequired = $True
|
|
}
|
|
}
|
|
|
|
$OutputObjectProperties.FeatureList.Add((New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($FeatureObjectProperties)))
|
|
}
|
|
|
|
#region Determine whether the hypervisor management cmdlets are usable within the current session
|
|
Switch ($OutputObjectProperties.RebootRequired)
|
|
{
|
|
{($_ -eq $False)}
|
|
{
|
|
$HyperVModule = Try {Get-Module -Name 'Hyper-V' -ListAvailable -ErrorAction SilentlyContinue | Sort-Object -Property @('Version') -Descending | Select-Object -First 1} Catch {$Null}
|
|
|
|
Switch ($Null -ine $HyperVModule)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("Attempting to import the `"Hyper-V`" powershell module [Version: $($HyperVModule.Version.ToString())]. Please Wait..."))
|
|
|
|
$Null = Import-Module -Name 'Hyper-V' -Global -DisableNameChecking -Force -Verbose:$False -ErrorAction Stop
|
|
|
|
$VMHostDetails = Try {Hyper-V\Get-VMHost -ErrorAction SilentlyContinue} Catch {$Null}
|
|
|
|
$OutputObjectProperties.IsAvailable = ($Null -ine $VMHostDetails)
|
|
|
|
Switch ($OutputObjectProperties.IsAvailable)
|
|
{
|
|
{($_ -eq $True)}
|
|
{
|
|
$WriteLogMessage.Invoke(0, @("The hypervisor is available. [Host: $($VMHostDetails.Name)] [Logical Processors: $($VMHostDetails.LogicalProcessorCount)] [Memory Capacity: $([System.Math]::Round($VMHostDetails.MemoryCapacity / 1GB, 2)) GB]"))
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
$WriteLogMessage.Invoke(2, @("The hypervisor management service did not respond. A restart is most likely required before the hypervisor becomes available."))
|
|
|
|
$OutputObjectProperties.RebootRequired = $True
|
|
}
|
|
}
|
|
}
|
|
|
|
{($_ -eq $False)}
|
|
{
|
|
$WriteLogMessage.Invoke(2, @("The `"Hyper-V`" powershell module could not be located. A restart is most likely required before the module becomes available."))
|
|
|
|
$OutputObjectProperties.RebootRequired = $True
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
$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
|