Saturday, July 25, 2020

Fully automate the Business Central Container build process

By Steve Endow

I don't like maintenance tasks.  I don't like having to remember to do something on a regular basis.  I don't like having to do low value tasks constantly.

While it has been valuable for me to manually run NavContainerHelper the last several months, I think I am finally at a point where I sufficiently understand how it works and what it does.  I'm now ready to automate my Business Central Container build process so that I don't have to do it manually.

Just tell me how it went...

This isn't anything new--I'm just finally getting around to doing it for me with the features that I want.  If there are other ways or better ways to do this, let me know.

I share the background, my thinking, and my journey creating the script in this video.

In short, I've just wrapped the call to New-BcContainer in some additional PowerShell script to enable full logging, some basic error handling, and email notification.

For now, I'll schedule this script on my test Docker server so that I always have a container ready to go with the latest BC build.

As I think of ways to improve and refine the automated build process, I may add additional functionality.  Like cleaning up older builds and images, perhaps centralized logging, and improved error handling and problem detection.  But given that this is the first PowerShell script I've written, I think it's a decent start.

I eventually want to learn how to use Azure DevOps and build a pipeline that includes this BC Container build process, so this is also a first step in that process.

The downside:  You will want to review the script, understand what it does, and adjust it to work in your environment.  This is just a sample based on the settings that worked for me in my environment.  If you've never worked with PowerShell before, I empathize, but it should be fairly easy to research the commands I used and understand what the script is doing.

#v1.1 - July 26, 2020
#Define the host, container, and image names
$server = $env:COMPUTERNAME
$containerName = "test"
$imageName = $containerName + "1"
$dnsIP = ""

#Define date values
$simpleDate = Get-Date -Format "M/d/yy"
$dateTime = Get-Date -Format "M/d/yy HH:mm"
$fileDateTime = Get-Date -Format "yyyy-MM-dd HHmm"

#Specify file location for activity transcript log file
$transcriptFile = "D:\BCPowerShell\Logs\" + $fileDateTime + " " + $containerName + " Container Build Log.txt"

#Start recording transcript
Start-Transcript -Path $transcriptFile

#Record the start time
$StartTime = $(get-date)

#Define email configuration
$emailFrom = ""
$emailTo = ""
$smtpServer = ""
$port = "587"
#Email password file created using:  Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath D:\BCPowerShell\Gmail.securestring
$passwordFile = "D:\BCPowerShell\Gmail.securestring"
$securePassword = ConvertTo-SecureString (Get-Content -Path $passwordFile)
$smtpCred = New-Object -TypeName PSCredential ($emailFrom, $securePassword)

$subject = $server + ": " + $simpleDate + " BC Build Log for Container: " + $containerName
$body = "Greetings human.`n`nI have prepared your Business Central Docker Container: " + $containerName + "`n`n"

$started = "Start time: " + $StartTime
$body += $started

$errFlag = $false
$warnFlag = $false

#Output startup info
Write-Output $subject
Write-Output $started

Try {

    #Remove existing container if it exists
    Remove-BCContainer $containerName
    #Call New-BcContainer - Use New-BCContainerWizard to generate the script/params for your environment
    $password = 'P@ssw0rd'
    $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
    $credential = New-Object pscredential 'admin', $securePassword
    $auth = 'UserPassword'
    $artifactUrl = Get-BcArtifactUrl -type 'Sandbox' -version '' -country 'us' -select 'Latest'
    New-BcContainer `
        -accept_eula `
        -containerName $containerName `
        -credential $credential `
        -auth $auth `
        -artifactUrl $artifactUrl `
        -imageName $imageName `
        -dns $dnsIP `
    Catch {
        #If the script fails or has an error, output to error stream
        Write-Error "Um, something didn't go well:"
        Write-Error $_
        #Capture error in email body 
        $body += "`nERROR:`n`n" + $_ + "`n`n"
        $errFlag = $true
    Finally {
        #PROTOTYPE - This is fairly fragile and needs improvement
        #Search for the Container OS Build number and compare it to the Host OS build number
        [string]$containerOSVersion = Select-String -Pattern "Container OS Version: " -SimpleMatch -Path $transcriptFile
        $trimBefore = $containerOSVersion.IndexOf("Version: ")
        #This is pretty crude parsing, so there is presumably a more elegant method
        $containerOSBuild = $containerOSVersion.Substring($trimBefore+9, ($containerOSVersion.Length-($trimBefore+9))).Split(" (")[0].Split(".")[3]
        $osVersion = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name UBR).UBR
        If ($containerOSBuild -ne $osVersion)
            $warnFlag = $true
            $buildWarning = "`nWARNING: Container OS Build " + $containerOSBuild + " does NOT match Host OS Build " + $osVersion
            Write-Error $buildWarning
            $body += "`n" + $buildWarning + "`n"
            $output = "Container OS Build " + $containerOSBuild + " matches Host OS Build " + $osVersion
            Write-Output $output
            $body += "`n" + $output + "`n"

        #Record end time
        $EndTime = $(get-date)
        $output = "End time: " + $EndTime
        $body += "`n" + $output 
        Write-Output $output 

        #Calculate elapsed time
        $elapsedTime = $EndTime - $StartTime
        $totalTime = "{0:HH:mm:ss}" -f ([datetime]$elapsedTime.Ticks)
        $output = "Elapsed time: " + $totalTime
        $body += "`n" + $output 
        Write-Output $output

        $body += "`n`nFull log file is attached" 

        If ($errFlag)
            $subject = "ERROR: " + $subject
        elseif ($warnFlag)
            $subject = "WARNING: " + $subject

        #Finish the transcript recording
        #The Transcript must be stopped before trying to send the email,
        #otherwise the log file will be locked, causing the email to fail
        #Send the email
        Send-MailMessage -From $emailFrom -To $emailTo `
            -Subject $subject `
            -Body $body `
            -Attachments $transcriptFile `
            -SmtpServer $smtpServer `
            -Credential $smtpCred `
            -Port $port `

Steve Endow is a Microsoft MVP in Los Angeles.  He works with Dynamics 365 Business Central, Microsoft Power Automate, Power Apps, Azure, .NET, Dynamics GP, and SQL Server.

You can also find him on Twitter and YouTube

No comments:

Post a Comment

All comments must be reviewed and approved before being published. Your comment will not appear immediately.

How many digits can a Business Central Amount field actually support?

 by Steve Endow (If anyone has a technical explanation for the discrepancy between the Docs and the BC behavior, let me know!) On Sunday nig...