Skip to main content

Posts

Showing posts from September, 2012

Send message to online user in Dynamics AX 4.0 (quick & dirty)

I am missing the functionality to send messages to online users that existed in Axapta 3.0 and is now gone. Well, I thought you could use the "Alert" functionality introduced in DAX 4.0. What to do: 1. make sure the user options are configured correctly (that means: set "Time poll interval" to 1 minute, set "Show popup" to "For all event rules") 2. make a button in the online user form that will open a dialog where you could enter your text 3. send the message to all users selected in the online user form datasource Here is a little job that creates an alert for a user. Remember this is a quick & dirty solution, so there are some drawbacks: * you will get an error in the alert form on the second tab * the message will only be displayed about 15 sec and disappears afterwards (you can check FRM EventAttentionGrabber to change that. Check methods fadeIn and fadeOut for variable opacity. This should give you a hint on how to display the f

Be careful when resetting your usage data

in Dynamics AX 4.0, a "Favorites" menu was introduced. You can drag a form to your favorites pane. Then you will be asked to give it a name, and: you can add a saved query to it. With that, you can easily make favorites the open the forms filtered on the data you just need. But there's another thing: as long as your system is still in change, everybody recommends that you delete your usage data from time to time (in User/Options). But what happens to your favorites if you delete all your usage data? The answer is: you'll get an error message. The system will tell you that it cannot find your saved query anymore. So, the whole work saving your queries and making your favorites is lost. A small little addition to the method " reset " in the form " SysLastValue " will help you avoid that. Add the line && _sysLastValue.recordType != UtilElementType::UserSetupQuery; in the "delete_from" statement. So, the deletion will not delete your

Activating query tracing for all users

Sometimes when you are tracking performance (or other) problems, you may want to activate the long running query tracing for all users. Here is a little job that will do that. Beware: you have to activate trace settings on your AOS server, unless most of the queries will not be caught. static void Set_SQLTrace_AllUsers(Args _args) { #LOCALMACRO.FLAG_SQLTrace (1 << 8) #ENDMACRO #LOCALMACRO.FLAG_TraceInfoQueryTable (1 << 11) #ENDMACRO boolean set; UserInfo userInfo; ; set = true; ttsbegin; while select forupdate userinfo { userinfo.querytimeLimit = 1000; if (set) { userInfo.DebugInfo = userInfo.DebugInfo | #FLAG_SQLTrace; userInfo.TraceInfo = userInfo.TraceInfo | #FLAG_TraceInfoQueryTable; } else { userInfo.DebugInfo = userInfo.DebugInfo ^ #FLAG_SQLTrace; userInfo.TraceInfo = userInfo.TraceInfo ^ #FLAG_Trac

Executing direct SQL on an external database using ODBC

public static server void main ( Args _args ) {     Statement             statement;     str                         query;     Resultset              resultSet;     LoginProperty      loginProperty;     OdbcConnection  odbcConnection;     ;     loginProperty = new LoginProperty ( ) ;     loginProperty. setDSN ( 'YOURDSN' ) ;     odbcConnection = new OdbcConnection ( loginProperty ) ;     // Create new Statement instance     statement = odbcConnection. CreateStatement ( ) ;     // Set the SQL statement     query = 'select name from CustTable' ;     // assert SQL statement execute permission     new SqlStatementExecutePermission ( query ) . assert ( ) ;     // when the query returns result,     // loop all results for processing by handler     //BP Deviation documented     resultSet = statement. executeQuery ( query ) ;     while ( resultSet. next ( ) )     {         // do something with the result         info ( resultSet. getS

Executing direct SQL on the current AX database

When you execute a SQL statement, there are two options: - either you did a select and you expect a result to be returned - or you did insert/update/delete and you don’t expect a result. The first sample is for a SQL statement that returns a result: public static server void main ( Args _args ) {     Connection    connection;     Statement      statement;     str                  query;     Resultset       resultSet;     ;     // create connection object     connection = new Connection ( ) ;     // create statement     statement = connection. createStatement ( ) ;     // Set the SQL statement     query = 'select name from CustTable' ;     // assert SQL statement execute permission     new SqlStatementExecutePermission ( query ) . assert ( ) ;     // when the query returns result,     // loop all results for processing     //BP Deviation documented     resultSet = statement. executeQuery ( query ) ;     while ( resultSet. next ( ) )    

Linked server sql statements

Some time ago, I talked about executing direct sql statements , and now I want to share some sql statements that I used to manage linked server connections. What follows are 4 sql statements that allow you to add en remove linked servers on a database at runtime. Some assumptions: - There is a str variable named “query” that will contain the query - there is a parm method on the class that return the sql server (“server” or “server\instance”) - there is a parm method that returns a username - there is a parm method that returns a password Check if linked server exist First check if the linked server doesn’t exist yet, or you will get an error when you try to add one that already exists. query = strfmt ( "select top 1 * from sys.servers where name = '%1'" , this. parmServer ( ) ) ; Add linked server When the linked server doesn’t exist, add it. query = strfmt ( "EXEC sp_addLinkedServer @server = '%1', @srvproduct=N'SQL Server'&quo

Remove duplicate key entry from a table

Sometimes, you can’t synchronize a table because is contains duplicate records. This can occur when you change field lengths on a extended data type for example. I’ve seen people write huge SQL statements, and even jobs that create these SQL statements to remove these duplicates from a table, but there’s actually a easy way to do this in AX. Say you have a table KLFTestDuplicates that contains duplicates, then simple run the following job to remove them. static void KLFRemoveDuplicates ( Args _args ) {     Set fieldSet = new set ( Types :: Integer ) ;     DictIndex  dictIndex = new DictIndex (         tablenum ( KLFTestDuplicates ) ,         indexnum ( KLFTestDuplicates , AUniqueIdx ) ) ;     int i;     ;     if ( dictIndex. numberOfFields ( ) )     {         for ( i = 1 ;i <= dictIndex. numberOfFields ( ) ;i ++ )         {             fieldSet. add ( dictIndex. field ( i ) ) ;         }         ReleaseUpdateDB :: indexAllowDup ( dictIndex ) ;         Relea

Append text from one file to an other

This method will append all text from the original file to the destination file. While this is a very easy task, the method shows many of the things you come across when you are working with files in AX, like: - Using the #File macro - Asserting FileIOPermission to be able to access files - Asserting InteropPermission to be able to use .NET Interop - Using a set to assert multiple permissions at once - Using .NET Clr Interop in AX (better than winapi and winapiserver) - Optional cleaning up after you’re done using reverAssert() void AppendFileToFile ( FileName original , FileName distination ) {     #File     FileIOPermission    FileIOPermissionA   = new FileIOPermission ( distination , #io_append ) ;     FileIOPermission    FileIOPermissionR   = new FileIOPermission ( original , #io_read ) ;     InteropPermission   InteropPermission   = new InteropPermission ( InteropKind :: ClrInterop ) ;     Set                 permissionset       = new set ( types :: Class

Get folders and subfolders

static void KlForLoopFoldersSystemIO ( Args _args ) {     int                 k;                   // counter for result loop     container           dirs;               // container for result     filePath            path = @ "C: \t emp" ;   // input path     container getDirectories ( str _dir , boolean _inclSubDirs = true )     {         container           dList;           // container to cast array into         int                 i;               // counter for array loop         System. Array        directories;     // array for result from .NET call         #Define. Pattern ( "*" )                 // input pattern: * = all         ;         // assert interoppermissions for .NET interop         new InteropPermission ( InteropKind :: ClrInterop ) . assert ( ) ;         // get directories using .NET interop         if ( _inclSubDirs )         {             // include subdirectories             directories = System. IO . Director

Delete an AX company on SQL

This week, we were shrinking a database of a development environment by deleting some companies. Here a nice little SQL statement that uses the sp_MSforeachtable stored procedure to delete all records of a specific company (CEU in this case) from all tables. EXEC sp_MSforeachtable 'delete from ? where ?.DataAreaID = "CEU"' Certainly fast(er than AX) and gets the job done. Use at your own risk ;-) Update: You’ll want do do some cleaning up to: delete the company id from the DataArea table and from the CompanyDomainList table DELETE FROM DataArea WHERE DataArea . ID = 'CEU' DELETE FROM CompanyDomainList WHERE CompanyDomainList . CompanyID = 'CEU' Update 2: When you want to delete all companies execpt a few (like DAT), just use this: EXEC sp_MSforeachtable 'delete from ? where ?.DataAreaID <> "DAT" AND ?.DataAreaID <> "DEMO"' DELETE FROM DataArea WHERE DataArea . ID <> 'DAT

Writing in the event log from Dynamics AX

static void EventViewer ( Args _args ) {     System. Diagnostics . EventLog eventlog;     #Define. LogSource ( "Dynamics AX" )     #Define. LogName ( "Dynamics AX Log" )         ;     // check if the log already exists     if ( ! System. Diagnostics . EventLog :: SourceExists ( #LogSource ) )     {         // create new log         System. Diagnostics . EventLog :: CreateEventSource ( #LogSource , #LogName ) ;     }     eventlog = new System. Diagnostics . EventLog ( ) ;     eventlog. set_Source ( #LogSource ) ;     // write info entry     eventlog. WriteEntry ( "Just writing in the event viewer." ) ;     // write error entry     eventlog. WriteEntry ( "Error! Please check the stack trace below. \n \n " +         con2str ( xSession :: xppCallStack ( ) ) , System. Diagnostics . EventLogEntryType :: Error ) ;     // write warning entry     eventlog. WriteEntry ( "Job finished." , System. Diagnostics . EventL

Research method on X++ forms

In AX2009 if you call the research() method on a form passing true, the cursor position within a grid should be retained.  However, this is true only if the query created by the form uses inner joins.  Any outer joins, aggregations, temp tables, etc. cause the cursor to go to either the first or last record (depending upon the StartPosition property for that data source).  In 2009 if you want to keep the cursor position and use joins other than inner joins you have to keep track of the cursor position.  Code like the following will do this:     int pos;     super();     pos = salesQuotationTable_ds.getPosition();     salesQuotationTable_ds.research();     salesQuotationTable_ds.setPosition(pos);

Calling a web service from X++ code

In the AOT under the References node add a service reference specifying the wsdl location of the service, a .NET code namespace and a reference namespace.  An example of a wsdl url is: http://myMachine.myDomain/MicrosoftDynamicsAXAif50/salesorderservice.svc?wsdl Once the reference has been created, in X++ create a new class that runs on the server.  In a method in the class write the code that calls the service.  An example of how to call the sales order service is below.  Once the code in the class compiles close the AX client.  Go to the Appl\Standard\ServiceReferences directory and copy the app.config and the generated assembly to the Server\bin directory.     SalesOrder.SalesOrderServiceClient proxyClient;     SalesOrder.AxdSalesOrder salesOrder;     SalesOrder.AxdEntity_SalesTable salesTable;     SalesOrder.AxdEntity_SalesLine salesLine;     SalesOrder.AxdEntity_InventDim inventDim;     SalesOrder.AxdEntity_InventDim[] inventDimArray;     SalesOrder.AxdEntity_SalesLine[] sale

Changing the language at runtime in X++

We recently had a case where the customer wanted to change the language being used for a given AX user at runtime.  The customer had sorted out how to change the language displayed in forms and reports but the buttons on the navigation pane were not getting updated.  Here is a way to handle this scenario: In the setSetup method of the SysUserSetup form within the if clause add the following:         if (infolog.language() != userinfo.language)         {             infolog.language(userinfo.language);             infolog.navPane().loadStartupButtons();         }

Deploy AX 2009 from Terminal Services with the AX configuration - AX 2009

In my company we were trying to deploy AX 2009 from terminal services and we encountered profiles issues when other users (other than the server admin or domain admin) were trying to access the application. This was strange as we did install the client on a public share, and we also imported the correct configuration for the AX 2009 client. Anyway, I need to give full credit to my company's System Manager. His name is Rohan Robinson and he is truly a master when it comes to Terminal Services and Citrix. His email is rrobinson@argointl.com  in case you have questions for him. So, Rohan came up with the following solution: 1- He installed the AX client on a public share 2- Imported the correct configuration for the client. In here make sure that the configuration file has the correct path as shown below: 3- Moved the configuration file to the bin folder (E:\Program Files (x86)\Microsoft Dynamics AX\50\Client\Bin) 4- Modified the properties in terminal servic

AX 2009: Citrix & Terminal Server Printing

Errors, or no output when printing from AX to a locally mapped Printer If you are mapping local printers to your TS or Citrix environment and have an issues where nothing is printed or the following error is generated: startDoc: rc:SP_ERROR you will need to change the Print Processor data type from Raw to EMF. This problem occurs because the UPD requires the Enhanced Metafile (EMF) format. By default, Microsoft Dynamics AX creates the print job in the RAW format. (As of AX To modify these printer settings. 1. Click Start, and then click Control Panel. 2. Double-click Printers and Faxes. 3. Right-click the printer that you want to use, and then click Properties. 4. On the Advanced tab, click Print Processor. 5. In the Default data type pane, click NT EMF 1.008, and then click OK. Microsoft Dynamics AX will recognize the EMF format. Additionally, Microsoft Dynamics AX will send the report to the spool file in the EMF format. This is described in further detail in this KB 94752

How to send emails from AX without requiring Outlook

Sending emails from AX has been somewhat of a pain when it tries to use Outlook.  This post is a simple code modification to one method in \Classes\Info\reportSendMail.  I did not develop this code, I merely tweaked it.  The original poster's blog has disappeared, and I can only find non-working remnants all around the web of this, but it is just too useful not to repost. If you have Outlook 64bit edition, you might get the "Either there is no default mail client or the current mail client cannot fulfill the messaging request.  Please run Microsoft Outlook and set it as the default mail client."  Followed by an AX MAPI error. Or sometimes you may get the "A program is trying to access e-mail address information stored in Outlook."...Allow/Deny/Hlep. This change basically bypasses outlook.  Put it in and give it a shot. void reportSendMail(PrintJobSettings p1) { //SysINetMail m = new SysINetMail(); System.Net.Mail.MailMessage mailMessage;

Reflection and recursion on the AOT to compare projects.

So I use the base layer compare tool via Tools>Development tools>Version update>Compare layers to create a project of the "CUS" layer (normally the VAR layer for me).  This gets all of the objects modified in the CUS layer in one project.  Then I set that in the #layerCompareProject macro, then add my projects I want to check against in the lower lines of code. I've used this tool countless times to compare two projects.  Another use I had was during an upgrade to Roll Up 7 from Roll Up 1.  Somehow I had deleted a modification to an obscure table...this made me worried that I could have accidentally deleted other objects that I would have no idea about.  To check this, I went into Live and created a layer compare project of the CUS layer, then went into my upgraded RU7 environment and made the same layer compare project.  Then all I had to do was run the job and it output the objects that were missing. I think it's clever/fun the way I wrote i

Job to export all AX 2009 security groups to files

static void ExportSecurityGroups(Args _args) { SysSecurityUserGroup sysSecurity = SysSecurityUserGroup::construct(); UserGroupInfo userGroupInfo; #file Dialog dialog = new Dialog( "@SYS14863" ); DialogField dialogFileName; Object formdialog; ; dialogFileName = dialog.addField(typeid(FilePath), "@SYS16423" ); dialog.doInit(); formdialog = dialog.formRun(); formdialog.filenameLookupTitle( "@SYS53669" ); if (dialog.run() && WinAPI::pathExists(dialogFileName.value())) { while select userGroupInfo { sysSecurity.parmUserGroupId(userGroupInfo.id); sysSecurity.load(); sysSecurity.export(dialogFileName.value() + @ '\' + userGroupInfo.id + ' .asg'); } } else warning( "No action taken..." ); info( "Done" ); }

How to find the maximum number of logged on users on a given day...

This checks max users for every hour because my original task was to show user count by hour.  You can easily modify this to check every second or whatever if you need a much more detailed number. static void JobFindMaxUsersLoggedInPerDay(Args _args) { SysUserLog sysUserLog; utcDateTime utc = DateTimeUtil::addHours(DateTimeUtil::newDateTime(systemDateGet(), 0), 8); int i; int n; int iUsers; ; utc = DateTimeUtil::newDateTime(systemDateGet(), 0); utc = DateTimeUtil::addDays(DateTimeUtil::addHours(utc, 13), -13); // utc = DateTimeUtil::applyTimeZoneOffset(utc, DateTimeUtil::getUserPreferredTimeZone()); for (i=1; i<=90; i++) { iUsers = 0; for (n=1; n<=11; n++) { // Find the number of users logged in select count(recId) from sysUserLog where sysUserLog.createdDateTime < DateTimeUtil::addHours(utc, n) &&

SSRS Report calling mechanism from form

I have made some of the reports and now I want to call this report one from the main navigation area of account receivable that i have done by using menu item . Also I want to show the same report from the tab   on the form for which I want to show the report for the same Invoice Account which I have selected  by just clicking the mouse button on the same invoice Account. I mean to say is the report should get open for the same Invoice account which I have selected at that time ... Also I want to mention that I have use the rdp class as the data source in Ax 2012. I hope I got it right...  do you want to print the report for the selected (highlighted) record? Since you are using RDP classes, it is not a big issue, you just have to overwrite the prePromptModifyContract() method on the report controller class. Here is an example of code for prePromptModifyContract() method which checks if there were any args passed through the menu item button (a datasource arg).

Batch-renaming a primary key.

For a programmer who has experienced the "terror" of having to batch-rename primary keys in Dynamics AX's predecessor XAL/C5, I was pleasantly surprised to find how easy it is in Dynamics AX 2009. Though not documented a method called renamePrimayKey exists on the kernel class xRecord which each table apparently inherits from. If you call this method after changing the primarykey field on a tablebuffer, this change will be cascaded to related tables. :0) I was given a task to perform renaming of Items in the InventTable. In an environment that is to go live - so THERE WERE NO TRANSACTIONS OG STOCK LEVELS . If this has been the case we would have adviced the customer to live with it, as the renaming process potentially would have taken a very long time Nearly 75% of the item numbers were named with 4 digits, the rest with 5. The customer wanted only 5-digit item numbers. How to do this in one go ? A simple job is enough: static void Job7(Args _args) { In

Rename a Primary Key in AX through code

In this post, I am going to discuss about how to rename a primary key in AX. The renaming can be done for any record using the CCPrimaryKey Class. Let us suppose, if Customer Id 1000 has to be renamed as Cust_1000 then it will be renamed in all the tables(SalesTable, CustTable, SalesQuotationTable etc.,) wherever this cutomer Id has been used. The generic way to rename a primary key for any table is as follows:   Send a record to this method as a parameter. void renamePrimaryKey(Common _common) { Common   common; FieldId fieldId; DictTable dictTable; DictField   dictField; ; common = _common; dictTable = new SysDictTable(common.TableId); dictField = new SysDictField(dictTable.id(), dictTable.primaryKeyField()); if (isConfigurationkeyEnabled(configurationkeynum(SIG))) {   SIGBaseDocument::checkAndCacheRename(common,dictField.id(),newValue); }   startLengthyOperation(); fieldId = dictField.id(); try {   ttsbegin;

ftp x++

str ftpHostName = 'ftp.microsoft.com'; // without "ftp://", only name str username    = 'myloginname'; str password    = 'mypassword'; str oldname     = 'oldfilename'; str newname     = 'newfilename'; System.Net.Sockets.Socket socket; System.Net.Dns dns; System.Net.IPHostEntry  hostEntry; System.Net.IPAddress[] addresses; System.Net.IPAddress    address; System.Net.IPEndPoint endPoint; void sendCommand(str _command) {     System.Text.Encoding ascii;     System.Byte[] bytes;     ;     ascii = System.Text.Encoding::get_ASCII();     bytes = ascii.GetBytes(_command + '\r\n');     socket.Send(bytes, bytes.get_Length(), System.Net.Sockets.SocketFlags::None); } ; socket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily::InterNetwork, System.Net.Sockets.SocketType::Stream, System.Net.Sockets.ProtocolType::Tcp); hostEntry = System.Net.Dns::GetHostEntry(ftpHostName); addresses = hostEntry.get_Add