Microsoft Dynamics 365 Blog

OK, chances are that you won’t ever need to do this. But if you do need an automated way to create CNAME records in your domain, then this post is for you.

Why?

A previous post explored how to automate the Dynamics NAV Management Portal via web services. If you use a custom domain, then every new tenant will need a CNAME record to map its custom name to its default name. This can be done manually quite easily, like the portal can be used manually. But if you do want a fully automated provision of new tenants, then you may also need to automate the new CNAME.

What?

Let’s say you provision a new tenant called MyNewCustomerLtd. The portal will add a few random characters and the default domain to its new name, so the tenant name will be for example MyNewCustomerLtd-qasc82.navcloudapp.net. But let’s say that you own the domain CoffeebreakBiz.com, and you would rather that your customer logged on to MyNewCustomerLtd.CoffeeBreakBiz.com. Since this is your domain you can set it up to redirect any address in the domain you like, so you just need to redirect MyNewCustomerLtd.CoffeeBreakBiz.com to MyNewCustomerLtd-qasc82.navcloudapp.net. This is what a CNAME record in your domain does.

Where?

The Dynamics NAV management portal cannot make the CNAME record for you. This example shows how to use the API from www.DNSMadeEasy.com , which we can use for domain administration. The actual domain may be registered with another service, for example www.GoDaddy.com so you don’t need to move all your existing domains to DNSMadeEasy, in fact you can’t. For this example to work you need to set up an account in DNSMadeEasy which you then set up to administer your current domain(s). In order for the API to be available in DNSMadeEasy you need an account which includes Business Membership. Other domain services are without doubt available, so if you use other services then you will need to refer to their support or manuals if they offer an API and how.

Thank you, Freddy Kristiansen, for the script!

Let’s do it!

First, open the script below in your preferred PowerShell environment (IDE): Scroll down to the end of this post, copy the chunk of PowerShell and copy it, then run it, then return here. The script creates functions New-CNameRecord and Set-CNameRecord which is the ones we will show here. Before you run it you need to set up authentication, this is done via these three variables:

$DMEAPI = 'd123a0b0-c456-78de-9fb2-876g5fh43210'
$DMESecret = '98a7654b-32c1-1d23-ef45-67g898765hij'
$DMEBaseUrl = 'https://api.dnsmadeeasy.com/V2.0/dns/managed/'

Find the first two in your DNSMadeEasy account under Config -> Account Information. This is where you need to have Business Membership included in your account to get this API key and Secret Key.

The last one (BaseUrl) is the way it has to be, so copy that one the way it is. Including the last /.

Then, finally we can create our new CNAME record. This command will create the address Me.CoffeeBreakBiz.com and redirect to MyNewCustomer-ztcs9s.navcloudapp.net, with a TimeToLive of 1800 seconds:

New-CNameRecord -Domain 'CoffeeBreakBiz.com' -Record 'Me' -Value 'MyNewCustomer-ztcs9s.navcloudapp.net.' -TTL 1800

Or use Set-CNameRecord, which will check if the record already exists, and otherwise create it:

Set-CNameRecord -Domain 'CoffeeBreakBiz.com' -Record 'Me' -Value 'MyNewCustomer-ztcs9s.navcloudapp.net.' -TTL 1800

And a few examples for updating and removing CNAME records:

# Change Tenant to point to Unavailable website
#Set-CNameRecord 'MyNewCustomer.CoffeeBreakBiz.com' 't1' 'unavailable.CoffeeBreakBiz.com.'

# Change Tenant version : t1.CoffeeBreakBiz.com to v2.CoffeeBreakBiz.com
#Set-CNameRecord 'CoffeeBreakBiz.com' 't1' 'v2'

# Remove Tenant Redirection : t1.CoffeeBreakBiz.com.net
#if (Set-CNameRecord 'CoffeeBreakBiz.com' 't1') {
#    Remove-CNameRecord 'CoffeeBreakBiz.com' 't1'
#}

More documentation on the DNSMadeEasy.com api can be found here:

http://www.dnsmadeeasy.com/integration/pdf/API-Docv2.pdf

 

