In-place build script

In recent posts (here and here) I presented a PowerShell module simplifying some administrative tasks in Dynamics AX. I also promised to show its usage in builds both with and without Team Foundation Server.

To use TFS Build has some advantages – e.g. for logging and result reporting, automatic creating of work items if a build fails and so on. Without TFS, we have to implement all we need by ourselves – nevertheless TFS is really used just in minority of companies, so a TFS-free solution is often necessary.

This post deal with such a script – I want especially to demonstrate how easily you can implement an infrastructure for build progress logging and result reporting in PowerShell.

Features

I developed and really use this script for updates of development environments, not for full-scale builds. It’s able to compile AX applications, to update cross-references, restart AOSes etc. The build progress is logged to file and if configured, results including the log are sent as an e-mail.

The log content looks like this:

Dynamics AX 2012 Automated Build
Run by: mdrab
AX config: X:\AxConfig\DEV.axc
==========
[19:20:24] Started
[19:20:24] Restarting AOS
[19:22:56] AOS restarted
[19:22:56] Compiling application
[22:22:48] Application compiled
X++ compilation errors: 4
[22:22:48] Compiling IL
[22:37:07] IL compiled
[22:37:07] Synchronizing database
[22:47:20] Database synchronized
[22:47:20] Finished

What follows is an e-mail about a successful build (that means in this scripts that all steps passed off, not necessarily without compilation errors). Notice the attached log and compilation results.

If the build fails, the e-mail contains a text of the exception and a description of the place where it occurred:

Download

The script is placed in the same CodePlex project as DynamicsAxCommunity modul, specifically here.

Prerequisites

The script requires DynamicsAxCommunity module – that must be installed either in a PowerShell profile or alternatively in the script itself. It also expects to be run on the same machine as AOS.

Additional requirements depends on actions to be performed. One example is access to Dynamics AX (don’t forget about that especially when you make a scheduled task running under another user).

Script description

The script requires the only parameter – a path to a Dynamics AX client configuration:

param
(
	[Parameter(Mandatory=$true)]
	[string]$axConfigFile
)

The script is divided to regions illustrating its structure:

In-place build script structure

In Process definition region you can define which steps will be performed:

$restartAos = $true
$runCompilation = $true
$runCompilationToIL = $true
$runSynchronization = $true
$runXref = $true

and how long will the system wait for their completion:

$compilationTimeout = (New-TimeSpan -Hours 4).TotalSeconds
$ilCompilationTimeout = (New-TimeSpan -Minutes 30).TotalSeconds
$synchronizationTimeout = (New-TimeSpan -Minutes 30).TotalSeconds
$xrefTimeout = (New-TimeSpan -Hours 6).TotalSeconds
$xrefIndexTimeout = (New-TimeSpan -Minutes 30).TotalSeconds

E-mail parameters region defines whether an e-mail with results should be sent, the SMTP server etc.

In Variables, several variables are initialized, for example $logFileName.

Functions contains two functions – LogMessage writing a message to the log file and SendEmail sending the results.

Error handling region uses trap statement to catch and log exceptions.

Some dirty work is done in Initialization – input validation and log file initialization.

Process region performs the build itself. It always verifies that the particular step should be run, log the start and the end and calls a function from DynamicsAxCommunity module. See for example the database synchronization:

if ($runSynchronization)
{
	LogMessage "Synchronizing database" -AppendTime
	Synchronize-AXDatabase -LogPath $logdir -Timeout $synchronizationTimeout
	LogMessage "Database synchronized" -AppendTime
}

Finally, Send status e-mail region sends an e-mail, if everything went successfully.

Automatic start

Automatic starts can be easily set in Task Scheduler in Widows – it supports delayed starts, periodic runs and also other activation events if needed.

Create a new task in Task Scheduler and choose Start a program as Action. Specify powershell.exe in Program/script field and -NonInteractive -File “path to script.ps1” “path to configuration.axc” in Add arguments field. Don’t forget about Prerequisites.

Conclusion

This script is not very universal – I made it for conditions on my project and it likely won’t exactly meet your requirements. But I consider it’s architecture both sufficiently simple and useful in the same time – and it’s up to you to adapt it to your needs.

Currently most partners manage environments manually, which leads to permanent troubles with badly compiled applications, late detected errors, outdated cross-references and so on. Automation is an effective solution of these problems – it would be a pity not to use it.

