Pages

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
    */
}

Wednesday, June 3, 2015

How to export/import your MorphX VCS settings and history

When using MorphX for version control, sometimes you need to restore a backup of a database, and you don't want to lose all of your check in/out history.  I wrote this job to export and import your MorphX VCS settings.

Use at your own risk, but it's worked fine for me.

Enjoy!


static void AKBackupMorphXVCData(Args _args)
{
    SysDataExport           sysDataExport;
    SysDataImport           sysDataImport;
    
    Dialog                  dialog = new Dialog();
    FormBuildRadioControl   fbImportExport;
    FormRadioControl        radioResults;
    
    dialog.addText("Warning, if you choose Import, this will replace your VCS data and is not reversible!");
    
    // Add the radio button, name it anything
    fbImportExport = dialog.formBuildDesign().addControl(FormControlType::RadioButton, 'RadioButton1');
    fbImportExport.caption("Choose Import/Export");
    fbImportExport.items(2); 

    fbImportExport.item(1);
    fbImportExport.text("Export");
    
    fbImportExport.item(2);
    fbImportExport.text("Import");

    dialog.doInit();
    dialog.formRun().design().moveControl(fbImportExport.id());
    dialog.run();

    if (dialog.closedOk())
    {
        radioResults = dialog.formRun().control(fbImportExport.id());
        
        if (radioResults.selection() == 0) // Export
        {
                sysDataExport = new SysDataExport();
                sysDataExport.parmDoNotBypassDefIO(true);
                sysDataExport.parmServerAccess(true);
                sysDataExport.addTmpExpImpTable(tableNum(SysVersionControlMorphXItemTable), false);
                sysDataExport.addTmpExpImpTable(tableNum(SysVersionControlMorphXLockTable), false);
                sysDataExport.addTmpExpImpTable(tableNum(SysVersionControlMorphXRevisionTable), false);
                sysDataExport.addTmpExpImpTable(tableNum(SysVersionControlParameters), false);
                sysDataExport.addTmpExpImpTable(tableNum(SysVersionControlSynchronizeLog), false);

                if (sysDataExport.prompt())
                {   
                    sysDataExport.parmFiletype(FileType::Binary);
                    sysDataExport.run();
                }
        }
        else if (radioResults.selection() == 1) // Import
        {
            sysDataImport = new SysDataImport();

            if (sysDataImport.prompt())
            {
                sysDataImport.parmLoadAll(true);
                sysDataImport.parmInclTablesNotPerComp(true);
                sysDataImport.parmFiletype(FileType::Binary);
                sysDataImport.run();

                versioncontrol.init();
            }
        }
        
        info("Done!");
    }
}

Dynamic dialog controls at runtime

This is a job that demonstrates how to dynamically add controls (specifically radio button) to a dialog at runtime and also change their position and access their values.

You can use this style in custom advanced UI builders.

static void AKDynamicDialogExample(Args _args)
{
    Dialog                  dialog = new Dialog();
    FormBuildRadioControl   fbRadioControl;
    FormRadioControl        radioControl;
    
    // Add the radio button, name it anything
    fbRadioControl = dialog.formBuildDesign().addControl(FormControlType::RadioButton, 'RadioButton1');

    // Set radio basic properties
    fbRadioControl.caption("Test Radio Buttons");
    fbRadioControl.items(2); // This is needed

    fbRadioControl.item(1); // Switch to first item
    fbRadioControl.text("Item 1"); // Set first item's text
    
    fbRadioControl.item(2); // Switch to second item
    fbRadioControl.text("Item 2"); // Set second item's text

    // This is needed to instantiate the FormRun
    dialog.doInit();

    // Just passing one argument moves it UP.
    // So this moves it UP above the "OK/Cancel" buttons created
    dialog.formRun().design().moveControl(fbRadioControl.id());

    dialog.run();

    if (dialog.closedOk())
    {
        // You need to access it from the formRun() with the correct
        // form control
        radioControl = dialog.formRun().control(fbRadioControl.id());
        info(strFmt("%1", radioControl.selection()));
    }
}