Thursday, January 24, 2013

AX for Retail Store RetailLog table not logging all RTS XMLs

In an earlier blog post about retail logging, I discuss how you can adjust the RetailLog to capture details about processes in the POS with the highest level of detail ( We later encountered a situation where there were issues with only certain tasks or nothing at all were being logged. The biggest thing we were trying to do was capture an XML from RTS so that we could retest in AX without the need for the POS.

After some digging, it was determined that someone with admin-like privileges on that POS machine was able to log these missing calls. I'm not sure if it was something set within the database or the POS machine but the culprit was definitely security privileges. These same differences in security were what made error handling in the POS crash instead of present the user friendly AX message for why the process failed.  Unfortunately we never nailed this down but its definitely an issue.

In summary: Not having proper privileges in the AX 2012 for Retail POS can cause the following problems:
  • POS will crash for error handling
  • RetailLog table in Store DB will not log proper calls
Not too much detail in here and I apologize but thought it was worth sharing since there isn't much out there in the way of this. If you have more specifics in your experiences, please share below.

Friday, January 18, 2013

Create New Purchase Requisition is disabled AX 2012

In AX 2012, when attempting to create a new purchase requisition record, the button can be disabled (greyed out) and the user is unable to create any new records (Figure 1 below). Alternately in one of the purchase requisition list pages, the user can click on 'Edit in Grid', then New Purchase Requisition (it will actually be enabled even though it should not be), enter a name, and be met with the error 'Field 'Preparer' must be filled in' halting any further progress (Figure 2 below).  

SOLUTION: These two issues are due to one and the same configuration issue. See below Figure 2 for the solution.

 Figure 1 - Create New Purchase Requisition button is greyed out (disabled)

Figure 2 - The error 'Field 'Preparer' must be filled in.

The solution to this is just a setup issue involving the actual user who is encountering the issue. The user logging into the system must be linked to an AX worker. 

AX has the concept of 'Workers' and actual AX users. AX users are directly linked with Active Directory accounts and Workers are just setup in AX. A user can be both, an AX user and not be a worker, and a worker and not an AX user. If the user will never log into AX, why make them an AX user? The reason that these users are split up is that there will be many users doing things where they will never access AX but still need to be labeled in transactions (e.g. creating orders in AX for Retail POS or through external applications).

Here is how to fix the issue:
  1. Navigate to the System Administration -> Common -> Users -> 'Users' form  (Figure 3)
  2. Highlight the AX user who is having this problem and click on 'Relations' in the ribbon. This must be the user that is hitting the issue as their AX user needs to be linked to a worker. (Figure 4)
  3. Select the HRM Worker setup for that user in AX in the field 'Person'. The 'User Id' field will already be populated with the user selected in the AX user list. Make sure the 'Effective dates are for before today's date and sometime in the future. If the dates don't encompass the current date, the user won't be able to create purchase requisitions. (Figure 5)
Step 3 above populates the DirPerson and DirPersonUser tables.  After these steps are done, you should be in the clear! Note: If the HRM worker of interest in Step 3 can't be seen in the 'Person' field drop down, the user will need to have a worker setup for them or make sure they are not assigned to another user. I won't go into creating an HRM worker as that is a whole other process... Plenty of stuff out there about how to do that...

 Figure 3 - Navigate to the System administration 'Users' form

Figure 4 - The 'Relations' setup in the AX ribbon

Figure 5 - Setup the link between the HRM Worker and the selected AX user

Thursday, January 17, 2013

Change default format for XPS printing from OXPS to XPS

After installing Windows 8 using a clean install, I noticed a new XPS file format: OpenXPS. I often print receipts using XPS so I was kinda annoyed with the whole OXPS defaulting thing. I wanted to change it back to XPS because the OXPS file format is not supported in Windows 7 and below without the OXPS to XPS converter tool: In this blog I'll detail what that is but I wanted to go into what is going on here first.

.XPS File Format - an electronic paper format for one-way export from a Microsoft client application (feature rich) to a platform and application independent, paginated format document which follows Open Packaging conventions.

So what is the new OpenXPS (.OXPS) format? The exact same as .XPS except its been standardized as an open format by the Ecma International standards organization. In Windows 8, Microsoft changed the out of the box printing format for xps to the oxps format. Again, this wouldn't be a problem if the new format could be opened in previous versions on windows OOTB. To fix this, I needed to change the default back to the old xps.

Here is a step by step from I copy and pasted the manual way in case it gets removed from the other location.

To use Group Policy to change the default format, take the following steps:
  1. Search for "Group Policy" and select "Edit Group Policy" from Settings.

    Settings screen
  2. In the Group Policy control panel, select Local Computer Policy\Computer Configuration\Administrative Templates\Printers in the tree on the left of the screen. Then, double-click "Change Microsoft XPS Document Writer (MXDW) default output format to the legacy Microsoft XPS format (*.xps)" to open the options.

    Local Group Policy Editor screen
  3. You can make the following selections:
    • "NotConfigured" and "Disabled" will set the MXDW default to OpenXPS.
    • "Enabled" will set the MXDW default to Microsoft XPS.
  4. Click OK to save settings.

    Change policy screen

    Note: You may need to logoff/login or force a Group Policy update from an administrator command prompt (gpupdate /force) before the change in policy will take effect.

Wednesday, January 16, 2013

AX 2012 for Retail - Retail Transaction Services (RTS) logging

Sometimes calls from the AX 2012 for Retail POS terminals don't work the way they are expected to when they hit AX code. If the calls need to be immediate with the most up to date information, the retail transaction service (RTS) functionality would be leveraged. AX and the POS communicate back and forth through XMLs, but you can also pass other parameters as well. Note: As a rule of thumb, put as much in the XML as possible. Things can get to be a big pain very quickly when not using it.

<!---UPDATE 2/14/2013--->
 For CDE-RTS in AX for Retail R2 see below for logging. It's changed quite a bit from just using the retail log in the store DB
<!---END UPDATE--->

While there can be a few first steps to debugging an issue depending on the scenario, they may ultimately lead to needing to look at:
  1. The EventViewer on the RTS server used for the call (probably an AOS server)
  2. The RetailLog in the POS store DB who sent the call
The most detail is sent in the dbo.RetailLog table. In order to capture the most detail in this table and grab the XML of interest, do the following below:
  1. Set the RTS log level for the POS is at the highest possible level: 'Trace'. This is a setting in the Functionality profile, which is assigned to the store.  Run the below SQL statement against the POS database to set this. The LogLevel field is a base enum and 'Trace' is the zero value.  UPDATE RETAILFUNCTIONALITYPROFILE SET LOGLEVEL = 0
  2. Clear the RetailLog table to capture a fresh log of the error: DELETE FROM RETAILLOG 
  3. Launch the POS application and go through the order process to the selected action of interest. Once complete, run the following: SELECT * FROM RETAILLOG
Once you have the XML, you can look at it to debug it or you can copy and paste the XML into a job to manually recreate the process and debug the RTS static method that was used in the POS call.  Building this job should generally be only a few lines since the XML is already built. USEFUL HINT: When putting the XML into a string, use single quotes '</XML string>' around the whole XML as it may contain double quotes and its the fastest way.

Here is some more information around the Retail POS logging options

Saturday, January 12, 2013

AX XPO import error: 'Unknown export file format'

When someone attempted to import the file into the AOT, they received the error ‘Unknown export file format’ (Figure 1)
Check the first word in the XPO. The file has been manipulated somehow so that the first word is not 'ExportFile' or 'Documentation'. It needs to be one of these two but who knows what else is wrong in the file. You can try to fix it and try it again, but as long as you are able to grab a new XPO with the same content, that is the safest approach.
From the code below in the SysImportElements class' 'ExamineFile' method, we can see that the code is looking at the first word in the XPO file (Figure 3 line 14). If the first words in the XPO is not 'ExportFile' or 'Documentation', the condition on line 23 is hit which is our error.

Figure 1 - The error message

 Figure 2 - The XPO contents with the first word highlighted

Figure 3 - The code where the error is being thrown

Friday, January 11, 2013

AX X++ str2Date function deep dive

From time to time, a user will need to pull a date from a string. More often than not it is from an XML or from some other location. In order to actually use this date passed, it needs to be of data type 'date' so it can be written to fields, used in queries etc.  To do this, a user can use the 'str2Date' function.

The str2Date function takes two parameters:
  1. datestring = The date in string format. It can be separated by either a dash ('-') or slash ('/')
  2. sequence = A three number combo using 1, 2, and 3 where 1 = day, 2 = month, and 3 = year. (e.g. 231 would be MM-YYY-DD, and 321 would be YYYY-MM-YY)
If the sequence doesn't match the string or has an error, a '0' will result. Note that its very important to distinguish between day and month as an invalid month (>12) will result in a 0 and if both are <13, the date can translate totally wrong.

I've detailed out the results of various scenarios below. Should be pretty self explanatory so I won't go into too much additional detail. I included the output as well as the code used to get it. Kinda neat. Enjoy!

static void date2StringTest(Args _args)
    str dateStrYMD = '2013-01-08';
    str dateStrMYD = '01-2013-08';
    str dateStrMDY = '01-08-2013';
    str dateStrDMY = '08-01-2013';
    str dateStrYMD2y = '13-01-08'; // two digit year
    str dateStrYMD1m = '2013-1-08'; // 1 digit month
    str dateStrYMDinv = '2013-14-08'; // invalid month
    str dateStrYMDslsh = '2013/1/08'; // Slash instead of dash
    str zeroResultStr = 'INVALID and will be 0';
    int i;

    // Note that the second parameter to the str2Date field is 3 numbers which must be a
    // combination using 1, 2, and 3 where 1 = day, 2 = month, and 3 = year
    // For example 231 would be MM-YYYY-DD, and 321 would be YYYY-MM-YY

    // Year Month Day test
    info (strFmt("----Start YMD test for %1----", dateStrYMD));
    info (strFmt("1. YMD - str2Date('%1', 321) - result: %2", dateStrYMD, str2Date(dateStrYMD, 321)));
    info (strFmt("2. YMD - str2Date('%1', 231) - result: %2", dateStrYMD, str2Date(dateStrYMD, 231)));
    info (strFmt("3. YMD - str2Date('%1', 213) - result: %2", dateStrYMD, str2Date(dateStrYMD, 213)));
    info (strFmt("4. YMD - str2Date('%1', 123) - result: %2", dateStrYMD, str2Date(dateStrYMD, 123)));
    info ("");

    // Month Year Day test
    info (strFmt("----Start MYD test for %1----", dateStrMYD));
    info (strFmt("5. MYD - str2Date('%1', 321) - result: %2", dateStrMYD, str2Date(dateStrMYD, 321)));
    info (strFmt("6. MYD - str2Date('%1', 231) - result: %2", dateStrMYD, str2Date(dateStrMYD, 231)));
    info (strFmt("7. MYD - str2Date('%1', 213) - result: %2", dateStrMYD, str2Date(dateStrMYD, 213)));
    info (strFmt("8. MYD - str2Date('%1', 123) - result: %2", dateStrMYD, str2Date(dateStrMYD, 123)));
    info ("");

    // Month Day Year test
    info (strFmt("----Start MDY test for %1----", dateStrMDY));
    info (strFmt("9.  MDY - str2Date('%1', 321) - result: %2", dateStrMDY, str2Date(dateStrMDY, 321)));
    info (strFmt("10. MDY - str2Date('%1', 231) - result: %2", dateStrMDY, str2Date(dateStrMDY, 231)));
    info (strFmt("11. MDY - str2Date('%1', 213) - result: %2", dateStrMDY, str2Date(dateStrMDY, 213)));
    info (strFmt("12. MDY - str2Date('%1', 123) - result: %2", dateStrMDY, str2Date(dateStrMDY, 123)));
    info ("");

    // Day Month Year test
    info (strFmt("----Start DMY test for %1----", dateStrDMY));
    info (strFmt("13. DMY - str2Date('%1', 321) - result: %2", dateStrDMY, str2Date(dateStrDMY, 321)));
    info (strFmt("14. DMY - str2Date('%1', 231) - result: %2", dateStrDMY, str2Date(dateStrDMY, 231)));
    info (strFmt("15. DMY - str2Date('%1', 213) - result: %2", dateStrDMY, str2Date(dateStrDMY, 213)));
    info (strFmt("16. DMY - str2Date('%1', 123) - result: %2", dateStrDMY, str2Date(dateStrDMY, 123)));
    info ("");

    // Other scenarios
    info ("----START OTHER SCENARIOS----");
    info (strFmt("17. YMD using %1 which only has 2 digits for the year (e.g. 13 instead of 2013) -            result: %2", dateStrYMD2y, str2Date(dateStrYMD2y, 321))); // 2 digit year
    info (strFmt("18. YMD using %1 which only has 1 digit for the month (e.g. 1 instead of 01) -               result: %2", dateStrYMD1m, str2Date(dateStrYMD1m, 321))); // 1 digit month
    info (strFmt("19. YMD using %1 which has an invalid numbe for the month (e.g. 14 is greater than 12) - result: %2", dateStrYMDInv, str2Date(dateStrYMDInv, 321))); // invalid month
    info (strFmt("20. YMD using %1 which has a slash instead of a dash (e.g. 2013/01/08 instead of 2013-01-08) - result: %2", dateStrYMDslsh, str2Date(dateStrYMDslsh, 321))); // different divider

Thursday, January 10, 2013

AX Issue: Application very slow. SQL server not releasing memory

There can be an issue (usually in lower environments) where the AX application can almost come to a screeching halt. Emails fly about not being able to post SOs/POs, GL issues, the whole works. The fingers fly to deployments and new changes to the environments when looking at the individual issues but its important to see if the issues being sent out are for all users and are applying to all processes and not just deployment issues.

The first place I would look is to make sure the AOS is not hitting the 'rails' (aka maxing out the server). If there is more than 15% of the physical memory free, I would look at the SQL server for the block. Odds are it might look like Figure 1 below (if using 8GB SQL box space).

As I understand it, SQL will dynamically cache memory and not release it until it hits a certain memory limit. Once it hits these limits, it will start releasing memory. Out of the box, the memory is set to be unlimited. I'm sure there are reasons for this but I don't know them. Please share below if you can add to this topic. All I know is that this issue was causing an issue in an implementation and this solution fixed it.

To help resolve the issue, You can right click the DB of interest, click properties, select memory, and finally select an amount of memory to use.  When adjusting this maximum server memory, the actual consumed RAM will be a little higher. For example, adjusting to 6GB (6000 MB) will actually consume 6.4GB. If the value were 5500 (5.5GB), the actual memory consumed for the sqlservr.exe would be 5.9 GB. SQL must have enough memory to cache the appropriate number of objects as well as be small enough for the server to still have some RAM to do its thing as well. Its a balancing act so don't be afraid to play with these settings.

NOTE: In order to change these settings, your user must have the appropriate permissions. In my experience, the user must have either the Sys Admin or Security Admin roles added to allow the changes above.

 Figure 1 - SQL without a max cap

Figure 2 - Changing the Maximum server memory setting in SQL

Figure 3 - The sever sqlserv.exe size from Figure 1 after the change from Figure 2

Wednesday, January 9, 2013

AX X++ compile and compile forward explanations

When there is a stack trace error or a code promotion, people often ask about the need to compile and compile forward. I think its important for people to realize the purpose for those two processes and how to do them. Note: Synchronizing is different than compiling. I can write a post on that if people are interested.

In AX, there are two types of compilations: Compiling and compiling forward. You can compile objects individually, as a group, or with the entire application. There is also an option to 'compile into CIL' in AX 2012. I'm not getting into this option as a clean compile is needed for that step. Compiling is essentially taking the X++ code and 'packaging' it into p-code (packed code). Compiling forward basically does the same thing but packages the other relevant code that the selected object extends. If compiling forward is not done, the classes that are used in the extension of that selected class will not see the new changes to the code as their p-code will not be updated. When there are discrepancies, it can cause the AOS to crash.

All objects can and should be compiled upon import. The system should do that automatically with the import functionality but I have seen it a few time where this does not happen so I do it manually. as well just to be sure. Only items that are extending other classes need to be compiled forward.

Compile forward actions are only needed for objects (classes) that are inheriting other classes. Compiling forward should be considered an exception type compile. A compile forward can be identified as being needed by looking at the classDeclaration method of the class (see below). If this is not done, it can cause the application to crash. So if the system is crashing after a recent promotion and the code base is the same, make sure to compile forward the appropriate objects.

Hope that helps clear up any confusion on when the types of compilations are needed.

This post isn't intended to go into details about the compilations and bytecode updating. See Martin Drab's post below for more detail. His posts are always very solid. 

Here is how to do these:
  1. Compile the objects individually (Figure 1)
  2. Compile the objects as a group (Figure 2)
  3. Compile the entire application from the AOT (Figure 3)
  4. Compile the entire application from the front end (Figure 4) 
  5. Compile forward the object of interest (Figure 5) 
Figure 1 - Compile items individually

 Figure 2 - Compile items as a group

 Figure 3 - Compile the entire application from the AOT

Figure 4 - Compile the entire application from the front end (under system administration module)

Figure 5 - Compile forward option. This option will not be available for objects not extending other objects

Wednesday, January 2, 2013

Can't print pdf from Adobe Reader or W8 reader in Windows 8

I recently switched to the Windows 8 operating system and was trying to print a pdf doc but didn't see any options to print the document from within that program. Also, when I right clicked on the pdf in windows explorer, I didn't have an option to print.

After some research these two pdf applications don't have the ability to print from within those apps.  That sucks. Whatever.

Anyways I found something that does allow for PDFs to be printed. Found this website ( which had me download this free file (PDF2Printer Setup) which then allowed me to open up a pdf in a program called 'PDF Viewer for Windows 8 which had all the options I needed. I didn't follow any of the instructions on that article. Just opened the pdf using the 'Open with' (see below screenshot). The program has worked for me so far so hopefully other people can get some use out of this as well.