5 Comments

  1. hi there.
    In order for me to get this to work for me, i had to change the sendemail function a bit:
    if ((Test-Path $logDirBase)-and (Test-Path $logFile) -and ((Get-Content $logFile) -ne $null))

    I took out the global reference.
    I am relatively new to Powershell.. I was able to finish with only the Sync.
    I’ll be running the entire thing tonight.
    Thanks for the post.. great stuff.
    This will help a lot.

  2. one more comment…
    In the method, Process, in the if (runXref), the cmdlet Update-AXXRefIndex doesn’t have the parameter -LogPath.
    I removed it from my script, and will be testing it again today.

  3. Martin Dráb

    The Real Person!

    Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

    Hi gary, many thanks for the feedback.

    -LogPath is obviously a bug, I’ll fix it soon.

    About SendMail, what problem are you fixing? I’m not aware about any situation when $logDirBase contains an invalid path. If there is such a situation, let’s fix it in the Variable region, where $logDirBase is filled.

  4. Hi Martin,
    first of all many thanks for your great postings! I especially appreciate the AX Community Module, which unifies many useful tools for AX deployment.
    It would be great if you could have a look at my following issue, maybe you already encountered the problem and got a simple solution.

    In the project I am working in we are performing an upgrade from AX 2009 to AX 2012.

    For the solution in AX2009, we have an automated build procedure which does the following steps:
    –> Get the latest XPO files from TFS and combine them
    –> Take a standard, “virgin” AX2009 application folder as base, start the build AOS and import the XPOs
    –> Compile, synchronize
    –> Copy the build application folder to the test system.
    –> When deploying a new version to production environment, copy the layer file and labels to production environment and compile.

    Besides the DEV environments (we have one AOS/database per developer), for this procedure we need a “Standard” application folder, a build AOS and a test AOS with corresponding databases.

    I now have to get working a similar build for AX2012.
    The big difference in AX2012 which troubles me is the “ID question”.
    Deploying in AX2012 using exportstore/importstore has the great advantage of minimal downtime of the production environment, but it requires to be very careful with IDs.
    We have to guarantee that the IDs from our test environment remain the same on the production environment of our customer, if we want to deploy our solution via exportstore/importstore.
    To deal with this difficulty I have thought of the following solution, because I still haven’t found a simpler one.

    Split the build in 2 phases:
    –> first, create our desired model on a standard, “virgin” AX2012 installation. In this model import the XPOs from TFS.
    XPOs in AX2012 don’t save IDs anymore (just the Origin value), so everytime I import XPOs on a empty installation IDs will be randomized.
    –> second, export/import the desired model (just the model, not the modelstore) to another standard AX 2012 installation (must have the same IDs as the production system). Compile, generate CIL, synchronize.
    –> Delete the created model on the first system, but leave it on the second system.
    –> Exportstore/importstore to our test system. This modelstore will then also be used for deployment on the production environment.

    Doing so, the IDs on the second build system are randomized only the first time when I launch the build procedure.
    Every other time, if I import my model over the existing model without deleting it, IDs are preserved because of the ID assignment procedure described in http://technet.microsoft.com/en-us/library/hh352326.aspx

    A possibility to think of would be to use just one build system and always import the XPOs via file or VCS-synchronize in AX over the existing ones.
    Afaik this approach doesn’t work for deleted or renamed metadata.

    What do you think of this? Have you already encountered similar problems and maybe have a good advice for this problem?

    Many thanks and cheers,
    Franz

  5. Martin Dráb

    The Real Person!

    Author Martin Dráb acts as a real person and passed all tests against spambots. Anti-Spam by CleanTalk.

    Hi Franz,
    to be honest, I’m not sure if I can tell you anything new. Automated builds are not used in my current project and I don’t care about ID’s in my home environment, so I’ve never been forced to solve this issue.

    I would say you’re process is good – sure, it requires several steps, but it’s not so big issue.

    I see one more solution – we could build the environment from TFS, ignoring object ID’s completely, and then synchronize ID’s with any other environment. It would need to look at Origin field in dbo.ModelElement (or even Name + ParentId), compare the value and update AxId (+ ParentId on children). If the AxId was already used, a new ID would be allocated for the conflicting element.

    It would require some work, but I don’t think it’s too complicated (we can even reuse some logic implemented as stored procedures, e.g. XU_GetNextAvailableAxId). The question is whether it is worth doing.

Comments are closed.