Sunday, February 21, 2021

Automated PowerShell Script v2.1 to Recreate My Business Central Docker Containers

 By Steve Endow


UPDATE:  I have posted version 3 of the script here


In July 2020 I posted an earlier version of this script.  This updated version has a few refinements, such as removing and importing BcContainerHelper to ensure the latest version is being used, adding Flush-ContainerHelperCache to remove old helper files, and removing old Docker images.

Automate, Automate, Automate

I setup a "Dev Container" task in Windows Task Scheduler to run this script every morning at 6:45am so that I always have a fresh BC container based on the latest BC artifacts.  I call my main Container "dev17", based on the current 17.x version of Business Central.  Do NOT name your container just "dev"--see my video here on why that will cause problems.


And you'll notice that I have a second "container TEST" task setup in Windows Task Scheduler that creates a second Container, called test17.  If for some reason dev17 isn't working or wasn't created properly, I can usually rely on test17 to be working.  I just did a livestream that demonstrated why this is valuable--my dev17 container build script failed due to a download error, but I was able to easily work with my test17 container.

Changes you will probably want to make to my sample script:

1. Optionally change the container name to suit your preference

2. Set the DNS IP to your preferred DNS server. I have an internal Pi-Hole DNS server, but you will likely use an external DNS server, such as 8.8.8.8

3. Change the path for your transcript files to a drive & directory that works for you

4. Change the email addresses and SMTP server

5. Create a secure string password file containing your email password using the included command

6. Set your container BC password if you use something other than admin and P@ssw0rd to login

7. Change the parameters for the New-BcContainer command to meet your needs (e.g. license file, includeTestToolkit, etc.


Below is version 2.1 of the script, as of February 2021.

 #v2.1 - February 21, 2021  
 #  
 #Define the host, container, and image names  
 $server = $env:COMPUTERNAME  
 $containerName = "dev17"  
 $imageName = "i" + $containerName  
 $dnsIP = "8.8.8.8"  
 #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 = "youremail@gmail.com"  
 $emailTo = "youremail@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_password.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 {  
   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 #-version 16.5 #-type 'Sandbox' -version '' -country 'us' -select 'Latest'  
   New-BcContainer `  
     -accept_eula `  
     -containerName $containerName `  
     -credential $credential `  
     -auth $auth `  
     -artifactUrl $artifactUrl `  
     -dns $dnsIP `  
     -updateHosts `  
     -licenseFile "D:\BCLicense\BCv17.flf" `  
     -assignPremiumPlan `  
     -includeTestToolkit `  
     -includeAL `  
     -imageName $imageName  
   }  
   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"  
     }  
     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

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...