Friday, December 19, 2014

How to list all of the files in a folder and sub folder via AX

The following examples will show you how to list all of the files in a folder and sub folder.

 static void GetFilesInFoldersAndSubFolders(Args _args)
{
    System.String[] filePaths = System.IO.Directory::GetFiles(@"folder location", "*.*", System.IO.SearchOption::AllDirectories); //get listing of all files within the folder
    int fileCount = filepaths.get_Length(); //get how many files were found
    int currentFileCount;
   
    //go throw each one of the files that were found
    for(currentFileCount = 0; currentFileCount < fileCount ; ++currentFileCount)
    {
        info(filepaths.GetValue(currentFileCount));
    }
}

Send xml to BarTender via TCP sockets and get response back using UTF-8 encoding

This is an example that can be used to send print jobs/print previews (return jpgs/images via raw text) to the BarTender labeling system or using this example as a foundation to talk to your own servers via tcp sockets

 /// <summary>
/// Send print job btxml string to bartender and get a response back
/// </summary>
/// <param name="defaultXML">
/// the BTXML to send to bartender
/// </param>
/// <returns>
///BarTenderStaus if it was successfull or not
/// </returns>
public BarTenderStaus sendPrintJobTCP(str defaultXML)
{
    str                 response;
    XmlTextReader       xmlTextReader;
    boolean             msgFound;
    System.Text.Encoding encoding;
    str                 errorId;
    boolean             errorsFound;
    str                 message;
    SysOperationProgress previewProgress;
    System.Net.Sockets.TcpClient barTenderClient;
    System.Net.Sockets.NetworkStream stream;
    System.IO.StreamReader reader;
    System.Byte[] send_bytes;
    #avifiles
    try
    {
        //setup progress bar so we can let the user know what we are doing
        previewProgress = SysOperationProgress::newGeneral(#AviPublish2Web, 'Sending Print Job To BarTender', 7);

        if(serverSettings.BarTenderServerName == "" || serverSettings.BarTenderServerPortNumber == 0)
        {
            throw error("No valid BarTender server has been defined. Please contact IT.");
        }
        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Reading XML...");

        message = defaultXML;
        //check to see if we have something available to send to the server
        if(message == "")
        {
            throw error("No items are available to print");
        }

        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Connecting to BarTender Server...");
        //make connection to bartender and open stream with UTF8 encoding
        barTenderClient = new System.Net.Sockets.TcpClient(serverSettings.BarTenderServerName, serverSettings.BarTenderServerPortNumber);
        stream = barTenderClient.GetStream();
        reader = new System.IO.StreamReader(stream, System.Text.Encoding::get_UTF8());

        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Sending Label to BarTender...");
        //define the stream encoding as UTF8
        encoding = System.Text.Encoding::get_UTF8();
        //convert the xml to bytes to send to bartender
        send_bytes = encoding.GetBytes(message);
        //send the xml bytes to bartender
        stream.Write(send_bytes, 0, send_bytes.get_Length());
        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Reading Response From BarTender...");
        //get the response bartender sends back
        response = reader.ReadToEnd();
        //close the bartender stream
        stream.Close();
        //update progress bar
        previewProgress.incCount();
        previewProgress.setText("Task: Analyzing XML Response from BarTender...");
        //we need to parse the xml response string into xml nodes
        xmlTextReader = XmlTextReader::newXml(response, true);
        //loop through all of the xml nodes
        while(xmlTextReader.read())
        {
            //check to see what type of xml node we are currently on
            switch (xmlTextReader.NodeType())
                {
                    case XmlNodeType::Element:
                        //check to see if we are on the message node
                        if(xmlTextReader.Name() == "Message")
                        {
                            //read the message attributes
                            while (xmlTextReader.MoveToNextAttribute())
                            {
                                //check to see if the attributes node is severity
                                if(xmlTextReader.Name() == "Severity")
                                {
                                    //check to see if we are looking at an error
                                    if(xmlTextReader.Value() == "Error")
                                    {
                                        //error has been found
                                        msgFound = true;
                                        errorsFound = true;
                                    }
                                }
                                else if(xmlTextReader.Name() == "Id")
                                {
                                    //define what the id attribute is incase it is an error
                                    errorId = xmlTextReader.Value();
                                }
                            }
                        }
                        break;
                    case XmlNodeType::Text:
                        //check to see if the last node we were on was a message and if it was an error
                        if(msgFound)
                        {
                             //display error message to user in a friendly manner
                             setPrefix("BarTender Error " + errorId);
                             error(xmlTextReader.Value());
                             //reset message found flag
                             msgFound = false;
                        }
                        break;
                    case XmlNodeType::EndElement: //Display the end of the element.
                        break;
                    default:
                        break;
                }
        }

        //update progress bar
        previewProgress.setText("Task: Finished");
        previewProgress.setTotal(7);
        previewProgress.hide();
        //check to see if any errors occured
        if(!errorsFound)
        {
            return BarTenderStaus::Success;
        }
        else
        {
            return BarTenderStaus::Failure;
        }
    }
    catch
    {
        error("There was an unknown error communicating with the BarTender server. Please restart your computer and try again. If the issue continues please alert IT of this issue.");
        //update progress bar
        previewProgress.setText("Task: Finished");
        previewProgress.setTotal(7);
        previewProgress.hide();
        return BarTenderStaus::Failure;
    }
}

Thursday, October 23, 2014

C# Accessing AX Session Information & Defining which AOS to connect to via code

So while working on the last post I ran into an issue where I couldn't change which aos the data was being pulled in from so I wrote the following function that gets server information so you can see basic information on what connection is being used. Along with defining which AOS the code pulls from by defining a server within code so I can switch back and forth between DEV & TEST

You can download the C# visual studio project here: download now!


Its good to note that around the web the following "logon" statement is posted

// Logon to Dynamics AX
Session axSession = new Session();
axSession.Logon(null, null, null, null);

However in order to define which AOS server to connect to via code we need to do the following

// Logon to Dynamics AX
Session axSession = new Session();
axSession.Logon(null, null, "AOSServerName", null);




The following code are just some ways to pull in basic server information


private void GetAOSInfo_Click(object sender, EventArgs e)
{
 
try
{
 
// Logon to Dynamics AX
Session axSession = new Session();
axSession.Logon(null, null, axServerName, null);


 
//get server information

string axInfoAOSInstance = axSession.CreateObject("XSession").Call("AOSName").ToString();
string axInfoClientName = axSession.CreateObject("XSession").Call("clientComputerName").ToString();
string axInfoLoginDateTime = axSession.CreateObject("XSession").Call("loginDateTime").ToString();
string axInfoMasterSessionId = axSession.CreateObject("XSession").Call("sessionId").ToString();
string axInfoUserId = axSession.CreateObject("XSession").Call("userId").ToString();



CustomerOutput.Clear();
 
CustomerOutput.AppendText(("AOS Server: " + axInfoAOSInstance));
CustomerOutput.AppendText((Environment.NewLine + "Client Computer Name: " + axInfoClientName));
CustomerOutput.AppendText((Environment.NewLine + "Session Id: " + axInfoMasterSessionId));
CustomerOutput.AppendText((Environment.NewLine + "Login DateTime: " + axInfoLoginDateTime));
CustomerOutput.AppendText((Environment.NewLine + "User Id: " + axInfoUserId));



 
 
//log off ax
axSession.Logoff();

}
 
catch (Exception ex)
{

CustomerOutput.Clear();

CustomerOutput.AppendText(ex.Message.ToString());

}

}
 

Accessing AX CustTable & DirPatyTable (Customer Info) via C# via Linq.

In order to support legacy applications sometimes you need to pull in data from AX into standalone applications. Below will show you how to pull in customer id's + names (CustTable + DirPartyTable)
The following examples will show you how to manually loop through that data or tie it to a combo box via Linq.

You can download the C# Visual Studio Project Here: download now!

Step 1. Start new project within visual studio that has access to an aos via the application explorer
Step 2. Adding the following references to the visual studio project

C:\Program Files (x86)\Microsoft Dynamics AX\6.0\Client\Bin\

Microsoft.Dynamics.AX.Framework.Linq.Data.dll
Microsoft.Dynamics.AX.Framework.Linq.Data.Interface.dll
Microsoft.Dynamics.AX.Framework.Linq.Data.ManagedInteropLayer.dll
Microsoft.Dynamics.AX.ManagedInterop.dll


Then on your form/class add the following using classes

using Microsoft.Dynamics.AX.ManagedInterop;
using Microsoft.Dynamics.AX.Framework.Linq.Data;
using System.Linq;

Step 3. Right click on project and add to AOT
Step 4. After step 3 you can start to add objects from the application explorer into the project. Add this time we should add the table CustTable & DirPartyTable

The following code block will show you how to connect to ax, query the tables and loop through the data or tie it to multiple combo boxes. Its good to note that my name space for this project was Win2AX.

       


 
 


public void loadCustomers(Boolean listInTextBox)
{
           

try
{
               

isCustomersLoaded = false;

// Logon to Dynamics AX (usering windows logon info)
Session axSession = new Session();
axSession.Logon(null, null, axServerName, null);

// Create a query provider needed by the Linq Provider
QueryProvider provider = new AXQueryProvider(null);

//Connect to the table proxy's for the CustTable and DirPartTable tables within AX.
QueryCollection<Win2AX.CustTable> customerCollection = new QueryCollection<Win2AX.CustTable>(provider);

QueryCollection<Win2AX.DirPartyTable> partyCollection = new QueryCollection<Win2AX.DirPartyTable>(provider);



 
//select the account num and dirparty name so we can provide an account num + name

//its good to note that when I didnt include a field list the app crashed. I think linq is limited to the amount of data that can be processed

var allCustomers = (from customer in customerCollection
join partyDesc in partyCollection on customer.Party equals partyDesc.RecId
orderby customer.AccountNum ascending
select new { customer.AccountNum, partyDesc.Name, Description = customer.AccountNum + " - " + partyDesc.Name }).ToList();

allCustomers.Insert(0, new { AccountNum = "<Select Customer>", Name = "<Select Customer>", Description = "<Select Customer>" });

if (listInTextBox)
{

CustomerOutput.Clear();
//go through all of the records

foreach (var custTable in allCustomers)
{
//output the current record
CustomerOutput.AppendText(Environment.NewLine + "Customer Account: " + custTable.AccountNum + " Description: " + custTable.Name);
}

}
else if (!listInTextBox)
{
//bind all combo boxes to our linq list

//combination (Customer Id - Name)
CustomerList.DataSource = allCustomers;
CustomerList.DisplayMember = "Description";
CustomerList.ValueMember = "AccountNum";

//Customer Id only
CustomerIdSelection.DataSource = allCustomers;
CustomerIdSelection.DisplayMember = "AccountNum";
CustomerIdSelection.ValueMember = "AccountNum";

//Name Selection / Account Num Value
CustomerNameSelection.DataSource = allCustomers;
CustomerNameSelection.DisplayMember = "Name";
CustomerNameSelection.ValueMember = "AccountNum";

//signal that all combo boxes are loaded so we can handle comboboxes selected index changes correctly

isCustomersLoaded = true;
}

 
//log off ax
axSession.Logoff();

}
catch (Exception ex)
{

CustomerOutput.Clear();
CustomerOutput.AppendText(ex.Message.ToString());

}

Friday, October 10, 2014

Failed to create a session; confirm that the user has the proper privileges to log on to Microsoft Dynamics

Currently we have a developer working/coding in an active environment where users are currently using the system (non-prod) and had an issue generating good incremental CIL. During this time some of the users started to see the following message

"Failed to create a session; confirm that the user has the proper privileges to log on to Microsoft Dynamics"

In order to resolve this issue try the following

File/Tools/Options/Development/ uncheck the option for "Execute business operations in CIL"

This ended up fixing our issue, but after reading multiple other blogs about AX if this doesn't work then the next step is to run a full CIL. If that doesn't work you should restart the AOS and run the full CIL again.

Wednesday, September 24, 2014

Custom barcode and MICR fonts on SSRS reports from AX 2012 display correctly but will not print or export

So with AX 2012 you may want to display a custom, barcode, or micr font on a report to print for the bank or factory floor. By default a lot of these fonts get installed but some do not. What I have found out is that it will display correctly within the report window but whenever you try to print it or export it does not export the font correctly. In order to resolve the issue the following needs to be done

  1. Install font on AOS
    1. If you are running an AX Cluster be sure to install it on all AOS's
  2. Install font on SQL/SSRS box
    1. If you are running a SQL cluster be sure to install it on all AOS's
  3. When installing be sure to install the font thru the control panel>fonts and not C:\windows\fonts for some odd reason windows server 2012 R1+ does not always install the font correctly when you copy to the specific folder but it does if you use the control panel
  4. Reboot SQL boxes
  5. Reboot AX AOS boxes


If this does not work try reinstalling the font thru the control panel.

Thursday, September 11, 2014

Firming Production Orders Crashes AOS: Object Server 01: An error has occurred in the services framework. Method: AifMessageInspector::AfterReceiveRequest. Error: System.ServiceModel.FaultException: Failed to logon to Microsoft Dynamics AX.

​The issue: When firming a production order sometimes it may cause for all the AOS's in a cluster to crash and restart.

The setup: 2 AOS cluster with 1 Batch AOS (3 AOS's in total)

The research: Because firming the PO sometimes would not crash the AOS's I looked into the windows log whenever it did and found the following error:

Object Server 01: An error has occurred in the services framework. Method: AifMessageInspector::AfterReceiveRequest. Error: System.ServiceModel.FaultException: Failed to logon to Microsoft Dynamics AX.
at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.InitializeSession()

at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.InitializeContext()

at Microsoft.Dynamics.Ax.Services.AxServiceOperationContext.Attach(OperationContext owner)

at System.ServiceModel.ExtensionCollection`1.InsertItem(Int32 index, IExtension`1 item)

at System.Collections.Generic.SynchronizedCollection`1.Add(T item)


at Microsoft.Dynamics.Ax.Services.AifMessageInspector.AfterReceiveRequest(Message& request, IClientChannel channel, InstanceContext instanceContext)

 

 

Because the begining of the message contains "Failed to logon to Microsoft Dynamics" you know it is something dealing with creditials. Currently we have an account for the service called domain\AX_AOS_Service and an account for the .NET Business Connector called domain\AX_BusCon so I knew that it had to deal with one of these.

 

 

Action/Solution (Here is what I did to fix the issue):

  1. Make sure an SSRS default config instance is available for every aos. Including the batch server
  2. Add the AOS service account to the users in AX. This account should be added to the groups "System User" and "System Administrator" (leave employee off)
  3. Changed the report servers within AX from looking at a single sql server to the sql cluster  (individual server vs SQL cluster)
  4. Changed the "Batch Group" (under system administration > setup > batch group) so that only batch servers were listed in any of these and the empty batch group was changed to have no servers. Before it contained non batch servers listed as batch servers
  5. Restart all 3 AOS's (Microsoft service/via services.msc)
    1. After restarting the AOS  it would still crash but not as often. I would also now start get the following message instead of the original one
      1. The description for Event ID 180 from source Microsoft Dynamics AX cannot be found. Either the component that raises this event is not installed on your local computer or the installation is corrupted. You can install or repair the component on the local computer. If the event originated on another computer, the display information had to be saved with the event. The following information was included with the event: Microsoft Dynamics AX Business Connector Session 9.
        RPC exception 1722 in Ping occurred in session 19 ,process: Ax32.exe ,thread: 8456
    2. This message tells us that there is still an issue but no longer a specific one but generic. These type of messages are usually fixed by a system reboot.
  6. Restart all 3 AOS's boxes and not just the service (systems/windows)


Update:

Issue continues to be random even after all of this. After looking at a KB article from Microsoft we have also added the AX AOS Service & Bus Con accounts to the Active Directory groups

Windows Authorization Access group & Pre-Windows 2000 Compatibility Access group (within active directory)

But we still seem to have to have the same issue. I will keep this article updated with anything else we try.




Update 3/11/15: The issue continues and we started to work with Microsoft and they were able to track down the class & table that was causing the issue. It appears there is a KB fix for this specific issue.

Issue Location: \tables\ReqTrans & \class\ReqTransPoMarkFirm

KB2936744
 
PROBLEM
Update on ReqTransPoMarkFirm crashing Application Object Server (AOS).
DESCRIPTION OF CHANGE
The changes in the hotfix resolve this issue.
The table method ReqTrans.reqTransDirectlyDerived() is using a code pattern that’s not supported. As hightlighted below, the method is returning a table variable that’s in the JOIN clause and holds a where condition that references a local table variable (i.e. reqTransCov). This leads to invalid cursor when ::update is called upon the returned table variable.
Changing the X++ select in ReqTrans.reqTransDirectlyDerived() so that reqTrans is the main and reqTransCov is in the JOIN clause. Since ReqTransCov is never used, put it in exists join.



You may also need KB2629981 - Orders can be firmed more than once, which leads to duplicate planned orders.




With the issue of AifMessageInspector::AfterReceiveRequest. Error: System.ServiceModel.FaultException: Failed to logon to Microsoft Dynamics AX.

 If you turn off debugging on the specific aos it will no longer appear.
 

Friday, August 8, 2014

Opening specific form in AX from external application or windows shortcut

When working with the last ERP system I used (Infor Visual ERP) we always had the option to create a pretty generic .txt file and convert it to a shortcut so it would open the application, open a specific form and jump to a specific record.

So far in AX I have figured out how to open AX from an external source and jump to a specific form but I have not figured out how to make it jump to a specific record. Maybe that will be in a later post.

But thanks to http://msdn.microsoft.com/en-us/library/jj677292.aspx I can show you an provide examples on how to open a specific form in AX


Opening AX via a shortcut:

Download the following zip and inside you will find a .vbs file. Simply make a shortcut with the following properties
https://drive.google.com/file/d/0B7mPpWNLDR1Xd1ZxUlRHWHV1OHc/edit?usp=sharing
<location of vbs file>.vbs  -c <company> -d "<menu item name>"
Example : C:\openAX.vbs -c TEST -d "InventQualityOrderTable"


Opening AX via a C# application:

https://drive.google.com/file/d/0B7mPpWNLDR1XRW1xVEpKTGN5MGs/edit?usp=sharing
The above link provide a basic application that will show you how to open AX the only requirement is that you will need to add a reference to the COM object Dynamics AX Client 1.0 Type Library

Then add

using AxClientLib;
  private void button1_Click(object sender, EventArgs e)
        {
            AxClientLib.DynamicsAxApplication axComClient = new AxClientLib.DynamicsAxApplication();
            try
            {
                //find running Ax32.exe
                axComClient.OpenMenuItem("FWM", "BatchTraceStandAloneUIMenuItem", AxClientLib.AxMenuType.DisplayMenu);
            }
            catch
            {
                MessageBox.Show("failed");
            }
        }