Pages

Thursday, June 11, 2026

Unable to Login to Dataverse using cached credentials - D365 F&O Unified Environment

  


If you repeatedly receive this error when trying to connect to your Dynamics 365 Finance & Operations Unified environment, it means your local credential cache is likely corrupt.


Solution:

Close Visual Studio, delete the extension credential cache at...

%AppData%\Roaming\Microsoft\CRMDeveloperToolKit

 


Then relaunch Visual Studio and connect again. You'll be prompted to authenticate and a new credential cache will be created.

Thursday, May 9, 2019

Print statement in D365 and other interesting bits

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

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.


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:

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.

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.


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.

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));
    }
}



Friday, May 26, 2017

How to rename a D365 virtual machine, link it to LCS, and optionally join it to a domain - Dynamics 365 for Finance and Operations, Enterprise edition

Whenever I get a new D365 virtual machine, I need to rename it, link it to LCS, and join it to our domain. I kept forgetting what I did, so I wrote it down here to share.

I rename it because there are multiple copies of the VM on the same network, and joining to a domain should be obvious all of the benefits there.

So here are the steps that I perform to quickly do this, and if anyone has any other steps to add/modify, please let me know and I'll update the post. Follow carefully as each step is important.


  1. Connect to the VM, provision yourself (as usual)
  2. Go to Control Panel>System and Security>Security, click "Change Settings", and rename the machine to something unique.
  3. Restart VM
  4. Open Reporting Services Configuration Manager and connect to "localhost" or whatever you named your machine, and change the Database server so that it connects
  5. Open SQL Management Studio by doing "Run As Administrator", as the Local Admins security group is added but not the local administrator user.  Connect to SQL.
  6. Run these commands to get the name SQL thinks it is and the name you've used
    select @@SERVERNAME
    Select serverproperty('ServerName')
  7. Copy the old machine name and new machine name and replace it in these SQL commands and execute them
    --Run this with the updated names
    sp_dropserver 'MININT-S45GUTR'; --Old Name
    GO

    sp_addserver 'D365QDEVMAIN',LOCAL; --New Name
    GO
  8. Restart the machine
  9. In SQL (run as admin), run the steps in Step 6 again to verify both return the same new machine name result
  10. (Optional) The following steps are the optional LCS/Domain steps. Create a project in LCS that your D365 for Operations (or whatever it's called at the time of reading this) VM will connect to.
  11. (Optional) Download the LCS diagnostic installer under the "System Diagnostic" tile
  12. (Optional) Extract and run Setup. Choose "Create a new certificate" and enter whatever into the prefix. I usually just do the machine name.
  13. (Optional) This creates a certificate in the same directory as the installer, upload the certificate back into LCS under the same tile, then continue with the installer after it has been uploaded.
  14. (Optional) For the account/password I put in MachineName\Administrator and the administrator password. If you choose to use the local admin, it's important that you type the newly created machine name you chose or it doesn't always work.
  15. (Optional) There should now be a new icon on your desktop to launch the LCS diagnostic utility. Run this.
  16. (Optional) Put in the environment name and server name with your newly chosen name and put "AxDB" for the database name. Then click the buttons at the bottom of the tool in sequence.
  17. (Optional) When you get to step #3, your window may white-out and clear. Just choose the drop-down at the top under "Environment Name" and re-select your environment
  18. (Optional) Click "Generate Command" to get a copy/paste command that you can set up a scheduled task for so that LCS gets regular data. Copy the command and close the window. Mine was:

    "C:\Program Files\Microsoft Dynamics Lifecycle Services System Diagnostic Service\LCSDiagFXCollector.exe" "-Collect" "D365QDEVMAIN" "1"
  19. (Optional) Open "Task Scheduler" and create a new task and choose "Run whether user is logged on or not" and I choose "Run with highest privileges"
  20. (Optional) Choose the Triggers tab and setup a daily trigger (or whatever you want)
  21. (Optional) Choose the "Actions" tab, click New, and paste the entire command in the program/script window and click ok. It will prompt you to automatically fix it so just click yes.
  22. (Optional) Click OK to save the task and enter your user/password so that it can store it to run the task
  23. (Optional) Go back to LCS and under the same "System Diagnostic" tile, click on the "Environments" tab and verify your new environment is there and data is uploaded
  24. (Optional) Finally join it to your domain and restart.

And you are done!

Thursday, May 18, 2017

How to programmatically add menu items to favorites menu via X++ in Dynamics AX

The favorites menu is a bit unusual in AX in the way you need to add to it. Most of the way it works appears to be Kernel level, but through some TreeNode usage you can add items to it.

This will add the "SalesTable" menu item to your favorites.


static void JobAddToFavorites(Args _args)
{
    TreeNode                treeNode;
    TreeNode                menuToAdd = TreeNode::findNode(@"\Menu Items\Display\SalesTable");
    TreeNodeIterator        iterator;
    UserMenuList            userMenu;
    Menu                    menuNode;


    treeNode = infolog.userNode();
    iterator = treeNode.AOTiterator();
    treeNode = iterator.next();
    if (treeNode)
    {
        userMenu = treeNode;

        // find 'My Favorites' user menu; 
        treeNode = userMenu.AOTfindChild("@SYS95713");

        // Note menuNode is a different object than userMenu
        menuNode = treeNode;

        menuNode.addMenuitem(menuToAdd);
    }    
}

Another table to take note of is SysPersonalization if you're looking at doing this for multiple users. I haven't dug into this deeply, but this code snippet should get you started with what you may want to do.

Friday, May 5, 2017

How to remove the default filter controls on a ListPane in AX 2012

When you create a ListPage, the kernel automatically adds a FormFilterPaneControl with some filtering options. Many think this can't be removed or hidden, but it can!



It is added during the run method's super() call by the kernel, so using this code on the ListPage you can remove it.  The caveat is if you are using the ListPage FormTemplate, it's not possible because you're not able to override the run method.

The last closing comment I'll add is I don't know if the kernel expects the control to be there later on and if this can cause any issues or not. I just was curious if I could get it to go away.


public void run()
{
    int                 i;
    FormControl         formControl;

    super();
    
    for (i=1; i<=element.design().controlCount(); i++)
    {
        formControl = element.design().controlNum(i);

        if (formControl is FormFilterPaneControl)
        {
            element.design().removeControl(formControl.id());
        }
    }
}


Monday, April 17, 2017

Address Validation in Dynamics AX - QuAAX

I wanted to talk about a new product for address validation and discovery in AX called QuAAX (Quality Addressing for AX), powered by Experian Data Quality. It's more than a simple "is this address valid or not" type of program and allows for address discovery, quick entry, and some fuzzy operations.  It also works internationally with 200+ countries supported.


Disclaimer: I worked on this product.



Addresses in AX can be a headache.  Between data migrations from years of different systems, order imports from external sources, and user entry error you can end up with a mess of postal address records that you have varying degrees of confidence in. This is where QuAAX can help solve many of those issues.


The code is almost entirely X++ and lives in AX, which makes desired customization easy and provides transparency. It's designed to be an easy install via model file with a few overlayered objects to merge.

It is powered by Experian Data Quality web services, so for the configuration you simply put in your Auth Token and set a few minor settings. This means you don't need to update any on-premise software. It also back-fills related tables, so no need to create new zip-codes and it can automatically complete zip+4 codes.


There is a new indicator on the address records throughout the system that visually indicates the status of the address and shortcut buttons to verify addresses.


It will automatically create suggestions for validated addresses that are more well formatted.



When you have an address that is close to correct, it will automatically jump into the edit view with the parts it was able to verify and ask for user input. An example is this address (401 B St, San Diego, CA 92101) where I forgot to enter the suite #. It prompts me to enter it and provides acceptable ranges of input.


There is a fancy engine used for keying in new addresses that lets you very quickly enter data and also suggests inputs as you type.


It also has a bulk processing engine and simple API, so you can perform cleanup on your legacy data or perform validation calls on new addresses input from external sources.


Some final comments. Right now it's supported for AX 2012 and it's coming soon to Dynamics 365 for Operations.  There are plenty more features I wasn't able to cover in this post such as lat/long, address type indicator (residential vs commercial), and more.

More information can be found here: http://axmentor.com/solutions/products/quality-addressing-for-ax/

As always, happy DAX'ing!

Tuesday, March 14, 2017

New features (CDS Integrations) and some clarity on Common Data Services (CDS) and how it will work in the Dynamics 365 Sphere of products

For the last year how the Common Data Services (CDS) will actually work and play in the Dynamics 365 sphere of products has been a fairly gray area. It has been often promised that "it just works", without providing any details to what is going on behind the scenes. Hopefully I'll be able to provide some grain of clarity on that.

The following is still under development by Microsoft and hasn't been officially announced, but it's what I've gathered from speaking with the various product members.

How does the Common Data Model get filled by the CDS? - Microsoft is building 1st class integrations

Is it flow? - No

New configuration section - "Integrations":



So this doesn't yet exist yet, but there is supposed to come a new section in the PowerApps Admin Center where you can configure your environment integrations with the Common Data Service. It's also important to note that it lives in the PowerApps Admin Center for now only because they haven't come up with a better place, even though it doesn't seem to make total sense to me.

So things like:

  1. Dynamics 365 for Sales (CRM) to
  2. Common Data Services to
  3. Dynamics 365 for Operations (AX)


This order is important because "you have to crawl before you can walk" and Microsoft's first goal is to go from Sales => CDS => Operations. Eventually, they'll go both ways.

Flow Templates:

If you're like me and tried to figure it out on your own, you may have stumbled upon some pre-configured Flow Templates that might fit the need:

How are flow templates different than integrations?

Integrations to the Common Data Service will be more robust and "first class" integrations by Microsoft for bulk data moving and syncing, while Flow Templates are for purpose built, row-by-row needs.

What technologies do integrations use under the hood?
The integrations use a combination of Data Import Export Framework (DIXF), OData, and M-Engine which sits under Power Query.  These allow the integration to push bulk amounts of data between the systems on an unspecified syncing schedule.

This is an exciting time as these technologies are bleeding edge and new things are expected to release very soon! As always, happy DAX'ing! Or D365'ing?

Wednesday, February 15, 2017

Unlimited private agents for Visual Studio Team Services!

In January Microsoft announced via e-mail good changes to Build & Release Private Agents. They introduced the concept of "Private Pipelines" in Visual Studio Team Services.



What are Agents?
An agent is basically a task-runner that you can pass build activities to, such as PowerShell scripts, executables, etc. The agent can either be Hosted (in the cloud on Azure) or on-premise (installed on your private servers).

When you run a build/release process from VSTS, it sends your tasks to an Agent to do your work.

What's new?
The concept of a "pipeline" is introduced, which is essentially the number of concurrent agents running tasks at the same time.  Previously, you were allowed one on-premise agent (for free) and if you wanted more agents on different servers you had to buy additional ones ($15/ea).

Now, you can have unlimited on-premise agents! You are given one private and hosted pipeline free with your VSTS account. So this means you can still only run operations on one agent at a time unless you buy additional pipelines.

Why is this a good thing?
If you have Dev/Test/Prod systems, you may want to place an agent on each machine where each has certain privileges and setup your build processes to communicate with each agent. And you may have no need for many concurrent build/release operations, but you want to have your agents (task runners) on different machines. This is VERY convenient.  Previously, the work around was to just figure out ways to have your agent run on a single machine and execute operations remotely to the other machines, which you can imagine is a hassle.

Here is the full contents of the email:
Starting later this month and running through the end of February, we will be gradually deploying a change to how we count Build & Release Private Pipelines (previously called Private Agents) in Visual Studio Team Services.  It does not change your bill.

Up to this point, you have needed a Private Pipeline for each separate private build agent or private release agent that you configure in your Team Services account.
Going forward, you can configure as many private agents as needed in your Team Services account without incurring extra charges.  You'll only need as many Private Pipelines as the number of concurrent builds you want to run or releases you want to deploy using your own machines. With this new way of counting Private Pipelines you may be able to purchase fewer Private Pipelines than you do today. 
Learn more about the impact of this change on your builds and releases.
If you have any questions, please contact us through the Visual Studio Developer Community (http://developercommunity.visualstudio.com) or via customer support (https://www.visualstudio.com/team-services/support/). 

Tuesday, November 22, 2016

Dynamics 365 for Operations (AX7) leverages Facebook libraries

So a little known thing about Microsoft Dynamics 365 for Operations (AX7) is that the user interface appears to be built on Facebook's React JavaScript libraries, specifically React with Addons.

Hearing Facebook and D365 in the same sentence initially makes me wince a little, but it makes perfect sense. Facebook is in the business of user interface on the web, so why would Microsoft reinvent the wheel for user interface when the engineers at Facebook are leading the way and devoting more resources towards it?

You can see the React JS files in various locations, such as C:\AOSService\webroot\Scripts\ext\react*.js on a demo VM.



If you've tried to create/debug a D3fo custom control using Chrome/FF developer tools and stepped into the JavaScript, you'll eventually see it in the min versions of the React libraries.

As of writing this, the current version embedded in the Nov 1611 VM is 15.1.0 while the available is v15.3.2.

So if you're experiencing bugs with some controls, there may be a fix on the horizon.

Dynamics 365 for Operations (AX7) leverages Facebook libraries

So a little known think about Microsoft Dynamics 365 for Operations (AX7) is that the user interface appears to be built on Facebook's React JavaScript libraries, specifically React with Addons.

Hearing Facebook and D365 in the same sentence initially makes me wince a little, but it makes perfect sense. Facebook is in the business of user interface on the web, so why would Microsoft reinvent the wheel for user interface when the engineers at Facebook are leading the way and devoting more resources towards it?

You can see the React JS files in various locations, such as C:\AOSService\webroot\Scripts\ext\react*.js on a demo VM.



If you've tried to create/debug a D3fo custom control using Chrome/FF developer tools and stepped into the JavaScript, you'll eventually see it in the min versions of the React libraries.

As of writing this, the current version embedded in the Nov 1611 VM is 15.1.0 while the available is v15.3.2.

So if you're experiencing bugs with some controls, there may be a fix on the horizon.

Wednesday, May 4, 2016

The better way to detect when multiple records are selected

There are some instances when you want to know when multiple records are marked on a form. An example would be if you wanted a button to be enabled only when more than 1 record was selected.

You can make a button disabled when more than 1 record is selected, but not the other way around.

As far as I can tell, the way the grid functions is it stores an Array object of the cursor positions of the marked records. Below are the two ways I've seen to solve this problem. Method #2 is more common, but Method #1 seems more efficient to me as you're only interacting with the Array and not traversing the datasource's table buffer.


    FormDataSource  fds = salesTable.dataSource();

    // Method 1
    // The grid stores an array of marked records, and this
    // will return only the index, which is all that is needed
    if (fds.recordsMarked().lastIndex() > 1)
        info("Multiple records selected");
    else
        info("1 or 0 records selected");
    
    // Method 2
    // This will traverse the datasources buffer
    fds.getFirst(true);    
    if (fds.getNext())
        info("Multiple records selected");
    else
        info("1 or 0 records selected");

    // Enabling/Disabling button in one line
    myButton.enabled(SalesTable_ds.recordsMarked().lastIndex() > 1);

And how to enable/disable a button in one line:

    // Enabling/Disabling button in one line
    myButton.enabled(SalesTable_ds.recordsMarked().lastIndex() > 1);

Thursday, December 3, 2015

Some notes on Dynamics AX 2012 and the registry


  • [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AOS60$01]
    • Windows service configurations
  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Dynamics Server
    • AOS Server(s) configurations
  • HKEY_CURRENT_USER\Software\Microsoft\Dynamics\
    • When a user opens the client config, this registry key is created and it stores their configurations which take priority over HKLM
  • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Dynamics
    • These are the client config settings for the machine that are used when HKCU settings aren't present.

Tuesday, November 10, 2015

CacheDataMethod (AX2012 feature) property vs CacheAddMethod

A someone little known feature of AX 2012 is a new property called "CacheDataMethod".

Previously, when you wanted to improve performance by caching your display/edit methods, you would place a line of code like this after the super() in the datasource's init() method:

this.cacheAddMethod(tableMethodStr(CustTable, MyDisplayMethod));

In AX 2012, you can just change the "CacheDataMethod" property on the object without adding code.  I prefer this method where possible mainly because it keeps everything packaged together and I don't have to intermingle my code with base code.


Monday, November 9, 2015

The better way to pass containers between objects/forms using the Args() class, and not by converting to a string

If you need to pass a container between objects/forms using the Args class, don't convert it to a string and then back, use parmObject() and ContainerClass()!  I see many suggestions about converting it to a string, which can have much more unpredictable results and is not as versatile.

You wrap your container in the class ContainerClass() and then unwrap it at the other end.

Make your call like this:
args.parmObject(new ContainerClass(["Real container", 1234, "Not con2str container"]));
And retrieve it like this:
containerClass = element.args().parmObject() as ContainerClass;
myContainer = containerClass.value();

To test this, create a form (Form1) and overwrite the init method and put in this code:

public void init()
{
    ContainerClass      containerClass;
    container           conValue;
    
    if (!(element.args() && element.args().parmObject() && element.args().parmObject() is ContainerClass))
        throw error("@SYS22539");
    
    super();
    
    containerClass = element.args().parmObject() as ContainerClass;
    conValue = containerClass.value();
    
    info(strFmt("The container contains '%1'", con2Str(conValue)));
}

Then create a Job and put in this code:

static void JobForm1(Args _args)
{
    Args        args;
    FormRun     formRun;
    
    args = new Args();
    args.name(formStr(Form1));
    args.parmObject(new ContainerClass(['Real containers', 1234, 'Not con2str containers']));
    
    formRun = classFactory.formRunClass(args);
    formRun.init();
    formRun.run();
    formRun.wait();
}

And then run the job!