We all know the "print" statement, but what I find interesting is in Dynamics 365 for Finance and Operations, Enterprise Edition, it has been changed.
It works the same, but it now uses System.Diagnostics.Debug::WriteLine("Hello World") to print to the output window.
You can read more about it and other interesting debugger features here:
https://docs.microsoft.com/en-us/dynamics365/unified-operations/dev-itpro/dev-tools/new-x-debugger-features
Alex on DAX (Alex Kwitny)
This technical blog will be about my adventures with Microsoft Dynamics 365 for Operations (AX7/D3fo), AX 2012, and AX 2009.
Thursday, May 9, 2019
Tuesday, October 23, 2018
Fun and better method to get Args.Caller() name
In any AX/D365 environment, there's often a need to get the Args.Caller().Name(), except not all possible caller's will have a .name() method, so here's a little function that can be added to a form to get the caller's name in a more proper method.
private IdentifierName getCallerName() { Object caller; SysDictClass sysDictClass; IdentifierName callerName; // There must be a caller to retrieve a name if (!(element.args() && element.args().caller())) return callerName; caller = element.args().caller(); sysDictClass = new SysDictClass(classIdGet(caller)); // Use reflection & recursion to get the name if possible while (sysDictClass) { if (sysDictClass.hasObjectMethod(identifierStr(Name))) { callerName = caller.name(); sysDictClass = null; } else if (sysDictClass.extend()) { sysDictClass = new sysDictClass(sysDictClass.extend()); } else { sysDictClass = null; } } return callerName; }
Tuesday, April 24, 2018
How to convert a decimal to a fraction in X++ with Dynamics AX or Dynamics 365 FO
I had a customer who needed to convert a decimal to fraction, so I just translated this StackOverflow post where all credit is due. I'm sure someone else will find it useful at some point.
The two main things in the job below (details taken from StackOverflow) are "_real", which is the value you want to convert, and "_accuracy", which specifies the max relative error; not the max absolute error.
So _accuracy = 0.01 would find a fraction within 1% of the value.
I also quickly threw this job together, so I didn't test for extremely large integers or any edge cases.
Here's a simple job that demonstrates how it works.
The two main things in the job below (details taken from StackOverflow) are "_real", which is the value you want to convert, and "_accuracy", which specifies the max relative error; not the max absolute error.
So _accuracy = 0.01 would find a fraction within 1% of the value.
I also quickly threw this job together, so I didn't test for extremely large integers or any edge cases.
Here's a simple job that demonstrates how it works.
static void KW_DecToFrac(Args _args) { // Create a function that accepts these two parameters real _real = 0.45; real _accuracy = 0.01; int sign = _real < 0 ? -1 : (_real == 0 ? 0 : 1); real maxError; System.Decimal d; int n; int lower_n = 0; int lower_d = 1; int middle_n; int middle_d; int upper_n = 1; int upper_d = 1; int z; void f(int _N, int _D) { info(strFmt("%1/%2", _N, _D)); } _real = abs(_real); maxError = sign == 0 ? _accuracy : _real * _accuracy; d = System.Math::Floor(_real); n = System.Decimal::ToInt32(d); _real -= n; if (_real < maxError) { f(sign * n, 1); return; } if (1 - maxError < _real) { f(sign * (n+1), 1); return; } while (true) { z++; middle_n = lower_n + upper_n; middle_d = lower_d + upper_d; if (middle_d * (_real + maxError) < middle_n) { upper_n = middle_n; upper_d = middle_d; } else if (middle_n < (_real - maxError) * middle_d) { lower_n = middle_n; lower_d = middle_d; } else { f((n * middle_d + middle_n) * sign, middle_d); return; } } info("Done"); }
Wednesday, April 4, 2018
[#MsDyn365FO] How to adjust your DeployablePackages cleanup duration
Microsoft automatically cleans up the "DeployablePackages" folder for data older than 30 days on your Dynamics 365 for Finance and Operations, Enterprise Edition machine if you are using the LCS servicing flows.
If you prefer to keep less data, such as 10 days, you can add the following registry key to HKLM:\SOFTWARE\Microsoft\Dynamics\Deployment :
Or copy & paste the below into a *.reg file, and you can add it that way:
See Yammer thread here for Microsoft's comments.
If you prefer to keep less data, such as 10 days, you can add the following registry key to HKLM:\SOFTWARE\Microsoft\Dynamics\Deployment :
Or copy & paste the below into a *.reg file, and you can add it that way:
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Dynamics\Deployment] "CutoffDaysForCleanup"="10"
See Yammer thread here for Microsoft's comments.
Tuesday, January 2, 2018
How to see when AOS service was started/stopped with PowerShell for AX with 1 line of code.
I often need to see when an AOS was stopped for various reasons, and scanning the event viewer can be a hassle. Running this simple 1-line PowerShell script will give you that information.
You can also use this script as a guide to run other PowerShell scripts to search the event log for specific text.
You can also use this script as a guide to run other PowerShell scripts to search the event log for specific text.
Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable @{logname='system'; StartTime=(((get-date).AddDays(-30).Date)); EndTime=(((get-date).AddDays(1).Date)); id=7036} -MaxEvents 10000 | ? {$_.Message -like '*Microsoft Dynamics AX Object Server*'}
Tuesday, August 22, 2017
Job to export AX 2012 label file in Dynamics 365 FOEE format for easy movement
I am maintaining an ISV solution across AX 2012 and Dynamics 365 for Finance and Operations, Enterprise Edition, and when there is development in one, porting those changes to the other can be a hassle.
If I create a label in Dynamics AX 2012, exactly creating it in D365 isn't straight forward/simple.
I quickly wrote this simple job to export a label to a format that can be easily imported into Dynamics 365. I simply looked at how D365 label text is stored and inferred this, so I haven't done extensive testing, but it seems to work fine for several hundred labels when comparing via WinMerge what my job outputs vs what D365 contains.
If I create a label in Dynamics AX 2012, exactly creating it in D365 isn't straight forward/simple.
I quickly wrote this simple job to export a label to a format that can be easily imported into Dynamics 365. I simply looked at how D365 label text is stored and inferred this, so I haven't done extensive testing, but it seems to work fine for several hundred labels when comparing via WinMerge what my job outputs vs what D365 contains.
static void AlexOnDaxExportLabelToD365(Args _args) { #File str labelFileId = 'QUA'; str language = 'en-us'; Filename labelFilenameD365 = @'C:\Temp\AlexLabel.label.txt'; LabelId labelId; LabelString labelString; LabelDescription labelDescription; Set setLabelIds; SetEnumerator se; SysLabelFileReader labelFileReader; TextIo textIo; SysLabelFile labelFile = SysLabelFile::newLanguageModule(language, labelFileId); if (!Label::flush(labelFileId, language)) throw error(strFmt("Unable to flush label %1 in language %2", labelFileId, language)); new FileIOPermission(labelFilenameD365, 'W').assert(); // This just create the file if it doesn't exist textIo = new TextIo(labelFilenameD365, #IO_Write, #utf8Format); textIo.write(''); textIo = null; // We output the file somewhere if (labelFile.toFile(labelFilenameD365, true)) { labelFileReader = SysLabelFileReader::newFileClient(labelFilenameD365); if (labelFileReader) { setLabelIds = labelFileReader.labelIds(); } } if (!(setLabelIds && labelFileReader)) throw error("Unable to get label"); textIo = new TextIo(labelFilenameD365, #IO_Write, #utf8Format); se = setLabelIds.getEnumerator(); while (se.moveNext()) { labelId = se.current(); labelString = labelFileReader.labelText(labelId); labelDescription = labelFileReader.labelDescription(labelId); // There must be a value if (!labelString) labelString = ' '; if (labelDescription) textIo.write(labelId + '=' + labelString + '\n' + ' ;' + labelDescription); else textIo.write(labelId + '=' + labelString); } textIo.write(''); // Write ending CR textIo = null; CodeAccessPermission::revertAssert(); info(strFmt("Finished converting %1 to Dynamics 365 for Operations label file", labelFilenameD365)); }
Wednesday, August 16, 2017
How to find previously used addresses or other older records in Valid Time State Tables for Date Effective Data in Microsoft Dynamics 365 or AX
In Dynamics (AX/365) there is a concept of "Valid Time State Tables" that contain date effective data. In this post, you'll find code to list all the previous addresses tied to an individual record.
If you are not familiar with this type of data, the simplest example to wrap your head around is postal addresses. If you are, just skip the next paragraph.
Let's say you send an order/invoice to a customer at 123 South St on 1/1/2017. Then a few months later, the customer moves locations to 456 North Ave and you update their address in Dynamics. All is good so far. Then for some reason you need to reprint the 1/1/2017 invoice...well that address record for 123 South St still exists in the database, but it's "ValidTo" is in the past. This way you have the historical addresses.
This sample job below takes an address and loops through the older versions of that address. This method can be used to seek any data in valid time state tables though.
If you are not familiar with this type of data, the simplest example to wrap your head around is postal addresses. If you are, just skip the next paragraph.
Let's say you send an order/invoice to a customer at 123 South St on 1/1/2017. Then a few months later, the customer moves locations to 456 North Ave and you update their address in Dynamics. All is good so far. Then for some reason you need to reprint the 1/1/2017 invoice...well that address record for 123 South St still exists in the database, but it's "ValidTo" is in the past. This way you have the historical addresses.
This sample job below takes an address and loops through the older versions of that address. This method can be used to seek any data in valid time state tables though.
static void AlexOnDAXFindOldAddresses(Args _args) { CustTable custTable = CustTable::find("ZZZZ"); LogisticsPostalAddress logisticsPostalAddress; LogisticsPostalAddress logisticsPostalAddressOld; utcDateTime utcMinVal = DateTimeUtil::minValue(); utcDateTime utcMaxVal = DateTimeUtil::maxValue(); // Active address logisticsPostalAddress = CustTable.postalAddress(); info("Current: " + logisticsPostalAddress.Address); setPrefix("Previous Addresses"); // This will loop through the inactive addresses in order while select validTimeState(utcMinVal, utcMaxVal) logisticsPostalAddressOld order by ValidTo desc where logisticsPostalAddressOld.Location == logisticsPostalAddress.Location && logisticsPostalAddressOld.RecId != logisticsPostalAddress.RecId { info(strFmt("%1 [%2-%3]", logisticsPostalAddressOld.Address, logisticsPostalAddressOld.ValidFrom, logisticsPostalAddressOld.ValidTo)); } }
Subscribe to:
Posts (Atom)