Tuesday, June 10, 2014

WinAPI::ShellExecute() for a JAR file for Banking application

Recently came across a scenario where a banking a hash program needs to be called to encrypt a payment file before sending to the Bank. The bank had just changed the hashing program from an .exe file to a .cmd, this file inturn was a batch script file which would validate and then call a JAR file with the appropriate parameters.
Issue:- While executing the .cmd file from the Command prompt the JAR file runs and hashes the file correctly, but when we use the Dynamics AX to call the .cmd file the JAR file is not executed. The command used was WINAPI::ShellExecute(Command, Parameters);

Investigation :- After preliminary checking from both the AX and command prompt we guessed that the .cmd file was not being executed due to a failure in the calling mechanism. We tried different approaches by calling the cmd.exe and then pass the parameters to mimick the manual approach. But somehow the command prompt opened up but did not execute the JAR file. Then next approach was to add all the commands inside a batch script file which would call the JAR file, so from AX we wouldn't need to specify the details in AX but would just call the batch script file. Still the execution of JAR file from AX was not happening even though we could execute the file manually.
After a lots of Google searches on the execution environment with ample trial and error my colleague identified that the issue was due to JRE execution is the root cause. Meaning that Dynamics AX looks for the Java Runtime under the 32 bit folder(under C:\Program Files (x86)\Java\bin) instead of the 64 bit installed in Windows server 2012 machine. This could be because of the legacy run time approach followed since the older versions executing the JAR files.

Conclusion:-
Install the 32 bit Java runtime on the server.

Notes:
AX job would look something like this to call the WINAPI::ShellExecute(command); where the command = @D:\TestScript.bat

And test script  batch file will have,
CD D:\HashFile\
D:\HashFile\HashProgram.cmd parm1 parm2 parm3 parmx....
Another thing to note is to link the batch file to the folder using CD D:\HashFile\ before executing the .cmd file. To point to the correct execution folder before running the command.


  Execute exe as a process from X++  X++ run a process with user credentials

How to read in Active Directory

Dynamics Ax relies on Active Directory for user authentication. And thanx to CLR Interop, you too can use Active Directory and all it's objects and properties from within Ax. You can use AD for what it is designed for: a central storage location for application data.


But how does one get to read information from the AD? In following code snippet, I'll show you how to collect a list of all users from a specific domain, with some basic information about those users.
For this, we'll use the System.DirectoryServices namespace, an easy way of getting access to Active Directory from managed code.

In order for your code to work, don't forget to edit the networkDomain variable!

static void ReadFromAD(Args _args)
{
System.DirectoryServices.DirectorySearcher DirectorySearcher;
System.DirectoryServices.SearchScope SearchScope;
System.DirectoryServices.DirectoryEntry DirectoryEntry;
 
System.DirectoryServices.SearchResultCollection SearchResultCollection;
System.DirectoryServices.SearchResult SearchResult;
 
System.DirectoryServices.PropertyCollection PropertyCollection;
System.DirectoryServices.PropertyValueCollection PropertyValueCollection;
 
str networkDomain="yourdomainhere.com";
str prefix = 'LDAP://';
 
int totalCount;
int counter;
 
str mysamaccountname;
str myusername;
;
 
try
{
DirectoryEntry = new System.DirectoryServices.DirectoryEntry(prefix + networkDomain);
SearchScope = CLRInterop::parseClrEnum('System.DirectoryServices.SearchScope', 'Subtree');
 
DirectorySearcher = new System.DirectoryServices.DirectorySearcher(DirectoryEntry);
DirectorySearcher.set_SearchScope(searchScope);
DirectorySearcher.set_Filter(strfmt('(&(objectClass=user))'));
 
SearchResultCollection = DirectorySearcher.FindAll();
 
totalCount = SearchResultCollection.get_Count();
for (counter=0; counter < totalcount; counter++)
{
SearchResult = SearchResultCollection.get_Item(counter);
DirectoryEntry = SearchResult.GetDirectoryEntry();
 
if (DirectoryEntry)
{
PropertyCollection = DirectoryEntry.get_Properties();
 
if (PropertyCollection)
{
PropertyValueCollection = PropertyCollection.get_Item('samaccountname');
mysamaccountname=PropertyValueCollection.get_Value();
 
PropertyValueCollection = PropertyCollection.get_Item('name');
myusername=PropertyValueCollection.get_Value();
 
info(strfmt('%1 - %2',mysamaccountname,myusername));
}
}
}
 
DirectorySearcher.Dispose();
SearchResultCollection.Dispose();
}
catch (Exception::CLRError)
{
error("Error reading AD");
return;
}
 
}
 
 
 
Ax has it's own routines readily available to do the job as well.  Let's look at the class xAxaptaUserManager and xAxaptaUserDetails.

