Console output from Dynamics AX

Although Dynamics AX offers a whole range of options to call application logic (e.g. Business Connector or WCF), it’s often necessary to use the client (ax32.exe) directly from command line. That applies especially to administration and development – automated configuration, compilation, update of cross references and so on.

Unfortunately ax32.exe doesn’t return any console output, therefore it’s difficult to find out whether everything works or something failed. That’s a problem especially for automated scripts because they can’t look into GUI to learn what’s happening.

One possible approach is to write status information from Dynamics AX to a log (file, EventLog) and analyze them afterwards, but recording to console has many advantages – output is clearly bound to a concrete process and command, it’s not needed to manage access to external logs, console can further process the output easily (including redirection to file) and so on.

Console output can be easily writen to by .NET class System.Console so let’s create an X++ class calling System.Console through a very simplified interface.

class SysConsole
{
    public static void writeLine(str _message)
    {
        ;
        System.Console::WriteLine(_message);
    }
    public static void writeErrorLine(str _message)
    {
        System.IO.TextWriter writer = System.Console::get_Error();
        ;
        writer.WriteLine(_message);
        writer.Close();
    }
}

Then messages may be sent to console output simply be calling these methods, as shown in the following test job:

SysConsole::writeLine("First!");
SysConsole::writeLine("Another message");
SysConsole::writeErrorLine("Something is wrong");

If you start Dynamics AX client from command line and the test job afterwards, unfortunately nothing will happen. It’s necessary to use a bit more complicated approach.

The first problem is that command line doesn’t wait for program output, it just runs the program and continues with subsequent commands. But mere waiting for program to end, as in the following Powershell script, still shows nothing.

Start-Process ax32.exe -Wait

But if you redirect output and error streams to files, they’re written correctly.

Start-Process ax32.exe -Wait -RedirectStandardOutput c:\Temp\out.txt -RedirectStandardError c:\Temp\error.txt

So the output can be obtained and that’s the most important news. In some cases the simple recording to file is suitable (compared with writing directly from AX, this allows the caller to define file names) but it still isn’t “normal” console output.

In the next attempt we’re going to create a process directly by .NET class System.Diagnostics.Process, redirect the output and read it from process properties:

$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = 'C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\ax32.exe'
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
 
$process.Start() | Out-Null
$process.WaitForExit()
 
Write-Host $process.StandardOutput.ReadToEnd()
Write-Host $process.StandardError.ReadToEnd() -ForegroundColor Red
 
$process.Close()

That really displays output of our test job in console:

But this solution still has some shortcomings, especially that the output is shown only after the process ended. That’s sufficient to display final results, but not for any indication of progress.

C# offers quite straightforward solution by means of events OutputDataReceived and ErrorDataReceived – see the following simple console application.

class Program
{
    static void Main(string[] args)
    {
        new Program().Run();
    }
 
    void Run()
    {
        string file = @"c:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\ax32.exe";
 
        using (Process ax = new Process())
        {
            ax.StartInfo.FileName = file;
            ax.StartInfo.UseShellExecute = false;
            ax.StartInfo.RedirectStandardOutput = true;
            ax.StartInfo.RedirectStandardError = true;
            ax.OutputDataReceived += new DataReceivedEventHandler(outputDataReceived);
            ax.ErrorDataReceived += new DataReceivedEventHandler(errorDataReceived);
            ax.Start();
 
            ax.BeginOutputReadLine();
            ax.BeginErrorReadLine();
            ax.WaitForExit();
        }
        Console.ReadLine();
    }
    private void outputDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (!String.IsNullOrEmpty(e.Data))
        {
            Console.WriteLine(e.Data);
        }
    }
    private void errorDataReceived(object sender, DataReceivedEventArgs e)
    {
        if (!String.IsNullOrEmpty(e.Data))
        {
            Console.WriteLine("Error: {0}", e.Data);
        }
    }
}

Although Powershell allows us to handle the same events, I had to choose a slightly more complicated solution.

$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = "ax32.exe"
$process.StartInfo.UseShellExecute = $false
$process.StartInfo.RedirectStandardOutput = $true
$process.StartInfo.RedirectStandardError = $true
 
Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -SourceIdentifier AxOutput
Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -SourceIdentifier AxError
 
$process.Start() | Out-Null
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
 
Function GetAxMessages
{
    Get-Event -SourceIdentifier AxOutput -ErrorAction SilentlyContinue | %{
        if ($_.SourceEventArgs.Data)
        {
            $_.SourceEventArgs.Data
        }
        Remove-Event -EventIdentifier $_.EventIdentifier
    }
 
    Get-Event -SourceIdentifier AxError -ErrorAction SilentlyContinue | %{
        if ($_.SourceEventArgs.Data)
        {
            Write-Error $_.SourceEventArgs.Data
        }
        Remove-Event -EventIdentifier $_.EventIdentifier
    }
}
 
