Monday, August 24, 2009

Writing a quine

While reading through Wikipedia, I came across something called a quine, and here is its definition:

In computing, a quine is a computer program which produces a copy of its own source code as its only output.

I was inspired by this and decided to make a quine in X++, the programing language of Microsoft Dynamics AX.

static void quine(Args _args)
{
#AOT;
print TreeNode::findNode(strfmt(#JobPath,"quine")).AOTgetSource();
pause;
}
It’s a job that finds it’s own tree node in the AOT, gets the source of that node and prints it. I originally wrote it all on one line, because I like the look of that better, but I didn’t do that here for readability.

Now I’m not entirely sure if this qualifies as a quine, because a quine can have no input, and using the method AOTgetSource could be seen as cheating. But anyway, it reproduces itself, it’s short, and it made me happy

AOS crashed when importing XPO

Dynamics AX client crashed when clinking on show details. No message was shown, the application just quit. It was also impossible to reconnect to the Application Object Server because the AOS had crashed as well. When you restarted the AOS, you could easily reproduce this scenario by repeating the above steps.

Sometimes, the following message was logged in the event log on the client, basically informing you that the AX client had crashed:

Faulting application ax32.exe, version 5.0.1000.52, stamp 490c41fb, faulting module unknown, version 0.0.0.0, stamp 00000000, debug? 0, fault address 0×0bbf0b3c.

In the event log on the server an other message was logged, but not every time the AOS crashed:

Object Server 16: RPC error: Client provided an invalid session ID 3

From what I understand, this message is logged when a client sends an RPC request to a server that has ended the session for this client, which would be the case if the AOS crashed.

Once again, the event log is pretty useless. The real problem lies in the application files, more specifically the Microsoft Dynamics AX Label Index files. These are the files that have the extension .ali. For some reason, one or more of these index files are corrupt, and this causes the import process to fail, and the AOS to crash. At the time, the AOS server was low on diskspace, and that could be the cause of the corrupt index files. Luckily, index files such as ali files can be deleted when the AOS is down, and will be rebuilt when the AOS is started.
After all label index files were deleted, and the AOS restarted, the xpo could be imported, and the AOS didn’t crash anymore.

So, when the AOS crashed when you are trying to import an xpo file, stopping the AOS, deleting the ali files from the application directory and restarting the AOS might be the solution

Change security permissions for admin group

In Dynamics AX, it is not possible to change the user group permissions for admin group. This is because users in this group should always have full control over the Dynamics AX setup.

But sometime, you’ll have to change it, whatever you reason may be. One reason why you might need to change these, is because the user group permissions setup has been messed up by adding one or more new security keys, or by importing them from an xpo file. The security keys will be set to ‘No access’, and because you can’t change these for the admin group, this poses a problem.

This can easily be solved by changing the method isAdmin() on the form SysUserGroupSecurity to return false.

#admin
boolean isAdmin()
{
/*if (userGroupInfo.Id == #AdminUserGroup &&
(domainInfo.Id == #AdminDomain || !useDomains))
{
return true;
}*/
return false;
}

Gender information in Employees-form and the Global Address Book (GAB)

You can open the information about employees with the Global Address Book (Basic/Global Address Book) and with the Employees form (Basic/Employees).
Normally it is no problem to modify data in both forms, but unfortunately this is not true for the ‘gender’ property. This information is stored in the DirPersonPartyDetail (Gender) and EmplTable (EmplGender) table. The particularity here is, that the EmplGender property is of type ‘EmplGender’ which has two states (0:Male;1:Female) and the enum ‘Gender’ has three states (0:unknown;1:male;2:female).
If you know change the value for the gender in the GAB it won’t change the value for ‘EmplGender’ and if you change it through the Employees-form it won’t change the value for ‘Gender’.
This is why the gender information of employees must be modified with the Employees-form and not with the GAB.
If you need to synchronize the employees information with the GAB, you can add at the end of the method (tables\EmplTable\ValidateField), the following code :
1: //MODIFICATION(04/08/2009): Empl-form is not synchronizing some values with the GAB
2: case(fieldnum(EmplTable, emplGender)):
3: dirPersonPartyDetail = DirPersonPartyDetail::find(this.PartyId, true);
4: if(dirPersonPartyDetail)
5: {
6: dirPersonPartyDetail.Gender = enum2int(this.EmplGender);
7: dirPersonPartyDetail.update();
8: }
9: break;
10: //END MODIFICATION

Template Wizard does create SheetNames with more than 30 characters

The Template Wizard (Administration/Periodic/Data export/import/Excel spreadsheets/Template Wizard) creates unfortunately SheetNames that might exceed 30 characters, which is not allowed by Excel and results in annoying message boxes that are requesting a new SheetName with less than 31 characters.

The following code change in the method SysDataExcelDef/buildTmpExcelWorksheet will resolve this problem:

1: //modifications done for working with long tablenames
2: // str tempSheetName;
3: // str suffixName;
4: // int suffixlength;
5: //original code:
6: //tmpExcelWorksheet.SheetName = tmpExcelWorksheet.TableName + '_' + int2str(sysExpImpTable.OccurrenceId) + '-1';
7:
8: tempSheetName = tmpExcelWorksheet.TableName + '_' + int2str(sysExpImpTable.OccurrenceId) + '-1';
9:
10: if (strlen(tempSheetName) > 30)
11: {
12: tempSheetName = tmpExcelWorksheet.TableName;
13: suffixName = '_' + int2str(sysExpImpTable.OccurrenceId) + '-1'; //as this is done in the original code
14: suffixLength = strlen(suffixName);
15: tempSheetName = substr(tempSheetName, 0, 30 - suffixLength);
16: //assigning the new name
17: tmpExcelWorksheet.SheetName = tempSheetName + suffixName;
18: }
19: else
20: {
21: tmpExcelWorksheet.SheetName = tempSheetName;
22: }

Monday, August 17, 2009

Manage Cues on Role Center pages for Web parts

Cues Web parts display a visual representation of a user's workload and remaining work items, such as sales leads or overdue activities. You can configure or edit Cues, and then import or export them to other computers.

For information about how to add a Cues Web part, modify the properties of a Cue, create Cues, or modify Cue filters, see the Applications and Business Processes help, available from the Microsoft Dynamics AX Help menu.

Edit a Cue
You can configure several settings for a Cue, including the minimum and maximum values, the query used for the Cue, and which user profiles a Cue is available for.

Click Basic > Setup > Role Center > Edit Cues.

Select a Cue ID.

Click the General tab.

Enter a caption to be displayed for the Cue in the Web part.

Enter the minimum and maximum count information, which determines how the icon that is displayed for each Cue represents the number of records.

Specify the threshold. If the threshold is not met, a warning icon will be displayed for the Cue.

Select the totals to display for Cues that display currency information.

Select which users will have access to this Cue. If you select Specified Profiles, select which profiles will have access to the filtered view. This setting applies across all companies.

Click OK.

How to import or export Cues
If you edit Cues and you want to make those Cues available on other computers, you can import or export those Cues.

Click Basic > Setup > Role Center > Manage Cues.

Select the Cue IDs to import or export.

Click Import or Export and click the AOT or file option.

If you are importing or exporting Cues on the same computer, select the AOT option.

If you are importing or exporting Cues on another computer, select the file option.

Close the form to save your changes.

Tuesday, August 11, 2009

Sending attachments using the "mailto:" protocol

It works with Outlook at least. I think this example job is enough

static void Test_MailTo(Args _args){ str recepient='mbelugin@gmail.com'; str subject = 'Test'; str attachment = @'c:\AUTOEXEC.BAT'; str url = strFmt( 'mailto:%1?Subject=%2&attachments=""%3""', recepient, subject, attachment );; WinApi::shellExecute(url);}

Sunday, August 9, 2009

Problem solving in AX

Sometimes, when developing, AX doesn’t work as expected, or behaves weird.
Here are some of the things you can try if you run out of ideas, roughly in the order I do:

Try again
You probably already did, but make sure you can reproduce the problem. If it only occurred once, it’s not a problem.

Check your code again
Check your code carefully for errors, and maybe ask a colleague’s opinion.

Compile
Your project might contain compile errors, so compile it to be sure.

Close the debugger
Sometimes, when the debugger is active, AX will keep executing ‘old’ code. Close the debugger to make sure the latest code is executing.

Compile forward
When you have modified a class that is inherited by other classes, you might want to compile forward this class.

Synchronize data dictionary
You may have made changes to application objects that haven’t been synchronized with the database. Open the AOT, right click on the Data Dictionary node and choose Synchronize.

Restart AX client
Simple as that, close the AX client and start it again.

Reset usage data
Go to options screen (AX button > Extra > Options) and click the Usage Data button. Click reset to remove this data.

Check the application event log for clues
Open the event viewer on the AOS to see if the AOS service has logged something in it. Messages here can help you a great deal. You can also check the event log on the database server or your client pc.

Google it
If you receive an error, just copy and paste it in Google. Most likely you are not the only one having that problem.

Check your client version
Check your AX client version. You might for example be connecting to a SP1 application with an SP0 client. You can check this in the about screen: AX button > Help > About. The kernel version indicates the client version, below that is the application version.

Refresh AOD, Dictionary and Data
You can flush cashed information using three option in the Tools > Development tools menu: refresh AOD, refresh Dictionary and refresh Data. This can be useful when you just imported an xpo file, or when you are developing for the enterprise portal.

Delete AUC file
The application Unicode object cache file, if there is one, is located at C:\Documents and Settings\[USERNAME]\Local Settings\Application Data for xp, or C:\Users\USERNAME\AppData\Local for vista. Delete this file while the AX client is closed.

Check if other users are having the same problem
Knowing whether you are the only one that’s having that problem or if it’s a general problem is a big step towards solving the problem. For example, if you alone have the problem, restarting the AOS probably won’t solve it, but removing usage data might.

Check security settings
When only some users have a problem, big changes are that it has something to do with security settings. Security can be set up from Administration > Setup > Security, on the Permissions tab.

Check configuration key setup
Some features of AX won’t work if a configuration key is disabled, be aware of this.

Full compile
Open the AOT, right click the AOT node and select compile.

Restart AOS
Sometimes restarting the AOS solves your problem just fine. If you can, it’s worth the try as this doesn’t take long.

Remove .aoi file
When the AOS is stopped, you can delete the .aoi file from the application files. This file will be rebuilt when the AOS starts.

Add leading zeros

You want to show a integer with a fixed length (for example 2characters: 1 –> 01). You don’t have to write your own for-loop to ad the zero’s in front or something like that. The function strRFix does this for you automaticly.

info(strRFix(int2str(1), 2, '0'));

Class RunBaseReport

When you create a class that extends from RunBaseReport and you try to modify the printer preferences it won’t have any effect. This small bug can be fixed very easy.

Edit the class RunBaseReportDialog, method main and check lines 14 and 15:
static void main(Args args)
{
RunBaseReportDialog reportDialog = new RunBaseReportDialog(args.caller());
RunBaseReport runBaseReport = args.caller().runbase();
ReportRun reportRun = runBaseReport.reportRun();
Report report = reportRun.report();
boolean oldInteractive;
boolean res;
Dialog dialog;
;
// We must invoke the SysPrintForm via the report object so that we honor an prompt overrides.
oldInteractive = report.interactive();
report.interactive(true);
//res = reportRun.prompt(); // COMMENTED
res = reportDialog.prompt(); // CHANGE OF CODE
report.interactive(oldInteractive);
if (!res)
return;
dialog = Dialog::getDialogFromCaller(args.caller());
if (dialog)
{
dialog.updateServer();
}
runBaseReport.dialogUpdatePrinterSettings(dialog);
reportDialog.run();
}

Hide/destroy annoying Content Pane in AX2009

Here is a small job, which hides content pane in Dynamics Ax 2009. This content pane can be useful for users, but as a developer it can be very annoying

static void hideAnnoyingContentPane(Args _args)
{
HWND contentPane = WinApi::findWindowEx(
WinAPI::findWindowEx(infolog.hWnd(), 0, 'MDIClient', ''),
0,
'ContentFrame',
''
);
;
if (contentPane)
WinApi::destroyWindow(contentPane);
}

Type of Anytpe enum

Normaly you try to avoid using AnyType variables, but sometimes you are forced to use them. When you use a AnyType enum and you want to cast it to it’s actual data type, be sure you cast it to the right type to prevent stack-traces. You can get the enumId from a AnyType-enum by using the DictEnum::value2id(AnyType _value) -function. Then you can compare it with the enumnum(EnumType) to check if it is the right type.

In code this check could look like this:

static void testAnyTypeEnumCasting(Args _args)
{
DocumentStatus docStatus;
Anytype any = DocumentStatus::Invoice;
;
switch (DictEnum::value2id(any))
{
case enumnum(DocumentStatus) :
docStatus = any;
break;
case enumnum(...) :
...
}
}

Select Table/Field/User/… (PickList)

Sometimes you want your user to select a sertain table/field/… When you want to program this selection all by yourself you have to make a table, override the lookup()-method, …

There is also a easy way to do this. You can use the pic**** methodes in the Global class:
pickclasses
pickdatamethord...etc

How do you use these classes (you can find this example in \Forms\DocuOptionTable\Designs\Design\[Tab:TableTab]\[TabPage:Overview]\[Grid:TableOverviewGrid]\StringEdit:DocuTableName\Methods\lookup):
void lookup()
{
tableId id;
;
id = pickTable();
if (! id)
return;
docuTable.DocuTableId = id;
docuTable_ds.refresh();
}

The result:
Table : Field:
When you want to create you own customized pick-dialog you can by entering the following code:

static void testPickList(Args _args)
{
Object formRun;
container con;
;
formRun = classfactory.createPicklist();
formRun.init();

//fill container con

formRun.choices(con); // you can add a secundary parameter wth the imiges
formRun.caption("TEST"); // Make label
formRun.run();
formRun.wait();
if (formRun.choice())
{
print formRun.choice();
}
pause;
}

Security key disabled, even for the Admin

When you import a new security key trough a XPO, this new key will be disabled even for the admin group. Since you can’t adjust the security setting for the admin group you can’t use the keys functionality.

There is a verry simple solution for this problem. Open the method \Forms\SysUserGroupSecurity\Methods\isAdmin and force this method to return false.

#admin
boolean isAdmin()
{
/* if (userGroupInfo.Id == #AdminUserGroup &&
(domainInfo.Id == #AdminDomain || !useDomains))
{
return true;
}*/
return false;
}

Customizing EditorScripts

Ax had a few interesting scripts for the developer. When you have an idea for a new useful script you can extend the the EditorScripts Class (\Classes\EditorScripts) with your new class.

Here is a example where you automate some custom comment while developing:


void comments_MyComment(Editor e)
{
e.unmark();
e.gotoLine(1);
e.gotoCol(1);
e.insertLines(strfmt('//--> ProjectRef - By:%1 @ %2\n', curuserid(), systemdateget()));
}

How to identify the user that was used to change an object from AOT in AX2012

Get the object name for which we need to track these (user and date&time) information's. Login to SQL Server Management Studio an...