commit 9dc19c5b172e6ec2c5b271a6b21da208b7b91b5e Author: djpbessems Date: Sun Jan 24 10:41:02 2021 +0100 Delete commit history (containing proprietary code) diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..bce0d2f --- /dev/null +++ b/.drone.yml @@ -0,0 +1,37 @@ +kind: pipeline +type: kubernetes +name: 'Packer Build' + +steps: +- name: Active Directory Certificate Services + image: bv11-cr01.bessems.eu/library/packer-extended + commands: + - | + packer validate \ + -var-file=packer/variables.vsphere.json \ + -var vm_name=${DRONE_COMMIT_SHA:0:10}-$DRONE_BUILD_NUMBER \ + -var vsphere_password=$${VSPHERE_PASSWORD} \ + -var winrm_password=$${WINRM_PASSWORD} \ + packer/adcs.json + - | + packer build \ + -on-error=cleanup \ + -var-file=packer/variables.vsphere.json \ + -var vm_name=${DRONE_COMMIT_SHA:0:10}-$DRONE_BUILD_NUMBER \ + -var vsphere_password=$${VSPHERE_PASSWORD} \ + -var winrm_password=$${WINRM_PASSWORD} \ + packer/adcs.json + environment: + VSPHERE_PASSWORD: + from_secret: vsphere_password + WINRM_PASSWORD: + from_secret: winrm_password + # PACKER_LOG: 1 + volumes: + - name: output + path: /output + +volumes: +- name: output + claim: + name: flexvolsmb-drone-output diff --git a/README.md b/README.md new file mode 100644 index 0000000..198852d --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Packer.Images [![Build Status](https://ci.spamasaurus.com/api/badges/djpbessems/Packer.Images/status.svg)](https://ci.spamasaurus.com/djpbessems/Packer.Images) + diff --git a/packer/adcs.json b/packer/adcs.json new file mode 100644 index 0000000..659f1ce --- /dev/null +++ b/packer/adcs.json @@ -0,0 +1,90 @@ +{ + "builders": [ + { + "type": "vsphere-clone", + "name": "adcs", + + "vcenter_server": "{{user `vcenter_server`}}", + "username": "{{user `vsphere_username`}}", + "password": "{{user `vsphere_password`}}", + "insecure_connection": "true", + + "vm_name": "adcs-{{user `vm_name`}}", + "datastore": "{{user `vsphere_datastore`}}", + "folder": "{{user `vsphere_folder`}}", + "datacenter": "{{user `vsphere_datacenter`}}", + "host": "{{user `vsphere_host`}}", + "boot_order": "disk,cdrom", + + "communicator": "winrm", + "winrm_username": "administrator", + "winrm_password": "{{user `winrm_password`}}", + "winrm_timeout": "10m", + + "cpus": 2, + "RAM": 8192, + + "template": "Windows-Server-2019-LTSC", + + "floppy_files": [ + "packer/preseed/ADCS/Sysprep_Unattend.xml" + ], + + "boot_command": "", + "boot_wait": "2m30s", + + "shutdown_command": "C:\\Windows\\System32\\Sysprep\\sysprep.exe /generalize /oobe /unattend:A:\\Sysprep_Unattend.xml", + "shutdown_timeout": "1h", + + "export": { + "images": false + } + } + ], + "provisioners": [ + { + "type": "powershell", + "inline": [ + "New-Item -Path 'C:\\Payload\\Scripts' -ItemType 'Directory' -Force:$True -Confirm:$False" + ] + }, + { + "type": "file", + "source": "scripts/ADCS/payload/", + "destination": "C:\\Payload\\" + }, + { + "type": "powershell", + "scripts": [ + "scripts/ADCS/Install-Prerequisites.ps1", + "scripts/ADCS/Register-ScheduledTask.ps1" + ] + } + ], + "post-processors": [[ + { + "type": "shell-local", + "inline": [ + "pwsh -file scripts/Update-OvfConfiguration.ps1 \\", + " -OVFFile './output-adcs/adcs-{{user `vm_name`}}.ovf'", + "pwsh -file scripts/Update-Manifest.ps1 \\", + " -ManifestFileName './output-adcs/adcs-{{user `vm_name`}}.mf'", + "ovftool --acceptAllEulas --allowExtraConfig --overwrite \\", + " './output-adcs/adcs-{{user `vm_name`}}.ovf' \\", + " /output/ADCS-appliance.ova" + ] + } + ], + [ + { + "type": "shell-local", + "inline": [ + "pwsh -file scripts/Remove-Resources.ps1 \\", + " -VMName 'adcs-{{user `vm_name`}}' \\", + " -VSphereFQDN '{{user `vcenter_server`}}' \\", + " -VSphereUsername '{{user `vsphere_username`}}' \\", + " -VSpherePassword '{{user `vsphere_password`}}'" + ] + } + ]] +} diff --git a/packer/preseed/ADCS/Sysprep_Unattend.xml b/packer/preseed/ADCS/Sysprep_Unattend.xml new file mode 100644 index 0000000..c0d5cf8 --- /dev/null +++ b/packer/preseed/ADCS/Sysprep_Unattend.xml @@ -0,0 +1,27 @@ + + + + + 1 + + + true + true + + + + + + true + true + true + true + true + Work + 1 + true + true + + + + \ No newline at end of file diff --git a/packer/variables.vsphere.json b/packer/variables.vsphere.json new file mode 100644 index 0000000..cd6c507 --- /dev/null +++ b/packer/variables.vsphere.json @@ -0,0 +1,11 @@ +{ + "vcenter_server": "bv11-vc01.bessems.lan", + "vsphere_username": "administrator@vsphere.local", + "vsphere_datacenter": "DeSchakel", + "vsphere_host": "bv11-esx.bessems.eu", + "vsphere_hostip": "192.168.11.200", + "vsphere_datastore": "Datastore02.SSD", + "vsphere_folder": "/Packer", + "vsphere_templatefolder": "/Templates", + "vsphere_network": "LAN" +} \ No newline at end of file diff --git a/scripts/ADCS/Install-Prerequisites.ps1 b/scripts/ADCS/Install-Prerequisites.ps1 new file mode 100644 index 0000000..d6859ad --- /dev/null +++ b/scripts/ADCS/Install-Prerequisites.ps1 @@ -0,0 +1,48 @@ +[CmdletBinding()] +Param( + # No parameters +) + +$InstallWindowsFeatureSplat = @{ + Name = 'Adcs-Cert-Authority' + IncludeAllSubFeature = $True + IncludeManagementTools = $True + Restart = $False + Confirm = $False +} +Install-WindowsFeature @InstallWindowsFeatureSplat + +$InstallPackageProviderSplat = @{ + Name = 'NuGet' + MinimumVersion = '2.8.5.201' + Force = $True + Confirm = $False +} +Install-PackageProvider @InstallPackageProviderSplat +$SetPSRepositorySplat = @{ + Name = 'PSGallery' + InstallationPolicy = 'Trusted' +} +Set-PSRepository @SetPSRepositorySplat +$InstallModuleSplat = @{ + Name = 'powershell-yaml' + Force = $True + Confirm = $False +} +Install-Module @InstallModuleSplat +$SetPSRepositorySplat = @{ + Name = 'PSGallery' + InstallationPolicy = 'Untrusted' +} +Set-PSRepository @SetPSRepositorySplat + +# Double check whether the required PowerShell modules are available +$RequiredModules = @( + 'powershell-yaml' # Provides cmdlets 'ConvertTo-Yaml' and 'ConvertFrom-Yaml' +) +ForEach ($Module in $RequiredModules) { + If ([boolean](Get-Module -Name $Module -ListAvailable) -ne $True) { + Write-Error -Message "Missing PowerShell module '$($Module)'" + Exit 1 + } +} \ No newline at end of file diff --git a/scripts/ADCS/Register-ScheduledTask.ps1 b/scripts/ADCS/Register-ScheduledTask.ps1 new file mode 100644 index 0000000..57b7986 --- /dev/null +++ b/scripts/ADCS/Register-ScheduledTask.ps1 @@ -0,0 +1,7 @@ +[CmdletBinding()] +Param( + # No parameters +) + +# Create scheduled task +& schtasks.exe /Create /TN 'OVF-Properties' /SC ONSTART /RU SYSTEM /TR "powershell.exe -file C:\Payload\Apply-OVFProperties.ps1" \ No newline at end of file diff --git a/scripts/ADCS/payload/Apply-OVFProperties.ps1 b/scripts/ADCS/payload/Apply-OVFProperties.ps1 new file mode 100644 index 0000000..5221b77 --- /dev/null +++ b/scripts/ADCS/payload/Apply-OVFProperties.ps1 @@ -0,0 +1,166 @@ +#Requires -Modules 'ADDSDeployment' +[CmdletBinding()] +Param( + # No parameters +) + +$NewEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + ErrorAction = 'SilentlyContinue' +} +New-EventLog @NewEventLogSplat +$WriteEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + EntryType = 'Information' + EventID = 1 + Message = 'OVF-Properties sequence initiated' +} +Write-EventLog @WriteEventLogSplat + +$VMwareToolsExecutable = "C:\Program Files\VMware\VMware Tools\vmtoolsd.exe" + +[xml]$ovfEnv = & $VMwareToolsExecutable --cmd "info-get guestinfo.ovfEnv" | Out-String +$ovfProperties = $ovfEnv.ChildNodes.NextSibling.PropertySection.Property + +$ovfPropertyValues = @{} +foreach ($ovfProperty in $ovfProperties) { + $ovfPropertyValues[$ovfProperty.key] = $ovfProperty.Value +} + +# Check for mandatory values +If (!($ovfPropertyValues['guestinfo.hostname'] -and + $ovfPropertyValues['guestinfo.ipaddress'] -and + $ovfPropertyValues['guestinfo.dnsserver'] -and + $ovfPropertyValues['guestinfo.prefixlength'] -and + $ovfPropertyValues['guestinfo.gateway'])) { + # Mandatory values missing, cannot provision. + $WriteEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + EntryType = 'Error' + EventID = 66 + Message = 'Mandatory values missing, cannot provision.' + } + Write-EventLog @WriteEventLogSplat + & schtasks.exe /Change /TN 'OVF-Properties' /DISABLE + Stop-Computer -Force + Exit +} + +# Set hostname and description +If ($Env:ComputerName -ne $ovfPropertyValues['guestinfo.hostname']) { + $RenameComputerSplat = @{ + NewName = $ovfPropertyValues['guestinfo.hostname'] + Force = $True + Confirm = $False + } + Rename-Computer @RenameComputerSplat + $SetCimInstanceSplat = @{ + InputObject = (Get-CimInstance -ClassName 'Win32_OperatingSystem') + Property = @{ + Description = $ovfPropertyValues['guestinfo.hostname'] + } + } + Set-CimInstance @SetCimInstanceSplat + + # Restart the computer to apply changes + Restart-Computer -Force + Exit +} + +# Configure network interface +If ((Get-WmiObject -Class 'Win32_NetworkAdapterConfiguration').IPAddress -NotContains $ovfPropertyValues['guestinfo.ipaddress']) { + $NewNetIPAddressSplat = @{ + InterfaceAlias = (Get-NetAdapter).Name + AddressFamily = 'IPv4' + IPAddress = $ovfPropertyValues['guestinfo.ipaddress'] + PrefixLength = $ovfPropertyValues['guestinfo.prefixlength'] + DefaultGateway = $ovfPropertyValues['guestinfo.gateway'] + } + New-NetIPAddress @NewNetIPAddressSplat + + # Wait for network connection to become available + $Timestamp, $TimeoutMinutes = (Get-Date), 5 + Do { + If ($Timestamp.AddMinutes($TimeoutMinutes) -lt (Get-Date)) { + $WriteEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + EntryType = 'Warning' + EventID = 13 + Message = "Timeout after $($TimeoutMinutes) minutes waiting for network connection to become available." + } + Write-EventLog @WriteEventLogSplat + Break + } + + Start-Sleep -Milliseconds 250 + + $GetNetIPAddressSplat = @{ + InterfaceAlias = (Get-NetAdapter).Name + AddressFamily = 'IPv4' + ErrorAction = 'SilentlyContinue' + } + } Until ((Get-NetIPAddress @GetNetIPAddressSplat).AddressState -eq 'Preferred') + + $OldErrorActionPreference, $ErrorActionPreference = $ErrorActionPreference, 'SilentlyContinue' + $TestNetConnectionSplat = @{ + ComputerName = ([IPAddress]$ovfPropertyValues['guestinfo.dnsserver']).IPAddressToString + InformationLevel = 'Quiet' + } + $SetDnsClientServerAddressSplat = @{ + InterfaceAlias = (Get-NetAdapter).Name + ServerAddresses = If ( + [boolean]($ovfPropertyValues['guestinfo.dnsserver'] -as [IPaddress]) -and (Test-NetConnection @TestNetConnectionSplat)) { + ($ovfPropertyValues['guestinfo.dnsserver']) + } else { + ('127.0.0.1') + } + Validate = $False + } + Set-DnsClientServerAddress @SetDnsClientServerAddressSplat + $ErrorActionPreference, $OldErrorActionPreference = $OldErrorActionPreference, $NULL +} + +# Foo + +# Iterate through and invoke all payload scripts +#! TODO: add registry values to determine which scripts have already been invoked (in case of intermediate reboots) +$GetItemSplat = @{ + Path = "$($PSScriptRoot)\Scripts\*.ps1" +} +Get-Item @GetItemSplat | ForEach-Object { + Try { + $WriteEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + EntryType = 'Information' + EventID = 4 + Message = "Running script: '$($_.FullName)'" + } + Write-EventLog @WriteEventLogSplat + & $_.FullName -Parameter $ovfPropertyValues + } + Catch { + $WriteEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + EntryType = 'Error' + EventID = 66 + Message = $_.Exception.Message + } + Write-EventLog @WriteEventLogSplat + } +} + +$WriteEventLogSplat = @{ + LogName = 'Application' + Source = 'OVF-Properties' + EntryType = 'Information' + EventID = 42 + Message = 'OVF-Properties sequence applied and finished' +} +Write-EventLog @WriteEventLogSplat +& schtasks.exe /Change /TN 'OVF-Properties' /DISABLE diff --git a/scripts/ADCS/payload/scripts/01.Foo.ps1 b/scripts/ADCS/payload/scripts/01.Foo.ps1 new file mode 100644 index 0000000..ca142b8 --- /dev/null +++ b/scripts/ADCS/payload/scripts/01.Foo.ps1 @@ -0,0 +1,37 @@ +#Requires -Modules 'NetSecurity' +Param( + [Parameter(Mandatory)] + [hashtable]$Parameter +) + +If ($False) { + $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 + $Settings = $YamlDocuments[0..($YamlDocuments.Count - 2)] + } + Else { + $Settings = $YamlDocuments + } + + #Foo +} diff --git a/scripts/ADCS/payload/scripts/01.Foo.yml b/scripts/ADCS/payload/scripts/01.Foo.yml new file mode 100644 index 0000000..d6e2e45 --- /dev/null +++ b/scripts/ADCS/payload/scripts/01.Foo.yml @@ -0,0 +1,2 @@ +Foo: +- Bar \ No newline at end of file diff --git a/scripts/Remove-Resources.ps1 b/scripts/Remove-Resources.ps1 new file mode 100644 index 0000000..34a37f0 --- /dev/null +++ b/scripts/Remove-Resources.ps1 @@ -0,0 +1,39 @@ +[CmdletBinding()] +Param( + [Parameter(Mandatory)] + [string]$VMName, + [Parameter(Mandatory)] + [string]$VSphereFQDN, + [Parameter(Mandatory)] + [string]$VSphereUsername, + [Parameter(Mandatory)] + [string]$VSpherePassword +) + +$PowerCliConfigurationSplat = @{ + Scope = 'User' + ParticipateInCEIP = $False + Confirm = $False + InvalidCertificateAction = 'Ignore' +} +Set-PowerCLIConfiguration @PowerCliConfigurationSplat + +$ConnectVIServerSplat = @{ + Server = $VSphereFQDN + User = "$VSphereUsername" + Password = "$VSpherePassword" + WarningAction = 'SilentlyContinue' +} +Connect-VIServer @ConnectVIServerSplat | Out-Null + +$RemoveVMSplat = @{ + VM = "$($VMName)*" + DeletePermanently = $True + Confirm = $False + ErrorAction = 'SilentlyContinue' +} +Remove-VM @RemoveVMSplat + +# Also delete ISO/floppy? + +Disconnect-VIServer * -Confirm:$False \ No newline at end of file diff --git a/scripts/Update-Manifest.ps1 b/scripts/Update-Manifest.ps1 new file mode 100644 index 0000000..03e3ff8 --- /dev/null +++ b/scripts/Update-Manifest.ps1 @@ -0,0 +1,55 @@ +#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]$ManifestFileName +) + +$GetItemSplat = @{ + Path = $ManifestFileName +} +$ManifestFile = Get-Item @GetItemSplat + +$SetLocationSplat = @{ + Path = $ManifestFile.DirectoryName +} +Set-Location @SetLocationSplat + +$GetContentSplat = @{ + Path = $ManifestFile.FullName +} +$Manifest = Get-Content @GetContentSplat + +$UpdatedManifest = ForEach ($Line in $Manifest) { + Write-Host "Processing '$($Line)' ..." + If ($Line -match '^SHA256\((.+)\)= ([0-9a-fA-F]{64})$') { + If (Test-Path $Matches[1]) { + $GetFileHashSplat = @{ + Path = $Matches[1] + Algorithm = 'SHA256' + } + Write-Host "Updating checksum..." + "SHA256($($Matches[1]))= $((Get-FileHash @GetFileHashSplat).Hash)" + } + } +} + +If ($UpdatedManifest -ne $Null) { + $SetContentSplat = @{ + Path = $ManifestFile.FullName + Value = $UpdatedManifest + Force = $True + Confirm = $False + } + Set-Content @SetContentSplat +} Else { + Write-Host "Failed updating manifest." + Exit 1 +} diff --git a/scripts/Update-OvfConfiguration.ps1 b/scripts/Update-OvfConfiguration.ps1 new file mode 100644 index 0000000..fd731de --- /dev/null +++ b/scripts/Update-OvfConfiguration.ps1 @@ -0,0 +1,219 @@ +#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 +) + +$GetContentSplat = @{ + Path = "$($PSScriptRoot)\$($MyInvocation.MyCommand)".Replace('.ps1', ".yml") + Raw = $True +} +$RawContent = Get-Content @GetContentSplat +$ConvertFromYamlSplat = @{ + Yaml = $RawContent + AllDocuments = $True +} +$OVFConfig = ConvertFrom-Yaml @ConvertFromYamlSplat + +$SourceFile = Get-Item -Path $OVFFile +$GetContentSplat = @{ + Path = $SourceFile.FullName +} +$XML = [xml](Get-Content @GetContentSplat) +$NS = [System.Xml.XmlNamespaceManager]$XML.NameTable +[void]$NS.AddNamespace('Any', $XML.DocumentElement.xmlns) + +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('//Any:Envelope', $NS).InsertAfter($XMLSection, $XML.SelectSingleNode('//Any: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('//Any:VirtualHardwareSection', $NS).Attributes.Append($XMLAttrTransport) + +$XMLProductSection = $XML.SelectSingleNode('//Any:ProductSection', $NS) +If ($XMLProductSection -eq $Null) { + $XMLProductSection = $XML.CreateElement('ProductSection', $XML.DocumentElement.xmlns) + [void]$XML.SelectSingleNode('//Any:VirtualSystem', $NS).AppendChild($XMLProductSection) + Write-Host "Inserted 'ProductSection'" +} Else { + ForEach ($Child in $XMLProductSection.SelectNodes('//Any: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) diff --git a/scripts/Update-OvfConfiguration.yml b/scripts/Update-OvfConfiguration.yml new file mode 100644 index 0000000..71a7b1b --- /dev/null +++ b/scripts/Update-OvfConfiguration.yml @@ -0,0 +1,75 @@ +DeploymentConfigurations: +- Id: standalone-root + Label: Root Certificate Authority + Description: Root CA with self-signed certificate; should be kept turned off +- Id: enterprise-intermediate + Label: Subordinate enterprise Certificate Authority + Description: Subordinate CA on domain-member server; kept online to service certificate requests/enrollment and host CRL +- Id: standalone-intermediate + Label: Subordinate standalone Certificate Authority + Description: Subordinate CA on standalone server; kept online to service certificate requests and host CRL +PropertyCategories: +- Name: '' + ProductProperties: + - Key: deployment.type + Type: string + Value: + - standalone-root + - enterprise-intermediate + - standalone-intermediate + UserConfigurable: false +- Name: 1) Operating System + ProductProperties: + - Key: guestinfo.hostname + Type: string(1..15) + Label: Hostname* + Description: '(max length: 15 characters)' + DefaultValue: '' + Configurations: '*' + UserConfigurable: true +- Name: 2) Networking + ProductProperties: + - Key: guestinfo.ipaddress + Type: ip + Label: IP Address* + Description: '' + DefaultValue: '' + Configurations: '*' + UserConfigurable: true + - Key: guestinfo.prefixlength + Type: int(8..32) + Label: Subnet prefix length* + Description: '' + DefaultValue: '24' + Configurations: '*' + UserConfigurable: true + - Key: guestinfo.dnsserver + Type: ip + Label: DNS server* + Description: '' + DefaultValue: '' + Configurations: '*' + UserConfigurable: true + - Key: guestinfo.gateway + Type: ip + Label: Gateway* + Description: '' + DefaultValue: '' + Configurations: '*' + UserConfigurable: true +- Name: 3) Active Directory Certificate Services + ProductProperties: + - Key: adcsconfig.foo + Type: string(1..) + Label: Foo* + Description: '' + DefaultValue: '' + Configurations: '*' + UserConfigurable: true + - Key: adcsconfig.bar + Type: string(1..) + Label: Bar* + Description: '' + DefaultValue: '' + Configurations: '*' + UserConfigurable: true