while (!$process.WaitForExit(1000))
{
    GetAxMessages
}
 
$process.WaitForExit()
$process.Close()

The scripts starts ax32.exe, subscribe to RedirectStandardOutput and RedirectStandardError events and handle newly added events every second. Maybe it could be written simpler, anyway it does exactly what I need and that’s the main thing. All messages written from AX to output or error stream are caught and – with slight delay – sent to Powershell console. Errors are written by Write-Error so they may be handled in the usual way (e.g. to use -ErrorAction Stop parameter to stop on first error).

The Powershell code mentioned above is surely nothing what you would like to type manually to console. Fortunately I’ve already managed to integrate it to DynamicsAxCommunity module, so the only thing you have to do is to call Start-AXClient with -Wait parameter. (Download the latest version from repository, it’s not yet in any release.)

Console output may be utilized in many different ways – automated scripts can get messages from Dynamics AX, you can show progress of long-running processes (e.g. compilation) or debug messages, anything you like.

You can even send all errors from Dynamics AX to the error stream (i.e. call SysConsole::writeErrorLine() from Info.add()). If an error occurs, the calling scripts get info and can react somehow. Without capturing errors (in console or somehow else), Dynamics AX client would just continue to run or it would (as in the following example) ends without any indication of error.

Just in case – note that Dynamics AX normally doesn’t write anything to console (as far as I know) nor DynamicsAxCommunity contains any such code. Class SysConsole is not part of Dynamics AX as well. To make the example from the previous picture work, you have to implement calls in Dynamics AX yourself.

5 Comments

  1. Hello,

    Thanks for your cool article.

    I tried to use your trick to get feedback from the ‘synchronize’ startupcmd I modified to only synchronize the table I want, but I don’t get any output or error.
    It return nothing for both powershell and C# ways.
    When using redirection to files ‘out.txt’ and ‘error.txt’ are created but empty.

    My command looks like “ax32.exe -startupcmd=synchronize_MyTable”
    The command itself works well and I even can do step by step debuging and see writeConsole() invocation.

    > Do I miss something ? Need activate something to get the standard/error stream available ?

    Gilles

    • I tried to document everything what I know in the article, so I don’t have any additional information at this moment.

      But if you give me more information, maybe I’ll be able to help. What version of AX and what OS do you use? How exactly do you call the AX client?

      By the way, when I was testing the code, I simply had a job calling SysConsole::writeLine() – it’s more flexible to work with jobs than with startup commands.

  2. I’ve ‘partial’ good news 🙂

    1°) When I run this :
    “ax32.exe -startupcmd=synchronize_MyTable”
    => Std and error output are both raise once but Data is empty, my string message not transmitted.

    2°) When I run this (like you I guess):
    “ax32.exe -startupcmd=autorun_d:\\startup.xml”

    with startup.xml like:

    => Both std and error output was raised and contained Data 🙂

    Conclusion:
    It looks like when I invoke your awesome SysConsole::writeLine() from:
    – SysStartupCmd.construct(), it works 🙂
    – SysStartupCmdSynchronize.applInit(), writeLine() invoke isn’t received from C# side
    – I double check with another method of another startup cmd : SysStartupCmdAutoRun.inforRun(), writeLine() invoke isn’t received from C# side

    Is it possible the SysStartupCmd.construct()’s std output (looks like the one relaying message) is not the same than SysStartupCmdSynchronize.applInit() one ?
    How can I test it and redirect it to the correct one ?

    Thanks for your help
    AX 2012, version : Kernel 6.0.1108.882 / App 6.0.1108.670
    I’m more confortable with C# than X++ (by the way)

    • Okay, that seems to be obvious – applInit() is run on AOS, therefore the message is sent by ax32serv on the server machine, instead of by ax32 on client.
      This script works with ax32; getting messages from AOS would require some logic collecting server-based console messages, serializing them and sending them to console on client, which is not the intention of the script.

  3. Victory [SOLVED] 🙂
    Thanks to you and work mate Dmitry I finally find the way to make this working.

    SysConsole is by default RunOn=CalledFrom
    => then use std, err output from client or server

    If you switch SysConsole RunOn to ‘Client’
    => you get console messages anytime in your C# app

    Thanks for you help 😉
    Now I’ll automated all my deployments

Comments are closed.