This ABX Action will automatically create and delete DNS A, PTR, and CNAME records as part of a vRA Extensibility Subscription. This ABX is written to work with Microsoft DNS server and has been tested on the following software versions:
- MS Active Directory/DNS Server 2019
- vRA 8.5.0
Features
- Action Constants providing values/secrets for:
- domain_username: service account with sufficient rights to create and delete DNS records in UPN format – e.g. – username@mydomain.local
- domain_password: password for the above account
- dns_server1: DNS server FQDN e.g. – dns1.mydomain.local
- dns_server2: DNS server FQDN (optional)
- domain_name: e.g. – mydomain.local
- Custom property for the desired CNAME passed from the vRA deployment. This can be statically defined in the blueprint or requested as a user input
- This property is optional and does not need to be present in the blueprint if you do not wish to have a CNAME
- The CNAME will point to the hostname DNS A record
- Custom property create_dns used to target the action subscription
- Authentication to DNS using CredSSP secure transport
- IP Address and hostname imported from the vRA deployment
- Removes A, PTR, and CNAME records if the deployment is deleted
- Imports the pywinrm[credssp] python module to establish the connection
Pre-requisites
1. Account permissions:
A service account member of the Domain Admins group is required for Active Directory integrated DNS zones. This script has not been tested on Non-AD integrated DNS zones.
2. Enable PowerShell Remoting on each DNS Server:
Enable-psremoting -force
3. Enable CredSSP Authentication on each DNS Server:
Enable-WSManCredSSP -Role Server
4. Enable HTTPS for Windows Remote Management (WS-Management) service on each DNS server:
- Install a valid SSL certificate on the DNS server (self-signed, internal CA or Public).
- Configure the WinRM Listener for HTTPS.
- Open the Windows Firewall port.
Detailed instructions can be found in the following article: https://adamtheautomator.com/winrm-ssl
5. Grant the NETWORK SERVICE on each DNS Server group Full Control permissions over the certificate’s private key
- Open the Microsoft Management Console and add the Certificates Snap-in.
- Expand Certificates (Local Computer) > Personal > Certificates.
- Right-click the certificate, and select All Tasks > Manage Private Keys.
- Add the NETWORK SERVICE user to the list of groups and user names.
- Select the NETWORK SERVICE user and grant it Full Control rights.
- Click OK
6. Create a DNS reverse domain zone if you need PTR records
Script Execution
This is the flow of the python script – note: python is used with the pywinrm module because it did not work with the PS Core container spun up by vRA.
- Import the action constants
- Import the hostname, IP address, cnamerecord, and deployment event status from vRA
- Establish a connection to DNS Server 1. If this server is unavailable, try DNS Server 2, else fail
- Check the current deployment event topic
- If the event topic is “compute.provision”, then:
- Create the A and PTR records
- Create a CNAME record if it was defined in the blueprint
- If the event topic is “compute.removal”, then:
- Delete the A and PTR records
- Delete the CNAME record if it is found in the reverse zone
- If the event topic is “compute.provision”, then:
Action
Create the DNS Record action as follows:
1. Copy the following python code:
# ABX Action for lifecycle of DNS records in vRA deployments
# Created by Dennis Gerolymatos and Guillermo Martinez
# Version 1.0 - 24.10.2021
import winrm, sys
def handler(context, inputs):
event = str(inputs["__metadata"]["eventTopicId"]) # Gets the deployment Event Topic
ip_raw = inputs["addresses"] # Raw IP input created vy vRA IPAM
ipaddress = str(ip_raw[0])[2:-2] # Cleaned up IP
hostname_raw = inputs["resourceNames"] # Raw Hostname from deployment
hostname = str(hostname_raw)[2:-2] # Cleaned up Hostname
cnameRecord = inputs["customProperties"]["cnameRecord"] # Gets CNAME value from customproperties
DNS_Server1 = context.getSecret(inputs["dns_server1"]) # DNS server where the command will be executed
DNS_Server2 = context.getSecret(inputs["dns_server2"]) # DNS server where the command will be executed (secondary)
DNS_Domain = context.getSecret(inputs["domain_name"]) # DNS Domain to be updated
Username = context.getSecret(inputs["domain_username"]) # Username with righs to perform the operation
Password = context.getSecret(inputs["domain_password"]) # Password for the account
#Open session to DNS Server, try DNS 1 first and failback to DNS 2 if DNS 1 connection is not succesful
try:
session = winrm.Session('https://'+DNS_Server1+':5986/wsman', auth=(Username,Password), transport='credssp', server_cert_validation='ignore')
result = session.run_ps("hostname")
print("Connected to server "+result.std_out.decode())
except:
try:
session = winrm.Session('https://'+DNS_Server2+':5986/wsman', auth=(Username,Password), transport='credssp', server_cert_validation='ignore')
result = session.run_ps("hostname")
print("Connected to server "+result.std_out.decode())
except:
print("Connections to server "+DNS_Server1+" or "+DNS_Server2+" failed. Aborting script...")
sys.exit(0)
#Check for provision event topic
result = event.startswith('compute.provision')
if result == True :
print("Creating A record and PTR for "+hostname+" "+ipaddress)
dns_command = "Add-DnsServerResourceRecordA -ZoneName "+DNS_Domain+" -Name "+hostname+" -IPv4Address "+ipaddress+" -CreatePtr"
result = session.run_ps(dns_command)
print(result.std_out.decode())
#creates CNAME if requested in the deployment
if (cnameRecord) :
print("Creating CNAME record "+cnameRecord+" pointing to "+hostname+"."+DNS_Domain)
dns_command = "Add-DnsServerResourceRecordCname -ZoneName "+DNS_Domain+" -HostNameAlias "+hostname+"."+DNS_Domain+" -Name "+cnameRecord
result = session.run_ps(dns_command)
print(result.std_out.decode())
#Check for removal event topic
result = event.startswith('compute.removal')
if result == True :
print("Deleting A record and PTR for "+hostname+" "+ipaddress)
#Remove A record
dns_command = "Remove-DnsServerResourceRecord -ZoneName "+DNS_Domain+" -Name "+hostname+" -RRType A -force"
result = session.run_ps(dns_command)
print(result.std_out.decode())
#search for PTR record in order to get the zone name.
dns_command = "get-dnsserverzone | where isreverselookupzone -eq True |Get-DnsServerResourceRecord -RRType PTR | where {$_.RecordData.PtrDomainName -eq '"+hostname+"."+DNS_Domain+".'} | foreach {$_.distinguishedname}"
result = session.run_ps(dns_command)
content = result.std_out.decode()
if (not content):
print("PTR doesn't exist")
#if PTR record was found, remove the PTR record.
else:
tempvar = result.std_out.decode().split(",")
hostname2 = tempvar[0].replace('DC=','')
zonename2 = tempvar[1].replace('DC=','')
print("Removing PTR record "+hostname2+" in zone "+zonename2)
dns_command = "Remove-DnsServerResourceRecord -ZoneName "+zonename2+" -Name "+hostname2+" -RRType PTR -force"
result = session.run_ps(dns_command)
print(result.std_out.decode())
#search for a CNAME record pointing to hostname.
dns_command = "Get-DnsServerResourceRecord -RRType CNAME -ZoneName "+DNS_Domain+" | where {$_.RecordData.hostnamealias -eq '"+hostname+"."+DNS_Domain+".'} | foreach {$_.hostname}"
result = session.run_ps(dns_command)
content = result.std_out.decode()
if (not content):
print("CNAME record not found")
#if CNAME record was found, remove the CNAME record.
else:
print("Removing CNAME record "+result.std_out.decode())
dns_command = "Get-DnsServerResourceRecord -RRType CNAME -ZoneName "+DNS_Domain+" | where {$_.RecordData.hostnamealias -eq '"+hostname+"."+DNS_Domain+".'} | remove-dnsserverresourcerecord -zonename "+DNS_Domain+" -force"
result = session.run_ps(dns_command)
print(result.std_out.decode())
2. Create the Action Constants to present to the script handler:

Subscriptions
There are two subscriptions needed for this action:
1. DNS-Record-Create
- Subscribe to the Compute post provision Event Topic
- Set a condition
event.data.customProperties['create_dns'] == "true"
. This custom property must be in the vRA blueprint to trigger this subscription - Turn on Blocking to make sure that the DNS records are created before any other deployment or ABX action is run (for example in a multi-machine blueprint)

2. DNS-Record-Delete
- Subscribe to the Compute post removal Event Topic
- Set a condition
event.data.customProperties['create_dns'] == "true"
- No need for Blocking on this one

Blueprint
Create two custom properties in your blueprint:
1. create_dns: (type:boolean)
2. cnameRecord: (type:string)

I hope you found this a useful addition to your ABX arsenal – please feel free to share.
Hi,
I’ve got a question. I am trying to set up that configuration but when I am trying to open session I’ve got an error:
Running in polyglot! requests auth method is credssp, but requests-credssp is not installed
Do you have any idea if it is related to Windows configuration or vRA?
Similar problem is with Kerberos. NTLM works fine.
Hi,
Make sure pywinrm[credssp] is configured as a dependency of the action – see the screenshot in step 2 underneath the code block.