Pages

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!


Wednesday, October 28, 2015

How to create a self-elevating PowerShell script that will run as administrator every time

Often there are various build processes or other automated tasks that run via PowerShell and need to be run as administrator.  If you forget to run it as administrator, it won't work, and you don't always know.

I came across a great blog post by Ben Armstrong that I have to share, where he's created a block of code you just prefix to the beginning of your PowerShell script that will re-launch it as administrator if it is not.  Check his post out here:

http://blogs.msdn.com/b/virtual_pc_guy/archive/2010/09/23/a-self-elevating-powershell-script.aspx

Here's a screenshot of the PowerShell code in case his blog goes down:

Tuesday, October 13, 2015

Without any customization, how to incrementally compile the CIL from the command line

With Dynamics AX 2012, you can start an incremental CIL compile from the command line (or Power Shell) without any customization.  This is useful if you have any automated processes that import XPOs frequently and you don't want to constantly build the full CIL.

Create an XML file with this data:


<?xml version="1.0" ?>
<AxaptaAutoRun  
    exitWhenDone="true"  
    version="6.2"  
    logFile="C:\AxaptaAutorun.log"> 
 <CompileIL incremental="true" />
</AxaptaAutoRun>

Then save it in a place that is accessible from the AOS service account.  I saved it as "C:\IncrementalCIL.xml" on the AOS machine.

Then run this command, subbing in for your environment:

"C:\Program Files (x86)\Microsoft Dynamics AX\60\Client\Bin\Ax32.exe" \\MyNetworkShare\AOS.axc -startupcmd=autorun_C:\IncrementalCIL.xml

Some notes about the XML.  The version attribute must not be greater than your system's build, which can be found from calling xInfo::releaseVersion().

There are a TON more autorun features available, and you can use the following links or just dig into \Classes\SysAutoRun.

More information can be found at:





Thursday, September 10, 2015

How to export all private or shared projects with or without project definitions from a specified layer

I often have to switch development machines, and when I do, I lose all of my private or shared projects.  This is a good way to backup your projects and/or their definitions.


static void AKExportProjects(Args _args)
{
    #AotExport
    TreeNodeIterator        tni;
    ProjectNode             projectNode;
    int                     exportFlag;
    Dialog                  dialog = new Dialog();
    DialogField             folderName;
    DialogField             projectDefinitionOnly;
    DialogField             exportFromLayer;
    DialogField             projectType;
    UtilEntryLevel          layer;

    dialog.addText("This will export all projects (shared or private) that exist in a selected model.");
    projectType             = dialog.addFieldValue(enumStr(ProjectSharedPrivate), ProjectSharedPrivate::ProjPrivate);
    projectDefinitionOnly   = dialog.addField(extendedTypeStr(NoYesId), 'Project Definition Only');
    folderName              = dialog.addField(extendedTypeStr(FilePath));
    exportFromLayer         = dialog.addField(enumStr(UtilEntryLevel), 'Projects from layer');

    dialog.run();

    if (dialog.closedOk())
    {
        if (!folderName.value())
            throw error("Missing folder");

        exportFlag = #export;
        if (projectDefinitionOnly.value())
            exportFlag += #expProjectOnly;

        layer = exportFromLayer.value();
        
        switch (projectType.value())
        {
            case ProjectSharedPrivate::ProjPrivate:
                tni = SysTreeNode::getPrivateProject().AOTiterator();
                break;

            case ProjectSharedPrivate::ProjShared:
                tni = SysTreeNode::getSharedProject().AOTiterator();
                break;

        }
        
        projectNode = tni.next() as ProjectNode;
        
        while (projectNode)
        {
            if (projectNode.AOTLayer() == layer)
                projectNode.treeNodeExport(folderName.value() + '\\' + projectNode.name() + '.xpo', exportFlag);

            projectNode = tni.next() as ProjectNode;
        }
    }
    else
        warning("No action taken...");
}

Monday, August 3, 2015

How to check if a Base Enum has a valid value

Base enums can use integer assignment to be set, but you can set it 0 to any positive valid integer up to 255 inclusive, and that does not mean it's a valid enum.

Take for example the base enum "ABC" (\Data Dictionary\Base Enums\ABC).  You can assign ABC=555, and it will store an integer value of 255 with no issue.

To check if an enum value is valid, you can use this method:

static boolean checkABCEnum(ABC _abc)
{
    return new DictEnum(enumNum(ABC)).value2Symbol(_abc));
}

Here is a sample job that will demonstrate how this can be an issue:

static void CheckIfEnumIsValid(Args _args)
{
    // Possible enum values 0, 1, 2, 3
    ABC         abcValid, abcInvalid;
        
    // Valid enum
    abcValid = ABC::C;
    info(strFmt("%1, %2", enum2int(abcValid), abcValid));
    
    // Invalid enum, but integer assignment works and is stored
    abcInvalid = 555;
    info(strFmt("%1, %2", enum2int(abcInvalid), abcInvalid));
    
    if(new DictEnum(enumNum(ABC)).value2Symbol(abcValid))
        info(strFmt("Enum with type %1 and integer value %2 (%3) is valid", typeOf(abcValid), enum2int(abcValid), abcValid));
    else
        error(strFmt("Enum with type %1 and value %2 is invalid", typeOf(abcValid), enum2int(abcValid)));
    
    if(new DictEnum(enumNum(ABC)).value2Symbol(abcInvalid))
        info(strFmt("Enum with type %1 and integer value %2 (%3) is valid", typeOf(abcInvalid), enum2int(abcInvalid), abcInvalid));
    else
        error(strFmt("Enum with type %1 and value %2 is invalid", typeOf(abcInvalid), enum2int(abcInvalid)));
    
    /*
        Output:
        3, C
        4, 
        Enum with type Enum and integer value 3 (C) is valid
        Enum with type Enum and value 4 is invalid
    */
}