Item Journal Approval Workflow

I’ve create an Item Journal Approval Workflow. It can be used to change the default order settings for a journal of items as part of a workflow. The Workflow contains three approval steps, for purchase, inventory management and sales. Each approval sets or clears the stopped flag in the default order settings in the corresponding tab.

ItemJournalApprovalWF

 

Watch the Video at YouTube:

Item Journal Approval Workflow for Dynamics AX 2009

 

Download the Source at Axaptapedia

AX 2009 Database Reverse Engineering

I’ve made a video to demostrate how easy it is to reverse engineer tables from Dynamics AX 2009 Visio:

AX 2009 Database Reverse Engineering

Passed MB5-858 Sure Step

I’ve passed the MB5-858 exam, Managing Microsoft Dynamics Implementations Smiley

mb5-858-500

Some thoughts about MB5-858

  • Attending a course is good idea
  • The official training materials aren’t usefull to learn for the exam (IMHO)
  • You should already know Sure Step
  • Don’t spend too much time on the agile project type

Query NoYes fields in AIF 4.0

found an interesting behavior in AX 4.0 AIF. Querying a document with a NoYes field e.g. InventLocation.Manual requires to use the value “Yes” to become TRUE but value “1” doesn’t work.

var client = new InventLocationServiceSoapClient();
client.ClientCredentials.Windows.ClientCredential.Domain = "insideax";
client.ClientCredentials.Windows.ClientCredential.UserName = "AifUser";
client.ClientCredentials.Windows.ClientCredential.Password = "********";

var context = new DocumentContext();
context.MessageId = Guid.NewGuid().ToString();
context.SourceEndpoint = "EXTAPP";
context.DestinationEndpoint = "APP";

var query = new QueryCriteria();
query.CriteriaElement = new CriteriaElement[1];
query.CriteriaElement[0] = new CriteriaElement();
query.CriteriaElement[0].DataSourceName = "InventLocation";
query.CriteriaElement[0].FieldName = "Manual";
query.CriteriaElement[0].Operator = Operator.Equal;
query.CriteriaElement[0].Value1 = "1";

var axdInventLocation = client.findListInventLocation(context, query);
Console.WriteLine(axdInventLocation.InventLocation == null);

// new guid
context.MessageId = Guid.NewGuid().ToString();
// change value
query.CriteriaElement[0].Value1 = "Yes";

axdInventLocation = client.findListInventLocation(context, query);
Console.WriteLine(axdInventLocation.InventLocation == null);
Console.ReadKey();

Result is shown here:

image

Windows Server 2008 Active Directory, Configuration (70-640) passed

I passed the 70-640 Active Directory exam Smiley  To summarize it’s a good idea to have some practice and it’s extremely useful to attend a course or at least watch some training videos e.g. Trainsignal.

mctsrgb_1078_1269

Upgrade Issues (Part 3)

RefRecId fields without Extended Datatype

The upgrade scripts to AX 2009 converts RefRecId fields to Int64 data types. In some cases when a <Something>RefRecId field on a table has not an extended data type but was created as integer field you get a synchronization error.

Illegal data conversion from original field TABLE.FIELDREFRECID to TABLE.FieldRefRecId: Unable to convert string data types to anything but INT, or REAL field types.

The error message doesn’t really fit to the problem Trauriges Smiley

One way to deal with this issue is to copy the table content to another database. Next delete the malicious field and create a new one with RefRecId as extended datatype. AX will synchronized and delete the field on the database. Finally restore the data from the other database.importexportdb

I recommend to use the SQL Server Data Import/Export wizard.

  1. Create a new database called Backup
  2. Start SQL Server Import / Export from Programs Menu
  3. Source DB is the Upgrade DB
  4. Target DB is Backup (don’t use flat files etc.)
  5. Copy at least on table or view, select the malicious table and run
  6. In AX create a new field named as the defect one but as Int64 with RefRecId data type, e.g. MyRefRecId2
  7. In AX delete the original field and synchronize
  8. Start SQL Server Import/Export from Programs Menu and copy saved data back to Upgrade DB
  9. At selected table or view, activate overwrite instead of append

Upgrade Issues (Part 2)

AX 2009 AOS does not start

There are multiple reasons why the AX 2009 AOS will not start the very first time. One reason may be that some maggots have modified system classes like Info, Sys* etc. Fortunately the AOS posts its pain to the windows event log. In the case of modifications on system classes the AOS posts a stack trace to the event log.

    1. Review the event log errors
    2. Identify the poison modification
    3. Remove the modification in the source system
    4. Copy the cleaned layer file
    5. Delete the .aoi file
    6. Start the AOS

Upgrade Issues (Part 1)

I’ve come across some issues upgrading a Dynamics AX 3.0 Installation to Dynamics AX 2009

SQL Server 2008 R2

A German localized SQL Server Installation may fail on a German(Austria) localized system. “SQL Server setup media does not support the language of the OS or does not have ENU localized files…” In that case you need to change the regional settings from German(Austria) to German(Germany)

AX 2009 Installation