Below we have a sample script that you can base your script on –   Happy scripting!

 

 

=== Main body of script to copy ===

Function New-DMEHeaders {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $API,
[Parameter(Mandatory=$true,Position=2)]
[string] $Secret
)
[string] $Date = (get-date -format R (get-date).AddHours(0-(Get-Date -UFormat "%Z")))
$hmacsha = New-Object System.Security.Cryptography.HMACSHA1
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($Secret)
$signature = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($Date))
$hash = [string]::join("", ($signature | % {([int]$_).toString('x2')}))
 
$headers = @{
"x-dnsme-apiKey" = $API;
"x-dnsme-requestDate" = $Date;
"x-dnsme-hmac" = $hash
}
return $headers
}

Function Get-DMERecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $API,
[Parameter(Mandatory=$true,Position=2)]
[string] $Secret,
[Parameter(Mandatory=$true,Position=3)]
[string] $URI
)
$attempts = 0
while ($attempts -lt 5) {
try {
$headers = New-DMEHeaders -API $API -Secret $Secret
return Invoke-RestMethod -Method GET -Uri $URI -Headers $headers
}
catch {
sleep -Seconds 60
$attempts++
}
}
$headers = New-DMEHeaders -API $API -Secret $Secret
return Invoke-RestMethod -Method GET -Uri $URI -Headers $headers
}
 
Function Remove-DMERecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $API,
[Parameter(Mandatory=$true,Position=2)]
[string] $Secret,
[Parameter(Mandatory=$true,Position=3)]
[string] $URI
)
$attempts = 0
while ($attempts -lt 5) {
try {
$headers = New-DMEHeaders -API $API -Secret $Secret
return Invoke-RestMethod -Method DELETE -Uri $URI -Headers $headers
}
catch {
sleep -Seconds 60
$attempts++
}
}
$headers = New-DMEHeaders -API $API -Secret $Secret
return Invoke-RestMethod -Method DELETE -Uri $URI -Headers $headers
}
 
Function Set-DMERecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $API,
[Parameter(Mandatory=$true,Position=2)]
[string] $Secret,
[Parameter(Mandatory=$true,Position=3)]
[string] $URI,
[Parameter(Mandatory=$false)]
[string] $JSON,
[Parameter(Mandatory=$false)]
[string] $XML
)
if ($XML -and $JSON) {Write-Error "You may only specify JSON or XML, not both."; exit 999}
if (!$XML -and !$JSON) {Write-Error "You must specify JSON or XML data."; exit 998}
$attempts = 0
while ($attempts -lt 5) {
try {
$headers = New-DMEHeaders -API $API -Secret $Secret
if ($XML) {
return Invoke-RestMethod -Method PUT -Uri $URI -Headers $headers -ContentType "application/xml" -Body $XML
} else {
return Invoke-RestMethod -Method PUT -Uri $URI -Headers $headers -ContentType "application/json" -Body $JSON
}
}
catch {
sleep -Seconds 60
$attempts++
}
}
$headers = New-DMEHeaders -API $API -Secret $Secret
if ($XML) {
return Invoke-RestMethod -Method PUT -Uri $URI -Headers $headers -ContentType "application/xml" -Body $XML
} else {
return Invoke-RestMethod -Method PUT -Uri $URI -Headers $headers -ContentType "application/json" -Body $JSON
}
}
 
Function New-DMERecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $API,
[Parameter(Mandatory=$true,Position=2)]
[string] $Secret,
[Parameter(Mandatory=$true,Position=3)]
[string] $URI,
[Parameter(Mandatory=$false)]
[string] $JSON,
[Parameter(Mandatory=$false)]
[string] $XML
)
if ($XML -and $JSON) { throw "You may only specify JSON or XML, not both." }
if (!$XML -and !$JSON) { throw "You must specify JSON or XML data." }
$attempts = 0
while ($attempts -lt 5) {
try {
$headers = New-DMEHeaders -API $API -Secret $Secret
if ($XML) {
return Invoke-RestMethod -Method POST -Uri $URI -Headers $headers -ContentType "application/xml" -Body $XML
} else {
return Invoke-RestMethod -Method POST -Uri $URI -Headers $headers -ContentType "application/json" -Body $JSON
}
}
catch {
sleep -Seconds 60
$attempts++
}
}
$headers = New-DMEHeaders -API $API -Secret $Secret
if ($XML) {
return Invoke-RestMethod -Method POST -Uri $URI -Headers $headers -ContentType "application/xml" -Body $XML
} else {
return Invoke-RestMethod -Method POST -Uri $URI -Headers $headers -ContentType "application/json" -Body $JSON
}
}
 
