2021-06-08 08:14:10 +00:00
|
|
|
#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)
|
|
|
|
[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
|
|
|
|
)
|
|
|
|
|
2022-12-26 16:01:05 +00:00
|
|
|
$OVFConfig.PropertyCategories[@([int]$Disk.PropertyCategory, 0)[![boolean]$Disk.PropertyCategory]].ProductProperties += @{
|
2021-06-08 08:14:10 +00:00
|
|
|
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)
|
2022-12-26 16:01:05 +00:00
|
|
|
|
2021-06-08 08:14:10 +00:00
|
|
|
ForEach ($Configuration in $OVFConfig.DeploymentConfigurations) {
|
|
|
|
$XMLConfig = $XML.CreateElement('Configuration', $XML.DocumentElement.xmlns)
|
2022-12-26 16:01:05 +00:00
|
|
|
|
2021-06-08 08:14:10 +00:00
|
|
|
[void]$XMLConfig.SetAttribute('id', $NS.LookupNamespace('ovf'), $Configuration.Id)
|
2022-12-26 16:01:05 +00:00
|
|
|
|
2021-06-08 08:14:10 +00:00
|
|
|
$XMLConfigLabel = $XML.CreateElement('Label', $XML.DocumentElement.xmlns)
|
|
|
|
$XMLConfigLabel.InnerText = $Configuration.Label
|
|
|
|
$XMLConfigDescription = $XML.CreateElement('Description', $XML.DocumentElement.xmlns)
|
|
|
|
$XMLConfigDescription.InnerText = $Configuration.Description
|
2022-12-26 16:01:05 +00:00
|
|
|
|
2021-06-08 08:14:10 +00:00
|
|
|
[void]$XMLConfig.AppendChild($XMLConfigLabel)
|
|
|
|
[void]$XMLConfig.AppendChild($XMLConfigDescription)
|
2022-12-26 16:01:05 +00:00
|
|
|
|
2021-06-08 08:14:10 +00:00
|
|
|
[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)
|
2022-12-26 16:01:05 +00:00
|
|
|
|
2021-06-08 08:14:10 +00:00
|
|
|
[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'"
|
|
|
|
}
|
|
|
|
|
2022-12-26 16:01:05 +00:00
|
|
|
$XML.Save($SourceFile.FullName)
|