Choosing an SQL Server fails because server was not found or access denied. In SQL Server Configuration Manager make sure

  • SQL Server is running
  • TCP/IP Protocol is enabled
  • IP4 feature in TCP/IP is enabeld
  • The port you are using is not blocked by the firewall sqlconfiguration

Logon to Dynamics AX

First logon to Dynamics AX fails “You are not a recognized user …” . Make sure the user credentials in table UserInfo are correct, especially the Active Directory Security Identified (SID)

Execute Batch on Server

Sometimes it’s necessary to execute a batch file on the server. You may use some .NET code to achieve this. First create a new class e.g. called ExecuteBatchOnServer. Add a static method that is executed on the server. Be aware that the file path is local on the server.

public static server void executeBatch(FilePath _filePath)
{   
    #File
    InteropPermission interop;
    FileIOPermission fileIo;
    Set permissionSet = new Set(Types::Class);
    System.Diagnostics.Process process;
    System.Diagnostics.ProcessStartInfo startInfo;
    str filePath = @"C:\Batch\batchfile.bat";
    ;
   
    interop = new InteropPermission(InteropKind::ClrInterop);
    fileIo = new FileIOPermission(filePath,#io_read);
    permissionSet.add(interop);
    permissionSet.add(fileIo);
    CodeAccessPermission::assertMultiple(permissionSet);
   
    startInfo = new System.Diagnostics.ProcessStartInfo();
    startInfo.set_FileName(filePath);
   
    process = new System.Diagnostics.Process();
    process.set_StartInfo(startInfo);
    process.Start();
    process.WaitForExit();

    CodeAccessPermission::revertAssert();
}

Create a main method to make the class executable

public static void main(Args _args)
{
   DialogButton result;
   ;
   result = Box::yesNo("Excute Batch?",DialogButton::No);
   if(result == DialogButton::Yes)
      ExecuteBatchOnServer::executeBatch();
}

Add Exception Handling and Output Redirection

Typically its useful to see the standard output, error and exception text. Therefore its necessary to redirect the Standard* Output and add a try/catch block

public static server void executeBatch()
{
    #File
    InteropPermission interop;
    FileIOPermission fileIo;
    Set permissionSet = new Set(Types::Class);
    System.Diagnostics.Process process;
    System.Diagnostics.ProcessStartInfo startInfo;
    str filePath = @"C:\Batch\batch.bat";
   
    System.IO.StreamReader stdOut;
    System.IO.StreamReader stdErr;
    str txtOut;
    str txtErr;
    str txtExc;
    ;

    interop = new InteropPermission(InteropKind::ClrInterop);
    fileIo = new FileIOPermission(filePath,#io_read);
    permissionSet.add(interop);
    permissionSet.add(fileIo);
    CodeAccessPermission::assertMultiple(permissionSet);

    try
    {
        startInfo = new System.Diagnostics.ProcessStartInfo();
        startInfo.set_FileName(filePath);
        startInfo.set_UseShellExecute(false);
        startInfo.set_RedirectStandardOutput(true);
        startInfo.set_RedirectStandardError(true);

        process = new System.Diagnostics.Process();
        process.set_StartInfo(startInfo);
        process.Start();
        stdOut = process.get_StandardOutput();
        txtOut = stdOut.ReadToEnd();
        stdErr = process.get_StandardError();
        stdErr.ReadToEnd();
        process.WaitForExit();
    }
    catch(Exception::CLRError)
    {
        txtExc = ClrInterop::getLastException().toString();
    }

    if(txtOut)  info(txtOut);
    if(txtErr)  error(txtErr);
    if(txtExc)  error(txtExc);

    CodeAccessPermission::revertAssert();
}

AX 4.0 Send GUID in AIF document

I’ve noticed a a bug in AX 4.0 (K/A: 4.0.2501.116) AIF implementation when using a table field type GUID. Unfortunately the generated XSD schema referred to the field type as Int64 instead of GUID. The sent XML of course contained an GUID String {00000000-1234-5678-9101-000000000000}. My C# .NET client reported an error because the received value was not a valid Int64.
I solved the problem by editing AxdBaseGenerateXSD.addGuid() method
protected void addGuid(
SysDictType dt,
XmlSchemaSimpleTypeRestriction restriction
)
{;
/*  restriction.baseTypeName(
XmlQualifiedName::construct(
this.addInt64Type(),
targetNameSpace)); */
restriction.baseTypeName (XmlQualifiedName::construct(
this.addGuidType(),
targetNameSpace));
}
As result the generated XSD schema contained the correct GUID definition
<xs:simpleType name=”AxdExtType_MyGUID“>
<xs:annotation>
<xs:documentation xml:lang=”DE-AT“>A Guid field</xs:documentation>
</xs:annotation>
<xs:restriction base=”AxdType_GUID” />
</xs:simpleType>

<xs:simpleType name=”AxdType_GUID“>
<xs:restriction base=”xs:string“>
<xs:minLength value=”38” />
<xs:maxLength value=”38” />
<xs:pattern
value=”({[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}})” />
</xs:restriction>
</xs:simpleType>