#Requires -Modules 'powershell-yaml' [CmdletBinding()] Param( [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', ".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) ForEach ($ReferencedDisk in $OVFConfig.Disks.Referenced) { $XMLDisk = $XML.SelectSingleNode("//ns:DiskSection/ns:Disk[@ovf:diskId='$($ReferencedDisk.Id)']", $NS) If ($XMLDisk.Count -eq 1) { $FactorMap = @{ 'KB' = [Math]::Pow(2,10) 'MB' = [Math]::Pow(2,20) 'GB' = [Math]::Pow(2,30) 'TB' = [Math]::Pow(2,40) 'PB' = [Math]::Pow(2,50) } If (($FactorMap.Keys -contains $ReferencedDisk.UnitSize)) { # Convert new capacity to same scale as existing capacity If ($XMLDisk.capacityAllocationUnits -match 'byte \* 2\^(\d+)') { $NewSize = [Math]::Truncate($ReferencedDisk.Capacity * $FactorMap[$ReferencedDisk.UnitSize] / [Math]::Pow(2,$Matches[1])) } ElseIf ($XMLDisk.capacityAllocationUnits -eq 'byte') { $NewSize = $ReferencedDisk.Capacity * $FactorMap[$ReferencedDisk.UnitSize] } Else { Throw "Unexpected value '$($XMLDisk.capacityAllocationUnits)' for property 'capacityAllocationUnits'; aborting." } # Determine if specified capacity is equal or larger than current & allocated capacity If ($XMLDisk.populatedSize -le $NewSize -and $XMLDisk.capacity -le $NewSize) { [void]$XMLDisk.SetAttribute('ovf:capacity', $NewSize) Write-Host "Updated 'Disk' with new property value" } } Else { # Invalid UnitSize; skipping modification of existing disk Continue } } } ForEach ($DynamicDisk in $OVFConfig.Disks.Dynamic) { # Determine next free available diskId $XMLDisks = $XML.SelectNodes("//ns:DiskSection/ns:Disk[contains(@ovf:diskId,'vmdisk')]", $NS) $DiskId = $XMLDisks.Count + 1 While ($XMLDisks.DiskId -contains "vmdisk$($DiskId)") { $DiskId++ } $XMLDisk = $XML.CreateElement('Disk', $XML.DocumentElement.xmlns) $XMLDiskAttrUnits = $XML.CreateAttribute('capacityAllocationUnits', $XML.DocumentElement.ovf) Switch ($DynamicDisk.UnitSize) { 'KB' { $Powers = 10 } 'MB' { $Powers = 20 } 'GB' { $Powers = 30 } 'TB' { $Powers = 40 } 'PB' { $Powers = 50 } default { # Invalid UnitSize; skipping modification of existing disk Continue } } $XMLDiskAttrUnits.Value = "byte * 2^$($Powers)" $XMLDiskAttrFormat.CreateAttribute('format', $XML.DocumentElement.ovf) $XMLDiskAttrFormat.Value = 'http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized' $XMLDiskId.CreateAttribute('diskId', $XML.DocumentElement.ovf) $XMLDiskId.Value = "vmdisk$($DiskId)" $XMLDiskAttrCapacity.CreateAttribute('capacity', $XML.DocumentElement.ovf) $XMLDiskAttrCapacity.Value = '${{vmconfig.disksize.{0}}}' -f $DiskId $XMLDiskAttrPopulated.CreateAttribute('populatedSize', $XML.DocumentElement.ovf) $XMLDiskAttrPopulated.Value = 0 [void]$XMLDisk.Attributes.Append($XMLDiskAttrUnits) [void]$XMLDisk.Attributes.Append($XMLDiskAttrFormat) [void]$XMLDisk.Attributes.Append($XMLDiskId) [void]$XMLDisk.Attributes.Append($XMLDiskAttrCapacity) [void]$XMLDisk.Attributes.Append($XMLDiskAttrPopulated) [void]$XML.SelectSingleNode('//ns:DiskSection', $NS).AppendChild($XMLDisk) $OVFConfig.PropertyCategories[0].ProductProperties += @{ Key = "vmconfig.disksize.$($DiskId)" Type = If ([boolean]$DynamicDisk.Constraints.Minimum -or [boolean]$DynamicDisk.Constraints.Maximum) { "Int($($DynamicDisk.Constraints.Minimum)..$($DynamicDisk.Constraints.Maximum))" } Else { 'Int' } Label = "Disk $($DiskId) size*" Description = "$($DynamicDisk.Description) (in $($DynamicDisk.UnitSize))".Trim() DefaultValue = "$($DynamicDisk.Constraints.Minimum)" Configurations = '*' UserConfigurable = 'true' } } Write-Host "Inserted $($OVFConfig.Disks.Dynamic.Count) new nodes into 'DiskSection'" 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) $XMLConfigAttrId = $XML.CreateAttribute('id', $XML.DocumentElement.ovf) $XMLConfigAttrId.Value = $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.Attributes.Append($XMLConfigAttrId) [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" } $XMLAttrTransport = $XML.CreateAttribute('transport', $XML.DocumentElement.ovf) $XMLAttrTransport.Value = 'com.vmware.guestInfo' [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).Attributes.Append($XMLAttrTransport) ForEach ($ExtraConfig in $OVFConfig.AdvancedOptions) { $XMLExtraConfig = $XML.CreateElement('vmw:ExtraConfig', $XML.DocumentElement.vmw) $XMLExtraConfigAttrRequired = $XML.CreateAttribute('required', $XML.DocumentElement.ovf) $XMLExtraConfigAttrRequired.Value = "$([boolean]$ExtraConfig.Required)".ToLower() $XMLExtraConfigAttrKey = $XML.CreateAttribute('key', $XML.DocumentElement.vmw) $XMLExtraConfigAttrKey.Value = $ExtraConfig.Key $XMLExtraConfigAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.vmw) $XMLExtraConfigAttrValue.Value = $ExtraConfig.Value [void]$XMLExtraConfig.Attributes.Append($XMLExtraConfigAttrRequired) [void]$XMLExtraConfig.Attributes.Append($XMLExtraConfigAttrKey) [void]$XMLExtraConfig.Attributes.Append($XMLExtraConfigAttrValue) [void]$XML.SelectSingleNode('//ns:VirtualHardwareSection', $NS).AppendChild($XMLExtraConfig) } Write-Host "Added $($OVFConfig.AdvancedOptions.Count) 'vmw:ExtraConfig' nodes" $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) $XMLPropertyAttrKey = $XML.CreateAttribute('key', $XML.DocumentElement.ovf) $XMLPropertyAttrKey.Value = $Property.Key $XMLPropertyAttrType = $XML.CreateAttribute('type', $XML.DocumentElement.ovf) Switch -regex ($Property.Type) { '^boolean' { $XMLPropertyAttrType.Value = 'boolean' } '^int' { $XMLPropertyAttrType.Value = 'uint8' $Qualifiers = @() If ($Property.Type -match '^int\((\d*)\.\.(\d*)\)') { If ($Matches[1]) { $Qualifiers += "MinValue($($Matches[1]))" } If ($Matches[2]) { $Qualifiers += "MaxValue($($Matches[2]))" } $XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf) $XMLPropertyAttrQualifiers.Value = $Qualifiers -join ' ' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers) } } '^ip' { $XMLPropertyAttrType.Value = 'string' $XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.vmw) $XMLPropertyAttrQualifiers.Value = 'Ip' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers) } '^password' { $XMLPropertyAttrType.Value = 'string' $XMLPropertyAttrPassword = $XML.CreateAttribute('password', $XML.DocumentElement.ovf) $XMLPropertyAttrPassword.Value = 'true' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrPassword) $Qualifiers = @() If ($Property.Type -match '^password\((\d*)\.\.(\d*)\)') { If ($Matches[1]) { $Qualifiers += "MinLen($($Matches[1]))" } If ($Matches[2]) { $Qualifiers += "MaxLen($($Matches[2]))" } $XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf) $XMLPropertyAttrQualifiers.Value = $Qualifiers -join ' ' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers) } } '^string' { $XMLPropertyAttrType.Value = 'string' $Qualifiers = @() If ($Property.Type -match '^string\((\d*)\.\.(\d*)\)') { If ($Matches[1]) { $Qualifiers += "MinLen($($Matches[1]))" } If ($Matches[2]) { $Qualifiers += "MaxLen($($Matches[2]))" } $XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf) $XMLPropertyAttrQualifiers.Value = $Qualifiers -join ' ' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers) } ElseIf ($Property.Type -match '^string\[(.*)\]') { $XMLPropertyAttrQualifiers = $XML.CreateAttribute('qualifiers', $XML.DocumentElement.ovf) $XMLPropertyAttrQualifiers.Value = "ValueMap{$($Matches[1] -replace '","', '", "')}" [void]$XMLProperty.Attributes.Append($XMLPropertyAttrQualifiers) } } } $XMLPropertyAttrUserConfigurable = $XML.CreateAttribute('userConfigurable', $XML.DocumentElement.ovf) $XMLPropertyAttrUserConfigurable.Value = "$([boolean]$Property.UserConfigurable)".ToLower() $XMLPropertyAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.ovf) If ($Property.Type -eq 'boolean') { $XMLPropertyAttrValue.Value = "$([boolean]$Property.DefaultValue)".ToLower() } Else { $XMLPropertyAttrValue.Value = $Property.DefaultValue } [void]$XMLProperty.Attributes.Append($XMLPropertyAttrKey) [void]$XMLProperty.Attributes.Append($XMLPropertyAttrType) [void]$XMLProperty.Attributes.Append($XMLPropertyAttrUserConfigurable) [void]$XMLProperty.Attributes.Append($XMLPropertyAttrValue) 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 '*')) { $XMLPropertyAttrConfiguration = $XML.CreateAttribute('configuration', $XML.DocumentElement.ovf) $XMLPropertyAttrConfiguration.Value = $OVFConfig.DeploymentConfigurations.Id -join ' ' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrConfiguration) } ElseIf ($Property.Configurations.Count -gt 0) { $XMLPropertyAttrConfiguration = $XML.CreateAttribute('configuration', $XML.DocumentElement.ovf) $XMLPropertyAttrConfiguration.Value = $Property.Configurations -join ' ' [void]$XMLProperty.Attributes.Append($XMLPropertyAttrConfiguration) } If ($Property.Value.Count -eq 1) { $XMLPropertyAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.ovf) $XMLPropertyAttrValue.Value = $Property.Value [void]$XMLProperty.Attributes.Append($XMLPropertyAttrValue) } ElseIf ($Property.Value.Count -gt 1) { ForEach ($Value in $Property.Value) { $XMLValue = $XML.CreateElement('Value', $XML.DocumentElement.xmlns) $XMLValueAttrValue = $XML.CreateAttribute('value', $XML.DocumentElement.ovf) $XMLValueAttrValue.Value = $Value $XMLValueAttrConfiguration = $XML.CreateAttribute('configuration', $XML.DocumentElement.ovf) $XMLValueAttrConfiguration.Value = $Value [void]$XMLValue.Attributes.Append($XMLValueAttrValue) [void]$XMLValue.Attributes.Append($XMLValueAttrConfiguration) [void]$XMLProperty.AppendChild($XMLValue) } } [void]$XMLProductSection.AppendChild($XMLProperty) } Write-Host "Inserted $($Category.ProductProperties.Count) new node(s) into 'ProductSection'" } $XML.Save($SourceFile.FullName)