Few days ago my night build of a Dynamics AX 2009 application stopped working correctly. Some objects were missing in the built layer, which of course caused many compilation errors.
It occurred that data were correctly obtained from version control, but the Combine XPO Tool (CombineXPOs.exe) couldn’t process them. (If you’re not familiar with that, Combine XPO is used to merge several .xpo files into a single file, typically (as in this case) to combine all files obtained from version control before importing to AX.)
CombineXPOs.exe returned exit code -4 and wrote this error message to the standard output stream:
Error [-4] The file is bad: C:\NightBuild\SourceFiles\bus\Data Dictionary\Tables\AddressCountryRegion.xpo. Exception message: System.ArgumentOutOfRangeException: Length cannot be less than zero. Parameter name: length
As you can expect, Combine XPO didn’t process remaining files, therefore the merged .xpo file was incomplete. The other part of the problem was that exit code was not handled by the build script.
The problematic .xpo contained this:
Exportfile for AOT version 1.0 or later Formatversion: 1 ***Element: END
As you can see, it doesn’t contain almost anything – not even object type and name. And Combine XPO is not the only tool that has troubles with such .xpo files – version comparison in AX2009 threw a runtime error on this object (it seems to work without issues in AX2012).
Version of Combine XPO Tool
When testing the behavior, I noticed that different versions of the Combine XPO Tool handle this situation differently:
Version 1.0.3974.781 (this is the version available on mfp’s blog and the one I used in night builds)
Result | Processing stopped at the corrupted .xpo file |
Exit code | -4 |
Standard output | Error [-4] The file is bad: C:\NightBuild\SourceFiles\bus\Data Dictionary\Tables\AddressCountryRegi on.xpo. Exception message: System.ArgumentOutOfRangeException: Length cannot be less than zero. Parameter name: length |
Version 6.0.947.5121 (downloaded from InformationSource; official name Microsoft Dynamics AX 2012 Combine XPO Tool Beta 1.0):
Result | All files processed |
Exit code | -4 |
Standard output | 1764 XPO files processed Ok |
Error output | CombineXPOs.exe : Warning [-5] CombineXPOs.exe : Error [C:\NightBuild\SourceFiles\bus\Data Dicti onary\Tables\AddressCountryRegion.xpo] Length cannot be less than zero. Parameter name: length |
Both versions complain about the corrupted .xpo, but the newer one simply skips it and continues, the older version stops the processing and you’ll get .xpo that doesn’t contain contents of all input .xpo files. The problem was probably noticed and the behavior changed. One interesting thing is that both versions returns exactly the same exit code.
How to create corrupted .xpo
It’s surprisingly easy to create such a corrupted .xpo. Follow these steps, for example:
- Find a table that exists in a lower layer (e.g. SYS) but not in your development layer.
- Add a new field.
- Add the object to version control.
- Check it in.
- Check it out.
- Remove the field.
- Check the object in again.
At point 7, you essentially check in an object that does not exist in the development layer anymore, because you removed all changes (the single field). AX then exports all changes (= none) which creates empty .xpo as described above (tested in AX2009 + SourceSafe and AX2012 R2 + TFS).
The described procedure is not what you should use – if you removing all changes, you’re in fact deleting the object, so you should really delete it and check the deletion into version control (deletion is simply one of action types in version control).
How to handle exit codes from Combine XPO
If the exit code was handled properly by the build script, the problem would be still there – but it would be obvious what’s wrong.
Because the new version of Combine XPO skips corrupted files, you may tend to ignore the problem. But that’s not the right approach, because there are other reasons why Combine XPO may fail. For example, error code -2 is returned if the source folder does not exist.
It would be also wrong to ignore exit code -4 – it’s returned for any exception in one part of Combine XPO; it’s not specific to this particular problem.
You could analyze messages returned by the error stream, nevertheless the safest way is to stop processing every time when exit code is not zero.
As an example, look at the following Powershell code. It works with both versions of Combine XPO (it checks both error and output stream for error message) and it also handles exit codes without any message. It simply throw an exception, because all exceptions are then handled by the build script using trap
statement (not shown here).
$output = & $combineXpoExe -XpoDir $sourceFilesDir -CombinedXpoFile $combinedFile -utf8 2>&1 if ($LastExitCode -ne 0) { $err = "CombineXPOs.exe: Exit code $LastExitCode" if ($output) { $err = $output | ?{$_.getType().Name -eq "ErrorRecord"} if (!$err) { $err = $output } } throw $err }
On a related note, there is a bug in older versions of AX 2012 (I have to check which CU fixed it) where X++ Project XPOs get corrupted. There is a hotfix, and it’s also fixed in the more recent CUs (sorry, not sure… i think CU3 fixed it)