Function Get-DomainId {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $Domain
)
(Get-DMERecord -API $DMEAPI -Secret $DMESecret -URI $DMEBaseUrl).data | % { if ($_.name -eq $Domain) { return $_.id } }
}
 
Function Get-CNameRecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $Domain,
[Parameter(Mandatory=$true,Position=2)]
[string] $Record
)
$Record = $Record.ToLowerInvariant()
$id = Get-DomainId $Domain
$Url = "$DMEBaseUrl$id/records?type=CNAME&recordName=$Record"
(Get-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url).data | % { if ($_.name -eq $Record) { return $_ } }
}

Function Remove-CNameRecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $Domain,
[Parameter(Mandatory=$true,Position=2)]
[string] $Record
)
$Record = $Record.ToLowerInvariant()
$id = Get-DomainId $Domain
$Url = "$DMEBaseUrl$id/records?type=CNAME&recordName=$Record"
$dnsrecord = (Get-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url).data | % { if ($_.name -eq $Record) { return $_ } }
if ($dnsrecord) {
$recordid = $dnsrecord.id;
$Url = "$DMEBaseUrl$id/records/$recordid"
Remove-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url
}
}

Function New-CNameRecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $Domain,
[Parameter(Mandatory=$true,Position=2)]
[string] $Record,
[Parameter(Mandatory=$true,Position=3)]
[string] $Value,
[Parameter(Mandatory=$false,Position=4)]
[int] $TTL = 1800
)
$Record = $Record.ToLowerInvariant()
$id = Get-DomainId $Domain
$Url = "$DMEBaseUrl$id/records/"
$json = ('{"name":"' + $Record + '","type":"CNAME","value":"' + $Value + '","gtdLocation":"DEFAULT","ttl":' + $TTL + '}')
New-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url -JSON $json
}
 
Function Set-CNameRecord {
Param(
[Parameter(Mandatory=$true,Position=1)]
[string] $Domain,
[Parameter(Mandatory=$true,Position=2)]
[string] $Record,
[Parameter(Mandatory=$false,Position=3)]
[string] $Value,
[Parameter(Mandatory=$false,Position=4)]
[int] $TTL
)
$Record = $Record.ToLowerInvariant()
$id = Get-DomainId $Domain
$Url = "$DMEBaseUrl$id/records?type=CNAME&recordName=$Record"
$dnsrecord = (Get-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url).data | % { if ($_.name -eq $Record) { return $_ } }
if (!$dnsrecord)
{
$Url = "$DMEBaseUrl$id/records/"
$json = ('{"name":"' + $Record + '","type":"CNAME","value":"' + $Value + '","gtdLocation":"DEFAULT","ttl":' + $TTL + '}')
New-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url -JSON $json
} else {
$recordid = $dnsrecord.id
if (!$Value) { $Value = $dnsrecord.Value }
if (!$TTL) { $TTL = $dnsrecord.ttl }
$Url = "$DMEBaseUrl$id/records/$recordid"
$json = ('{"name":"' + $Record + '","type":"CNAME","value":"' + $Value + '","id":"' + $recordid + '","gtdLocation":"DEFAULT","ttl":' + $TTL + '}')
Set-DMERecord -API $DMEAPI -Secret $DMESecret -URI $Url -JSON $json
}
}

Best regards

navblog_coffeebreakteam

 

We're always looking for feedback and would like to hear from you. Please head to the Dynamics 365 Community to start a discussion, ask questions, and tell us what you think!