VMWare.Templates.psm1
|
using module .\Classes\VMWare.Auth.psm1 using module .\VMWare.Authentication.psm1 <# .SYNOPSIS Builds the folder path (relative to the datacenter's "vm" root) by walking up the Parent chain. #> function Get-FolderPath { param($Folder) $names = @() $current = $Folder while ($null -ne $current -and $current.GetType().Name -eq 'FolderImpl') { # Stop at the hidden "vm" root folder of the datacenter; it is not part of the user-facing path. if ($current.Name -eq 'vm' -and $current.Parent.GetType().Name -eq 'DatacenterImpl') { break } $names = , $current.Name + $names $current = $current.Parent } return ($names -join '/') } <# .SYNOPSIS Validates the folder's ancestry level by level against the expected path segments. e.g. for 'parent1/parent2/child' it confirms Child's parent is Parent2, and Parent2's parent is Parent1. #> function Test-FolderAncestry { [CmdletBinding()] [OutputType([bool])] param($Folder, [string[]]$ExpectedSegments) Write-Verbose "Validating candidate '$($Folder.Name)' (Id: $($Folder.Id)) against path '$($ExpectedSegments -join '/')'." $current = $Folder # Walk from the leaf up to the root, comparing each expected segment in reverse order. for ($i = $ExpectedSegments.Length - 1; $i -ge 0; $i--) { $expected = $ExpectedSegments[$i] if ($null -eq $current -or $current.GetType().Name -ne 'FolderImpl') { Write-Verbose " Level $i : ran out of parent folders while expecting '$expected'. FAIL" return $false } Write-Verbose " Level $i : expecting '$expected', at folder '$($current.Name)'." if ($current.Name -ne $expected) { Write-Verbose " Level $i : mismatch - expected '$expected' but found '$($current.Name)'. FAIL" return $false } Write-Verbose " Level $i : matched '$expected'. Moving up to parent '$($current.Parent.Name)'." $current = $current.Parent } Write-Verbose "Candidate '$($Folder.Name)' matched the full ancestry. PASS" return $true } <# .SYNOPSIS Resolves a full VM folder path (e.g. 'parent1/parent2/child') to the single folder whose ancestry matches the path, disambiguating between folders that share the same leaf name. #> function Resolve-VMFolder { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [String]$Path ) $splitFolder = $Path -split '/' $leafName = $splitFolder[-1] # All folders matching the leaf name (there may be several across different locations). $candidates = @(Get-Folder -Name $leafName -Type 'VM') # Keep the first one whose full ancestry matches (leaf -> ... -> root), then stop looking. $matched = $null foreach ($candidate in $candidates) { if (Test-FolderAncestry -Folder $candidate -ExpectedSegments $splitFolder) { $matched = $candidate break } } if (-not $matched) { Write-Warning "No VM folder found matching path '$Path'. Candidates found: $($candidates.Count)" $candidates | ForEach-Object { Write-Warning " - $(Get-FolderPath -Folder $_)" } throw "Unable to resolve VM folder path '$Path'." } return $matched } <# .SYNOPSIS Creates a VM based on a template. Will prompt the user for all settings needed. #> function New-VMFromTemplate { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='High')] param( [Parameter(Mandatory = $false)] [String]$ServerName = '', [Parameter(Mandatory = $false)] [String]$ServerNameRegex = '^[A-Z]{2}-[A-Z]{2}-[A-Z]{2}-[A-Z]-[A-Z]{2}[0-9]{2}$', [Parameter(Mandatory = $false)] [String]$OSCustomizationSpec, [Parameter(Mandatory = $false)] [String]$Template, [Parameter(Mandatory = $false)] [String]$Cluster, [Parameter(Mandatory = $false)] [String]$VirtualNetwork, [Parameter(Mandatory = $false)] [String]$DataStore, [Parameter(Mandatory = $false)] [Int]$CPUCores, [Parameter(Mandatory = $false)] [Int]$MemoryGB, [Parameter(Mandatory = $false)] [String]$Folder ) $errorPref = $ErrorActionPreference $ErrorActionPreference = 'Stop' # Stop on all errors # Connect to VSphere [VMWareAuth]::GetInstance() | Out-Null # Get settings to use - or use defaults if valid $_Spec = Select-FromObjectList(Get-OSCustomizationSpec) -DefaultValue $OSCustomizationSpec $_Template = Select-FromObjectList(Get-Template) -DefaultValue $Template $_Cluster = Select-FromObjectList(Get-Cluster) -DefaultValue $Cluster $_Network = Select-FromObjectList(Get-VirtualNetwork) -DefaultValue $VirtualNetwork $_DataStore = Select-FromObjectList(Get-Datastore) -DefaultValue $DataStore $_Cores = Read-ValidInput -Prompt 'Enter Number of CPU Cores' -Regex '^2|4|6|8|10|12|16|20|24|32$' -DefaultValue $CPUCores $_MemoryGB = Read-ValidInput -Prompt 'Enter GB of Memory' -Regex '^([1-4][0-9])|[2-9]$' -DefaultValue $MemoryGB $_Folder = if([string]::IsNullOrEmpty($Folder)) { $null } else { Resolve-VMFolder -Path $Folder } # Get and confirm name $_ServerName = Read-ValidInput -Prompt 'Enter Server Name' -Regex $ServerNameRegex -DefaultValue $ServerName Write-Output "Validating $_ServerName is available..." $dnsResult = Resolve-DnsName $_ServerName if ($null -ne $dnsResult) { throw 'There appears to already be a DNS record for that machine name. Confirm it is available to use.' } # Confirm Settings Write-Output @{ Spec = $_Spec.Name Template = $_Template.Name Cluster = $_Cluster.Name Network = $_Network.Name DataStore = $_DataStore.Name ServerName = $_ServerName CPUCores = $_Cores MemoryGB = $_MemoryGB Folder = if ($null -ne $_Folder) { $_Folder } else { 'Default' } } if ($pscmdlet.ShouldProcess($_ServerName, 'create')){ # Create VM $_LocationParam = if ($null -ne $_Folder) { @{ Location = $_Folder } } else { @{} } $VM = New-VM -Name $_ServerName -Template $_Template -OSCustomizationSpec $_Spec -ResourcePool $_Cluster -Datastore $_DataStore.Name @_LocationParam -Confirm:$false # Set Network $Adapters = Get-NetworkAdapter $VM $Adapters | Set-NetworkAdapter -NetworkName $_Network -Confirm:$false | Out-Null # Set CPU / Memory Set-VM -VM $VM -NumCpu $_Cores -CoresPerSocket $($_Cores / 2) -MemoryGB $_MemoryGB -Confirm:$false | Out-Null # Start VM Start-VM $VM -Confirm:$false } $ErrorActionPreference = $errorPref # Set back to previous value } |