Pages

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/).