Some examples:

How to get the SID from AD for a domain user:



static void GetSID(Args _args)
{   xAxaptaUserManager xUsrMgr = new xAxaptaUserManager();
   ;
   info(xUsrMgr.getUserSid('youruserid','youraddomain'));
}

Note: Remember you can user the field networkalias from table UserInfo to do a conversion from Ax user id to the domain user id.

You can use this class for various purposes. Also for checking a password.

How to validate the system password in AD from within Ax



static void CheckPassword(Args _args)
{  
    xAxaptaUserManager  xUsrMgr = new xAxaptaUserManager();
    ;
    if(xUsrMgr.validatePassword('youruserid','youraddomain','yourpassword'))
        info('Password correct');
    else
        error('Password incorrect');
}

We could get some more information from the AD regarding the user by using class xAxaptaUserDetails.

How to get the user name from AD for a user



static void GetADUserName(Args _args)
{  
    xAxaptaUserManager  xUsrMgr = new xAxaptaUserManager();
    xAxaptaUserDetails  xUsrDet;
    ;
    xUsrDet = xUsrMgr.getDomainUser('youraddomain','youruserid');
     
    if(xUsrDet)
        info(xUsrDet.getUserName(0));
}

How to get the email address from AD for a user



static void GetADEmailAddress(Args _args)
{  
    xAxaptaUserManager  xUsrMgr = new xAxaptaUserManager();
    xAxaptaUserDetails  xUsrDet;
    ;
    xUsrDet = xUsrMgr.getDomainUser('youraddomain','youruserid');
     
    if(xUsrDet)
        info(xUsrDet.getUserMail(0));
}
 

What are the tables InventSumDateTable and InventSumDateTrans used for?


When browsing the AOT, you may come across these two tables: InventSumDateTable and InventSumDateTrans.

Using your table browser, you see that they are empty.  So what are they used for?

Both tables are used by the report Physical inventory by inventory dimension, which can be found under Inventory Management - Reports - Status - Physical inventory
This report takes you back in time, calculating the stock on hand of your items for any given date in the past. 
You might experience some performance isssues when running this report, as some calculations are needed.

The report uses both tables mentioned earlier, to store these calculations.  After generating the report, a cleanup is performed.  So if you should stumble on some left over data in these tables (the user wasn't patient enough and aborted), you can safely delete any data left in these tables

Column base report in axapta 2009

A report with a true column setup. So first column 1 is filled, top to bottom, then column 2. And then over to the next page.

So like this:

And not like this:

For this, we need 2 some lesser used report methods, gotoYmm100 and mm100Left. More about these later.

Like earlier, we use an inventory report, with InventTable as our datasource, for demonstration purposes.

Create your basic report settings (add the datasource and setup some ranges, create a report design and add some fields in the body section). Now for the extras we need:

public class ReportRun extends ObjectRun
{
int curcolumn;

int mytopofpage;
int mybottomofpage;
}


We need a variable that holds the current column position.
And 2 variables, indicating the top and bottom of the page.

For the init method of our report:

public void init()
{ ;
super();

mytopofpage=423;
mybottomofpage=1000;

curcolumn=2;
element.ChangeColumn();
}


We need a method to change columns, switching between column 1 and 2.
We call this method in the init method of the report, to make sure we start printing on each page at the same, correct top position.

void ChangeColumn()
{
if(curcolumn==1)
{
curcolumn=2;
element.gotoYmm100(mytopofpage);
InventTable_Body.leftMargin(100,Units::mm);
return;
} else if (curcolumn==2)
{
curcolumn=1;
element.newPage();
element.gotoYmm100(mytopofpage);
InventTable_Body.leftMargin(20,Units::mm);
return;
}
}

In the switching columns method, we shift from one column to the other.
In case we go to column 2, we reset our cursor print position back to the top of the page. For this, we use the gotoYmm100 method. This method allows us to set the vertical position where the next report section is printed.
Then we change the left margin of our section, in order for it to appear on the right side of the page.
When we go to column 1, we set the newPage command in order to start a new page in our report. After that, we set the print position with gotoYmm100 as well, to make sure we start on the same level for both column 1 and 2. Also the left margin is reset to the left.

One thing left, the body of our report, with the executesection.

public void executeSection()
{ ;
if(element.mm100Left() < mybottomofpage) element.ChangeColumn(); super(); }


In this part, we check where we are now with our cursor on the page. If we are near the end, we need to switch columns. We use the mm100Left method, but there is also currentYmm100.

Our result will look like this:





So item numbers counting up in column one, then column two.

Now if you look at your print preview in Ax, the output may look garbled. And if you use the built-in PDF generator, dito. But if you print the report to printer or use a third party PDF generator, all looks OK.

Source