public/New-OSDeployHyperVM.ps1
|
#Requires -PSEdition Core #Requires -Version 7.4 <# .SYNOPSIS Creates a Hyper-V virtual machine for OSDeploy workflows. .DESCRIPTION Creates a new Hyper-V VM with configurable generation, memory, CPU, switch, and VHD size. If -ISO is provided, the ISO is mounted to the VM's DVD drive. If -ISO is not provided, an empty DVD drive is created. .PARAMETER ISO Optional path to a boot ISO to mount on the VM DVD drive. If omitted, the function searches for the latest bootmedia.iso under $env:ProgramData\OSDeployCore\boot-media\ and mounts it automatically. If none is found the VM is created with an empty DVD drive. .PARAMETER NamePrefix Prefix used to generate the VM name. The final name is timestamped. .PARAMETER Generation VM generation to create. Valid values are 1 or 2. .PARAMETER MemoryStartupGB Startup memory in GB. .PARAMETER ProcessorCount Number of virtual processors. .PARAMETER VHDSizeGB Virtual disk size in GB. .PARAMETER DisplayResolution Display resolution to set on the VM video adapter. .PARAMETER SwitchName Virtual switch name. If omitted, the function tries to use 'Default Switch', then falls back to the first available switch, otherwise creates an unconnected VM. .PARAMETER CheckpointVM Create an initial checkpoint after VM creation. .PARAMETER SecureBootTemplate Secure Boot template to apply on Generation 2 VMs. Valid values: MicrosoftWindows, MicrosoftUEFICertificateAuthority, OpenSourceShieldedVM. Default is MicrosoftWindows. .PARAMETER StartVM Start the VM after creation. .OUTPUTS System.Management.Automation.PSCustomObject .INPUTS None. This function does not accept pipeline input. .NOTES Author: David Segura Company: Recast Software Requires: PowerShell 7.6+, Hyper-V enabled, Run as Administrator .EXAMPLE New-OSDeployHyperVM Creates a VM with an empty DVD drive. .EXAMPLE New-OSDeployHyperVM -ISO 'D:\ISO\WinPE.iso' -StartVM $true Creates a VM and mounts the specified ISO. #> function New-OSDeployHyperVM { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')] [OutputType([pscustomobject])] param ( [Parameter()] [ValidateScript({ if ([string]::IsNullOrWhiteSpace($_)) { return $true } if (-not (Test-Path -Path $_ -PathType Leaf)) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] ISO path was not found: $_" } if ([IO.Path]::GetExtension($_) -notmatch '^\.iso$') { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] ISO must point to an .iso file: $_" } return $true })] [string]$ISO, [Parameter()] [string]$NamePrefix = 'OSDeploy', [Parameter()] [ValidateSet('1','2')] [UInt16]$Generation = 2, [Parameter()] [ValidateRange(2, 64)] [UInt16]$MemoryStartupGB = 8, [Parameter()] [ValidateRange(1, 64)] [UInt16]$ProcessorCount = 2, [Parameter()] [ValidateRange(8, 512)] [UInt16]$VHDSizeGB = 64, [Parameter()] [ValidateSet('640x480','800x600','1024x768','1152x864','1280x720', '1280x768','1280x800','1280x960','1280x1024','1360x768','1366x768', '1400x1050','1440x900','1600x900','1680x1050','1920x1080','1920x1200', '2560x1440','2560x1600','3840x2160','3840x2400','4096x2160')] [string]$DisplayResolution = '1600x900', [Parameter()] [string]$SwitchName, [Parameter()] [ValidateSet('MicrosoftWindows', 'MicrosoftUEFICertificateAuthority', 'OpenSourceShieldedVM')] [string]$SecureBootTemplate = 'MicrosoftWindows', [Parameter()] [bool]$CheckpointVM = $true, [Parameter()] [bool]$StartVM = $true ) if (-not $IsWindows) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] New-OSDeployVM is supported only on Windows." } $currentPrincipal = [Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() if (-not $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] New-OSDeployVM requires Administrator rights. Re-run PowerShell as Administrator and try again." } if ([string]::IsNullOrWhiteSpace($ISO)) { $bootImageRoot = Join-Path -Path $env:ProgramData -ChildPath 'OSDeployCore\boot-media' $latestISO = Get-ChildItem -Path $bootImageRoot -Filter 'bootmedia.iso' -Recurse -ErrorAction SilentlyContinue | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 if ($latestISO) { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Auto-selected ISO: $($latestISO.FullName)" $ISO = $latestISO.FullName } else { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] No bootmedia.iso found in $bootImageRoot; VM will be created with an empty DVD drive." } } $requiredCommands = @( 'New-VM', 'Get-VMHost', 'Add-VMDvdDrive', 'Set-VMFirmware', 'Set-VMVideo', 'Get-VMIntegrationService', 'Enable-VMIntegrationService', 'Set-VMProcessor', 'Set-VMMemory', 'Set-VM', 'Get-VM', 'Start-VM', 'Checkpoint-VM', 'Get-VMSwitch' ) foreach ($commandName in $requiredCommands) { if (-not (Get-Command -Name $commandName -ErrorAction SilentlyContinue)) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Required Hyper-V command '$commandName' was not found. Ensure Hyper-V PowerShell tools are installed." } } $hyperVState = Get-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Hyper-V-All' -ErrorAction SilentlyContinue if (-not $hyperVState -or $hyperVState.State -notin @('Enabled', 'EnablePending')) { throw "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Hyper-V is not enabled. Run Install-OSDeployHyperV first. Current state: $($hyperVState.State)" } $availableSwitches = Get-VMSwitch -ErrorAction SilentlyContinue if (-not $SwitchName -and $availableSwitches) { $defaultSwitch = $availableSwitches | Where-Object { $_.Name -eq 'Default Switch' } | Select-Object -First 1 if ($defaultSwitch) { $SwitchName = $defaultSwitch.Name } else { $SwitchName = ($availableSwitches | Select-Object -First 1).Name } } $vmName = "$((Get-Date).ToString('yyMMdd-HHmmss')) $NamePrefix" $vmHost = Get-VMHost -ErrorAction Stop $vhdPath = Join-Path -Path $vmHost.VirtualHardDiskPath -ChildPath "$vmName.vhdx" $memoryStartupBytes = $MemoryStartupGB * 1GB $vhdSizeBytes = $VHDSizeGB * 1GB if (-not $PSCmdlet.ShouldProcess($vmName, 'Create and configure Hyper-V virtual machine')) { return [pscustomobject]@{ VMName = $vmName ISOPath = $ISO VHDPath = $vhdPath SwitchName = $SwitchName Generation = $Generation MemoryStartupGB= $MemoryStartupGB ProcessorCount = $ProcessorCount VHDSizeGB = $VHDSizeGB Created = $false Started = $false Checkpointed = $false StartVMConnect = $false } } if ($SwitchName) { $vm = New-VM -Name $vmName -Generation $Generation -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $vhdSizeBytes -SwitchName $SwitchName -ErrorAction Stop } else { $vm = New-VM -Name $vmName -Generation $Generation -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $vhdSizeBytes -ErrorAction Stop } if ($ISO) { $dvdDrive = $vm | Add-VMDvdDrive -Path $ISO -Passthru -ErrorAction Stop } else { $dvdDrive = $vm | Add-VMDvdDrive -Passthru -ErrorAction Stop } if ($Generation -eq 2 -and $dvdDrive) { $vm | Set-VMFirmware -FirstBootDevice $dvdDrive -EnableSecureBoot On -SecureBootTemplate $SecureBootTemplate -ErrorAction Stop if (Get-Command -Name 'Get-TPM' -ErrorAction SilentlyContinue) { $tpm = Get-TPM if ($tpm.TpmPresent -eq $true -and $tpm.TpmReady -eq $true) { if (Get-Command -Name 'Set-VMSecurity' -ErrorAction SilentlyContinue) { $vm | Set-VMSecurity -VirtualizationBasedSecurityOptOut:$false -ErrorAction Stop } if (Get-Command -Name 'Set-VMKeyProtector' -ErrorAction SilentlyContinue) { $vm | Set-VMKeyProtector -NewLocalKeyProtector -ErrorAction Stop } if (Get-Command -Name 'Enable-VMTPM' -ErrorAction SilentlyContinue) { $vm | Enable-VMTPM -ErrorAction Stop } } } } $vm | Set-VMMemory -DynamicMemoryEnabled $false -ErrorAction Stop $vm | Set-VMProcessor -Count $ProcessorCount -ErrorAction Stop $horizontalResolution = [int]($DisplayResolution.Split('x')[0]) $verticalResolution = [int]($DisplayResolution.Split('x')[1]) $vm | Set-VMVideo -HorizontalResolution $horizontalResolution -VerticalResolution $verticalResolution -ResolutionType Single -ErrorAction Stop $integrationService = Get-VMIntegrationService -VMName $vm.Name -ErrorAction SilentlyContinue | Where-Object { $_ -match 'Microsoft:[0-9A-Fa-f]{8}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{4}[-][0-9A-Fa-f]{12}\\6C09BB55-D683-4DA0-8931-C9BF705F6480' } if ($integrationService) { $vm | Get-VMIntegrationService -Name $integrationService.Name | Enable-VMIntegrationService -ErrorAction SilentlyContinue } $vm | Set-VM -AutomaticCheckpointsEnabled $false -AutomaticStartAction Nothing -AutomaticStartDelay 3 -AutomaticStopAction Shutdown -ErrorAction Stop $checkpointed = $false if ($CheckpointVM) { $vm | Checkpoint-VM -SnapshotName 'New-OSDeployHyperVM' -ErrorAction Stop $checkpointed = $true } $started = $false $startVmConnnect = $false if ($StartVM) { if (Get-Command -Name 'vmconnect.exe' -ErrorAction SilentlyContinue) { vmconnect.exe $env:ComputerName $vmName Start-Sleep -Seconds 10 $startVmConnnect = $true } $vm | Start-VM -ErrorAction Stop $started = $true } $finalVm = Get-VM -Name $vmName -ErrorAction Stop [pscustomobject]@{ VMName = $finalVm.Name ISOPath = if ($ISO) { $ISO } else { $null } VHDPath = $vhdPath SwitchName = $SwitchName Generation = $Generation MemoryStartupGB = $MemoryStartupGB ProcessorCount = $ProcessorCount VHDSizeGB = $VHDSizeGB DisplayResolution = $DisplayResolution Created = $true Started = $started Checkpointed = $checkpointed StartVMConnect = $startVmConnnect } } Register-ArgumentCompleter -CommandName New-OSDeployHyperVM -ParameterName 'SwitchName' -ScriptBlock { Get-VMSwitch | Select-Object -ExpandProperty Name | ForEach-Object { if ($_.Contains(' ')) { "'$_'" } else { $_ } } } |