Public/New-PVEVM.ps1
|
function New-PVEVM { <# .SYNOPSIS Creates a QEMU virtual machine. .DESCRIPTION Creates the VM with POST /nodes/{node}/qemu, waits for the create task, and writes the new VM with its live status. Options not given are left to PVE's own defaults (for example 512 MiB of memory, one core, no disk, no network device). What the parameters add to the configuration: - -DiskGB: one new disk per value on -Storage, on the -DiskBus bus (scsi0, scsi1, ...). With the scsi bus the controller is set to virtio-scsi-single (PVE's own default is lsi). - -NetworkBridge: one network device per bridge (net0, net1, ...), of model -NetworkModel. - -IsoImage: a CD drive (ide2) with that ISO. - -Bios ovmf: also an EFI vars disk (efidisk0, efitype 4m) on -Storage. - A boot order of the first disk, then the CD drive, then the first network device, for the ones that exist. - -Parameter: any other create options, by their API names. Applied last, so they override the above. .PARAMETER Name VM name. PVE requires a valid DNS name. .PARAMETER VMId ID for the VM. Defaults to the next free ID. PVE checks a given ID is free before the VM is created. .PARAMETER Node Node to create the VM on. Can be omitted when the server has a single node. .PARAMETER Cores Cores per socket. .PARAMETER Sockets CPU sockets. .PARAMETER MemoryGB Memory in GiB, sent to PVE in whole MiB (for example 1.5 is 1536 MiB). .PARAMETER DiskGB Size in GiB of each new disk to create on -Storage. One disk per value. .PARAMETER DiskBus Bus for the new disks: scsi (the default), virtio, sata or ide. .PARAMETER Storage Storage for the new disks and the EFI vars disk. Required with -DiskGB and with -Bios ovmf. .PARAMETER NetworkBridge Bridge for each network device, for example vmbr0. One device per value. .PARAMETER NetworkModel Network device model. Defaults to virtio. .PARAMETER IsoImage ISO volume to insert in a CD drive, for example local:iso/debian-13.1.0-amd64-netinst.iso. .PARAMETER OSType Guest OS type, which PVE uses to enable OS-specific optimisations. From the PVE API documentation: l26 is a Linux 2.6 - 7.x kernel; win10 is Windows 10/2016/2019; win11 is Windows 11/2022/2025; other is unspecified. .PARAMETER Bios seabios (PVE's default) or ovmf (UEFI). .PARAMETER Machine QEMU machine type, for example q35 or pc (i440fx). .PARAMETER CpuType Emulated CPU type, for example host or x86-64-v2-AES. .PARAMETER GuestAgent Enable the QEMU guest agent device. The agent itself has to be installed in the guest. .PARAMETER Pool Resource pool to add the VM to. .PARAMETER Tags Tags for the VM. .PARAMETER Description Description (notes) shown in the VM's summary. .PARAMETER StartOnBoot Start the VM when the node boots. .PARAMETER PowerOn Start the VM once it has been created. .PARAMETER Parameter Further create options by their API names, for example @{ balloon = 0; vga = 'serial0' }. .PARAMETER RunAsync Return the create task at once instead of waiting. .PARAMETER Server Connection to use. Defaults to the default connection. .EXAMPLE New-PVEVM -Name web01 -Cores 2 -MemoryGB 4 -DiskGB 32 -Storage local-lvm -NetworkBridge vmbr0 -OSType l26 .EXAMPLE New-PVEVM web02 -DiskGB 32, 100 -Storage local-lvm -IsoImage local:iso/debian-13.1.0-amd64-netinst.iso -PowerOn .EXAMPLE New-PVEVM win01 -OSType win11 -Bios ovmf -Machine q35 -MemoryGB 8 -DiskGB 80 -Storage local-lvm -Parameter @{ tpmstate0 = 'local-lvm:1,version=v2.0' } #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] [OutputType('PVEPS.VM', 'PVEPS.Task')] param( [Parameter(Mandatory, Position = 0)] [ValidateNotNullOrEmpty()] [string]$Name, [ValidateRange(100, 999999999)] [int]$VMId, [ValidateNotNullOrEmpty()] [string]$Node, [ValidateRange(1, [int]::MaxValue)] [int]$Cores, [ValidateRange(1, [int]::MaxValue)] [int]$Sockets, [ValidateRange(0.015625, [double]::MaxValue)] [double]$MemoryGB, [ValidateRange(1, [int]::MaxValue)] [int[]]$DiskGB, [ValidateSet('scsi', 'virtio', 'sata', 'ide')] [string]$DiskBus = 'scsi', [ValidateNotNullOrEmpty()] [string]$Storage, [ValidateNotNullOrEmpty()] [string[]]$NetworkBridge, [ValidateSet('e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'e1000e', 'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3')] [string]$NetworkModel = 'virtio', [ValidateNotNullOrEmpty()] [string]$IsoImage, [ValidateSet('other', 'wxp', 'w2k', 'w2k3', 'w2k8', 'wvista', 'win7', 'win8', 'win10', 'win11', 'l24', 'l26', 'solaris')] [string]$OSType, [ValidateSet('seabios', 'ovmf')] [string]$Bios, [ValidateNotNullOrEmpty()] [string]$Machine, [ValidateNotNullOrEmpty()] [string]$CpuType, [switch]$GuestAgent, [ValidateNotNullOrEmpty()] [string]$Pool, [ValidateNotNullOrEmpty()] [string[]]$Tags, [string]$Description, [switch]$StartOnBoot, [switch]$PowerOn, [hashtable]$Parameter, [switch]$RunAsync, [Alias('Connection')] [object]$Server ) # Unbound array parameters are $null, and .Count on $null throws under strict mode. $diskSizes = @(if ($DiskGB) { $DiskGB }) $bridges = @(if ($NetworkBridge) { $NetworkBridge }) if ($DiskGB -and -not $Storage) { $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message '-DiskGB needs -Storage, the storage to create the disks on.' -Target $DiskGB)) } if ($Bios -eq 'ovmf' -and -not $Storage) { $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message '-Bios ovmf needs -Storage, for the EFI vars disk.' -Target $Bios)) } $slots = @{ scsi = 31; virtio = 16; sata = 6; ide = 4 } $diskSlots = @(0..($slots[$DiskBus] - 1) | Where-Object { -not ($DiskBus -eq 'ide' -and $IsoImage -and $_ -eq 2) }) if ($diskSizes.Count -gt $diskSlots.Count) { $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message "The $DiskBus bus has room for $($diskSlots.Count) disks here; -DiskGB has $($diskSizes.Count)." -Target $DiskGB)) } foreach ($key in 'vmid', 'node') { if ($Parameter -and $Parameter.ContainsKey($key)) { $PSCmdlet.ThrowTerminatingError((New-PVEArgumentError -Message "-Parameter cannot set '$key'; use -VMId or -Node." -Target $Parameter)) } } try { $connection = Resolve-PVESingleConnection -Server $Server -Reason 'a VM is created on exactly one' $nodeName = Resolve-PVENodeName -Connection $connection -Node $Node $id = Get-PVENewVMId -Connection $connection -VMId $VMId } catch { $PSCmdlet.WriteError($_) return } $body = @{ vmid = $id; name = $Name } if ($PSBoundParameters.ContainsKey('Cores')) { $body.cores = $Cores } if ($PSBoundParameters.ContainsKey('Sockets')) { $body.sockets = $Sockets } if ($PSBoundParameters.ContainsKey('MemoryGB')) { $body.memory = ConvertTo-PVEMiB $MemoryGB } if ($OSType) { $body.ostype = $OSType } if ($Bios) { $body.bios = $Bios } if ($Machine) { $body.machine = $Machine } if ($CpuType) { $body.cpu = $CpuType } if ($GuestAgent) { $body.agent = 1 } if ($Pool) { $body.pool = $Pool } if ($Tags) { $body.tags = $Tags -join ';' } if ($PSBoundParameters.ContainsKey('Description')) { $body.description = $Description } if ($StartOnBoot) { $body.onboot = 1 } if ($PowerOn) { $body.start = 1 } $boot = [System.Collections.Generic.List[string]]::new() for ($i = 0; $i -lt $diskSizes.Count; $i++) { $key = '{0}{1}' -f $DiskBus, $diskSlots[$i] $body[$key] = '{0}:{1}' -f $Storage, $diskSizes[$i] if ($i -eq 0) { $boot.Add($key) } } if ($DiskGB -and $DiskBus -eq 'scsi') { $body.scsihw = 'virtio-scsi-single' } if ($IsoImage) { $body.ide2 = '{0},media=cdrom' -f $IsoImage $boot.Add('ide2') } for ($i = 0; $i -lt $bridges.Count; $i++) { $body["net$i"] = '{0},bridge={1}' -f $NetworkModel, $bridges[$i] if ($i -eq 0) { $boot.Add('net0') } } if ($Bios -eq 'ovmf') { $body.efidisk0 = '{0}:1,efitype=4m' -f $Storage } if ($boot.Count -gt 0) { $body.boot = 'order={0}' -f ($boot -join ';') } if ($Parameter) { foreach ($key in $Parameter.Keys) { $body[$key] = $Parameter[$key] } } $label = 'VM {0} ({1}) on {2}' -f $id, $Name, $nodeName if (-not $PSCmdlet.ShouldProcess($label, 'Create')) { return } $operations = [System.Collections.Generic.List[psobject]]::new() try { $path = Format-PVEPath '/nodes/{node}/qemu' @{ node = $nodeName } $upid = Invoke-PVERequest -Connection $connection -Method POST -Path $path -Parameters $body } catch { $PSCmdlet.WriteError($_) return } Write-Verbose "Create $label`: task $upid" $operations.Add([pscustomobject]@{ Upid = [string]$upid; Connection = $connection; Guest = $null; Type = 'qemu'; Node = $nodeName; VMId = $id; Pool = $Pool }) Complete-PVEOperation -Cmdlet $PSCmdlet -Operation $operations -RunAsync:$RunAsync } |