#Requires -Modules 'powershell-yaml' [CmdletBinding()] Param( [Parameter(Mandatory)] [ValidateSet('Bootstrap', 'Upgrade')] [string]$ApplianceType, [Parameter(Mandatory)] [ValidateScript({ If (Test-Path($_)) { $True } Else { Throw "'$_' is not a valid filename (within working directory '$PWD'), or access denied; aborting." } })] [string]$OVFFile, [hashtable]$Parameter ) $GetContentSplat = @{ Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".$($ApplianceType.ToLower()).yml") Raw = $True } $RawContent = Get-Content @GetContentSplat $ConvertFromYamlSplat = @{ Yaml = $RawContent AllDocuments = $True } $YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat # Check if the respective .yml file declared substitutions which need to be parsed If (($YamlDocuments.Count -gt 1) -and $YamlDocuments[-1].Variables) { ForEach ($Pattern in $YamlDocuments[-1].Variables) { $RawContent = $RawContent -replace "\{\{ ($($Pattern.Name)) \}\}", [string](Invoke-Expression -Command $Pattern.Expression) } # Perform conversion to Yaml again, now with parsed file contents $ConvertFromYamlSplat = @{ Yaml = $RawContent AllDocuments = $True } $YamlDocuments = ConvertFrom-Yaml @ConvertFromYamlSplat $OVFConfig = $YamlDocuments[0..($YamlDocuments.Count - 2)] } Else { $OVFConfig = $YamlDocuments } $SourceFile = Get-Item -Path $OVFFile $GetContentSplat = @{ Path = $SourceFile.FullName } $XML = [xml](Get-Content @GetContentSplat) $NS = [System.Xml.XmlNamespaceManager]$XML.NameTable [void]$NS.AddNamespace('ns', $XML.DocumentElement.xmlns) [void]$NS.AddNamespace('ovf', $XML.DocumentElement.ovf) [void]$NS.AddNamespace('rasd', $XML.DocumentElement.rasd) [void]$NS.AddNamespace('vmw', $XML.DocumentElement.vmw) # Create copy of existing 'Item/ResourceType'=17 (=Hard disk) node $XMLDiskTemplate = $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='17']", $NS).ParentNode.CloneNode($True) ForEach ($Disk in $OVFConfig.DynamicDisks) { # Determine next free available 'diskId' $XMLDisks = $XML.SelectNodes("//ns:DiskSection/ns:Disk[contains(@ovf:diskId,'vmdisk')]", $NS) $DiskId = 1 While ($XMLDisks.DiskId -contains "vmdisk$($DiskId)") { $DiskId++ } # Add new 'Disk' node (under 'DiskSection') $XMLDisk = $XML.CreateElement('Disk', $XML.DocumentElement.xmlns) $PowersMap = @{ KB = 10 MB = 20 GB = 30 TB = 40 PB = 50 } If ($PowersMap.Keys -notcontains $Disk.UnitSize) { # Invalid UnitSize; skipping adding new disk Continue } [void]$XMLDisk.SetAttribute('capacityAllocationUnits', $NS.LookupNamespace('ovf'), "byte * 2^$($PowersMap[$Disk.UnitSize])") [void]$XMLDisk.SetAttribute('format', $NS.LookupNamespace('ovf'), 'http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized') [void]$XMLDisk.SetAttribute('diskId', $NS.LookupNamespace('ovf'), "vmdisk$($DiskId)") [void]$XMLDisk.SetAttribute('capacity', $NS.LookupNamespace('ovf'), '${{vmconfig.disksize.{0}}}' -f $DiskId) [void]$XMLDisk.SetAttribute('populatedSize', $NS.LookupNamespace('ovf'), 0) [void]$XML.SelectSingleNode('//ns:DiskSection', $NS).AppendChild($XMLDisk) # Add new 'Item/ResourceType' node (under 'VirtualHardwareSection') $XMLDiskItem = $XMLDiskTemplate.CloneNode($True) $XMLDiskItem.SelectSingleNode('rasd:AddressOnParent', $NS).InnerText = ($DiskId - 1) $XMLDiskItem.SelectSingleNode('rasd:ElementName', $NS).InnerText = "Hard Disk $($DiskId)" $XMLDiskItem.SelectSingleNode('rasd:HostResource', $NS).InnerText = "ovf:/disk/vmdisk$($DiskId)" # Determine next free available and highest 'InstanceID' $InstanceIDs = $XML.SelectNodes('//ns:VirtualHardwareSection/ns:Item/rasd:InstanceID', $NS).InnerText $InstanceID = 1 While ($InstanceIDs -contains $InstanceID) { $InstanceID++ } $HighestInstanceID = ($InstanceIDs | Measure-Object -Maximum).Maximum $XMLDiskItem.SelectSingleNode('rasd:InstanceID', $NS).InnerText = $InstanceID [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).InsertAfter( $XMLDiskItem, $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:InstanceID[.='$($HighestInstanceID)']", $NS).ParentNode ) $OVFConfig.PropertyCategories[@([int]$Disk.PropertyCategory, 0)[![boolean]$Disk.PropertyCategory]].ProductProperties += @{ Key = "vmconfig.disksize.$($DiskId)" Type = If ([boolean]$Disk.Constraints.Minimum -or [boolean]$Disk.Constraints.Maximum) { "Int($($Disk.Constraints.Minimum)..$($Disk.Constraints.Maximum))" } Else { 'Int' } Label = "Disk $($DiskId) size*" Description = "$($Disk.Description) (in $($Disk.UnitSize))".Trim() DefaultValue = "$($Disk.Constraints.Minimum)" Configurations = '*' UserConfigurable = 'true' } } Write-Host "Inserted $($OVFConfig.DynamicDisks.Count) new node(s) into 'DiskSection' and 'VirtualHardwareSection' respectively" If ($OVFConfig.DeploymentConfigurations.Count -gt 0) { $XMLSection = $XML.CreateElement('DeploymentOptionSection', $XML.DocumentElement.xmlns) $XMLSectionInfo = $XML.CreateElement('Info', $XML.DocumentElement.xmlns) $XMLSectionInfo.InnerText = 'Deployment Type' [void]$XMLSection.AppendChild($XMLSectionInfo) ForEach ($Configuration in $OVFConfig.DeploymentConfigurations) { $XMLConfig = $XML.CreateElement('Configuration', $XML.DocumentElement.xmlns) [void]$XMLConfig.SetAttribute('id', $NS.LookupNamespace('ovf'), $Configuration.Id) $XMLConfigLabel = $XML.CreateElement('Label', $XML.DocumentElement.xmlns) $XMLConfigLabel.InnerText = $Configuration.Label $XMLConfigDescription = $XML.CreateElement('Description', $XML.DocumentElement.xmlns) $XMLConfigDescription.InnerText = $Configuration.Description [void]$XMLConfig.AppendChild($XMLConfigLabel) [void]$XMLConfig.AppendChild($XMLConfigDescription) [void]$XMLSection.AppendChild($XMLConfig) } [void]$XML.SelectSingleNode('//ns:Envelope', $NS).InsertAfter($XMLSection, $XML.SelectSingleNode('//ns:NetworkSection', $NS)) Write-Host "Inserted 'DeploymentOptionSection' with $($Configuration.Count) nodes" If ($OVFConfig.DeploymentConfigurations.Count -eq $OVFConfig.DeploymentConfigurations.Size.Count) { # Create copies of existing 'Item/ResourceType' nodes $XMLCPUTemplate = $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='3']", $NS).ParentNode.CloneNode($True) $XMLMemoryTemplate = $XML.SelectSingleNode("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='4']", $NS).ParentNode.CloneNode($True) # Delete existing nodes ForEach ($Node in $XML.SelectNodes("//ns:VirtualHardwareSection/ns:Item/rasd:ResourceType[.='3' or .='4']", $NS).ParentNode) { [void]$Node.ParentNode.RemoveChild($Node) } # Add adjusted 'Item/ResourceType' nodes ForEach ($Configuration in $OVFConfig.DeploymentConfigurations) { $XMLCPU = $XMLCPUTemplate.CloneNode($True) [void]$XMLCPU.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Configuration.Id) $XMLCPU.SelectSingleNode('rasd:ElementName', $NS).InnerText = '{0} virtual CPU(s)' -f $Configuration.Size.CPU $XMLCPU.SelectSingleNode('rasd:VirtualQuantity', $NS).InnerText = $Configuration.Size.CPU $XMLMemory = $XMLMemoryTemplate.CloneNode($True) [void]$XMLMemory.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Configuration.Id) $XMLMemory.SelectSingleNode('rasd:ElementName', $NS).InnerText = '{0}MB of memory' -f $Configuration.Size.Memory $XMLMemory.SelectSingleNode('rasd:VirtualQuantity', $NS).InnerText = $Configuration.Size.Memory [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).InsertAfter( $XMLCPU, $XML.SelectSingleNode('//ns:VirtualHardwareSection/ns:System', $NS) ) [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).InsertAfter( $XMLMemory, $XML.SelectSingleNode('//ns:VirtualHardwareSection/ns:System', $NS) ) } } } [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).SetAttribute('transport', $NS.LookupNamespace('ovf'), 'com.vmware.guestInfo') ForEach ($ExtraConfig in $OVFConfig.AdvancedOptions) { $XMLExtraConfig = $XML.CreateElement('vmw:ExtraConfig', $XML.DocumentElement.vmw) [void]$XMLExtraConfig.SetAttribute('required', $NS.LookupNamespace('ovf'), "$([boolean]$ExtraConfig.Required)".ToLower()) [void]$XMLExtraConfig.SetAttribute('key', $NS.LookupNamespace('vmw'), $ExtraConfig.Key) [void]$XMLExtraConfig.SetAttribute('value', $NS.LookupNamespace('vmw'), $ExtraConfig.Value) [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).AppendChild($XMLExtraConfig) } Write-Host "Added $($OVFConfig.AdvancedOptions.Count) 'vmw:ExtraConfig' node(s)" $XMLProductSection = $XML.SelectSingleNode('//ns:ProductSection', $NS) If ($XMLProductSection -eq $Null) { $XMLProductSection = $XML.CreateElement('ProductSection', $XML.DocumentElement.xmlns) [void]$XML.SelectSingleNode('//ns:VirtualSystem', $NS).AppendChild($XMLProductSection) Write-Host "Inserted 'ProductSection'" } Else { ForEach ($Child in $XMLProductSection.SelectNodes('//ns:ProductSection/child::*', $NS)) { [void]$Child.ParentNode.RemoveChild($Child) } Write-Host "Destroyed pre-existing children in 'ProductSection'" } $XMLProductSectionInfo = $XML.CreateElement('Info', $XML.DocumentElement.xmlns) $XMLProductSectionInfo.InnerText = 'Information about the installed software' [void]$XMLProductSection.AppendChild($XMLProductSectionInfo) Write-Host "Inserted new 'Info' into 'ProductSection'" ForEach ($Category in $OVFConfig.PropertyCategories) { If ($Category.Name -ne '') { $XMLCategory = $XML.CreateElement('Category', $XML.DocumentElement.xmlns) $XMLCategory.InnerText = $Category.Name [void]$XMLProductSection.AppendChild($XMLCategory) Write-Host "Inserted new 'Category' into 'ProductSection'" } ForEach ($Property in $Category.ProductProperties) { $XMLProperty = $XML.CreateElement('Property', $XML.DocumentElement.xmlns) [void]$XMLProperty.SetAttribute('key', $NS.LookupNamespace('ovf'), $Property.Key) Switch -regex ($Property.Type) { '^boolean' { [void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'boolean') } '^int' { [void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'uint16') $Qualifiers = @() If ($Property.Type -match '^int\((\d*)\.\.(\d*)\)') { If ($Matches[1]) { $Qualifiers += "MinValue($($Matches[1]))" } If ($Matches[2]) { $Qualifiers += "MaxValue($($Matches[2]))" } [void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), $Qualifiers -join ' ') } } '^ip' { [void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'string') [void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('vmw'), 'Ip') } '^password' { [void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'string') [void]$XMLProperty.SetAttribute('password', $NS.LookupNamespace('ovf'), 'true') $Qualifiers = @() If ($Property.Type -match '^password\((\d*)\.\.(\d*)\)') { If ($Matches[1]) { $Qualifiers += "MinLen($($Matches[1]))" } If ($Matches[2]) { $Qualifiers += "MaxLen($($Matches[2]))" } [void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), $Qualifiers -join ' ') } } '^string' { [void]$XMLProperty.SetAttribute('type', $NS.LookupNamespace('ovf'), 'string') $Qualifiers = @() If ($Property.Type -match '^string\((\d*)\.\.(\d*)\)') { If ($Matches[1]) { $Qualifiers += "MinLen($($Matches[1]))" } If ($Matches[2]) { $Qualifiers += "MaxLen($($Matches[2]))" } [void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), $Qualifiers -join ' ') } ElseIf ($Property.Type -match '^string\[(.*)\]') { [void]$XMLProperty.SetAttribute('qualifiers', $NS.LookupNamespace('ovf'), "ValueMap{$($Matches[1] -replace '","', '", "')}") } } } [void]$XMLProperty.SetAttribute('userConfigurable', $NS.LookupNamespace('ovf'), "$([boolean]$Property.UserConfigurable)".ToLower()) If ($Property.Type -eq 'boolean') { [void]$XMLProperty.SetAttribute('value', $NS.LookupNamespace('ovf'), "$([boolean]$Property.DefaultValue)".ToLower()) } Else { [void]$XMLProperty.SetAttribute('value', $NS.LookupNamespace('ovf'), $Property.DefaultValue) } If ($Property.Label) { $XMLPropertyLabel = $XML.CreateElement('Label', $XML.DocumentElement.xmlns) $XMLPropertyLabel.InnerText = $Property.Label [void]$XMLProperty.AppendChild($XMLPropertyLabel) } If ($Property.Description) { $XMLPropertyDescription = $XML.CreateElement('Description', $XML.DocumentElement.xmlns) $XMLPropertyDescription.InnerText = $Property.Description [void]$XMLProperty.AppendChild($XMLPropertyDescription) } If (($Property.Configurations.Count -eq 1) -and ($Property.Configurations -eq '*')) { [void]$XMLProperty.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $OVFConfig.DeploymentConfigurations.Id -join ' ') } ElseIf ($Property.Configurations.Count -gt 0) { [void]$XMLProperty.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Property.Configurations -join ' ') } If ($Property.Value.Count -eq 1) { [void]$XMLProperty.SetAttribute('value', $NS.LookupNamespace('ovf'), $Property.Value) } ElseIf ($Property.Value.Count -gt 1) { ForEach ($Value in $Property.Value) { $XMLValue = $XML.CreateElement('Value', $XML.DocumentElement.xmlns) [void]$XMLValue.SetAttribute('value', $NS.LookupNamespace('ovf'), $Value) [void]$XMLValue.SetAttribute('configuration', $NS.LookupNamespace('ovf'), $Value) [void]$XMLProperty.AppendChild($XMLValue) } } [void]$XMLProductSection.AppendChild($XMLProperty) } Write-Host "Inserted $($Category.ProductProperties.Count) new node(s) into 'ProductSection'" } $XML.Save($SourceFile.FullName)