web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Dynamics 365 Community / Blogs / Goshoom. NET Dev Blog / Console output from Dynamic...

Console output from Dynamics AX

Martin Dráb Profile Picture Martin Dráb 237,978 Most Valuable Professional

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.


This was originally posted here.

Comments

*This post is locked for comments