By Steve Endow
I created this PowerShell script to automatically re-create my Business Central Docker Containers every morning.
Version 3 of the script records BC Image version information to a log file and to the notification email.
(Thanks to Kishor Mistry for the improved method to get the BC image version!)
Email notification with full log |
#v3.0 - June 17, 2021
#
#Define the host, container, and image names
$server = $env:COMPUTERNAME
$containerName = "dev18"
$imageName = "i" + $containerName
$dnsIP = "192.168.25.21"
#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"
$currentDate = Get-Date -Format "yyyy-MM-dd"
$currentTime = Get-Date -Format "hh:mm:ss"
#Specify file location for activity transcript log file
$transcriptFile = "D:\BCPowerShell\Logs\" + $fileDateTime + " " + $containerName + " Container Build Log.txt"
#File location for BC version tracking
$versionFile = "D:\BCPowerShell\Logs\" + $containerName + " BC Version Log.txt"
#Start recording transcript
Start-Transcript -Path $transcriptFile
#Record the start time
$StartTime = $(get-date)
#Define email configuration
$emailFrom = "precipioazure@gmail.com"
$emailTo = "steveendow@gmail.com"
$smtpServer = "smtp.gmail.com"
$port = "587"
#Email password file created using: Read-Host -AsSecureString | ConvertFrom-SecureString | Out-File -FilePath D:\BCPowerShell\Gmail.securestring
$passwordFile = "D:\BCPowerShell\Gmail_sendow.securestring"
$securePassword = ConvertTo-SecureString (Get-Content -Path $passwordFile)
$smtpCred = New-Object -TypeName PSCredential ($emailFrom, $securePassword)
$subject = $server + ": " + $simpleDate + " BC Build Log for Container: " + $containerName
$started = "Start time: " + $StartTime
$body = $started + "`n"
$errFlag = $false
$warnFlag = $false
#Output startup info
Write-Output $subject
Write-Output $started
Try {
#********************************************************************************************
#Get image version from current container
try {
$currentImageNum = (Get-BcContainerAppInfo -containerName $ContainerName | Where-Object -Property Publisher -EQ "Microsoft" | Where-Object -Property Name -EQ "Application").Version.ToString()
}
catch
{
$currentImageNum = 'No Container'
}
#********************************************************************************************
Remove-Module BcContainerHelper -ErrorAction SilentlyContinue
Import-Module BcContainerHelper -ErrorAction SilentlyContinue
#Remove old artifacts and images
Flush-ContainerHelperCache -cache bcartifacts -keepDays 7
#Remove existing container if it exists
Remove-BCContainer $containerName
#Remove old images without deleting base OS image
docker images --format "{{.Repository}}\t{{.Tag}}\t{{.ID}}" |
Select-String "businesscentral" -notMatch |
ConvertFrom-CSV -Delimiter "`t" -Header ("Repository","Tag","ID") |
Sort-Object Tag | % ID | % { docker rmi $_ }
#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 -country us -select Latest -type Sandbox
New-BcContainer `
-accept_eula `
-containerName $containerName `
-credential $credential `
-auth $auth `
-artifactUrl $artifactUrl `
-dns $dnsIP `
-updateHosts `
-licenseFile "D:\BCPowerShell\BCLicense\BCv18.flf" `
-assignPremiumPlan `
-includeTestToolkit `
-includeAL `
-shortcuts None `
-imageName $imageName
Publish-BcContainerApp -appFile "D:\zDisks\BCCL\BCCL_1.15.0.125\installer\Extension\Hougaard.com_Business Central Command Line_1.15.0.125.app" -containerName dev18 -install -packageType Extension -sync -syncMode Add
}
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 {
#********************************************************************************************
#Get image version info from new container
try {
$newImageNum = (Get-BcContainerAppInfo -containerName $ContainerName | Where-Object -Property Publisher -EQ "Microsoft" | Where-Object -Property Name -EQ "Application").Version.ToString()
}
catch
{
$newImageNum = 'No Container'
}
#Create line to write to BC Version log file
$versionEntry = $currentDate.ToString() + "`t" + $currentTime.ToString() + "`t" + $currentImageNum + "`t" + $newImageNum
#Write entry to BC Version log file
Add-Content -Path $versionFile -Value $versionEntry
#Add BC Versions to email body
$body += "`n" + "Prior BC version: " + $currentImageNum
$body += "`n" + "New BC version: " + $newImageNum + "`n"
#********************************************************************************************
#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"
}
Else
{
$output = "Container OS Build " + $containerOSBuild + " matches Host OS Build " + $osVersion
Write-Output $output
$body += "`n" + $output + "`n"
}
ipconfig /flushdns;
#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
$greeting = "Greetings human.`n`nIt appears there was an error building your Business Central Docker Container: " + $containerName + "`n`n"
}
elseif ($warnFlag)
{
$subject = "WARNING: " + $subject
$greeting = "Greetings human.`n`nI prepared your Business Central Docker Container: " + $containerName + ", but detected potential issues.`n`n"
}
else
{
$greeting = "Greetings human.`n`nI have prepared your Business Central Docker Container: " + $containerName + "`n`n"
}
$body = $greeting + $body
#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
Stop-Transcript
#Send the email
Send-MailMessage -From $emailFrom -To $emailTo `
-Subject $subject `
-Body $body `
-Attachments $transcriptFile `
-SmtpServer $smtpServer `
-Credential $smtpCred `
-Port $port `
-UseSsl
}
Steve Endow is a Microsoft MVP in Los Angeles. He works with Dynamics 365 Business Central, Microsoft Power Automate, Power Apps, Azure, and .NET.
You can also find him on Twitter and YouTube