AX 2012

Investigating Workflow security issues

Many of us might have encountered the below error messages when trying to run a newly created workflow. I would like to provide this neat troubleshooting tip with the below code snippet for investigating workflow security issues.

Stopped (error): X++ Exception: Work item could not be created. 
Insufficient rights for user %1.

In most of the cases it is due to the lack of security access for the execution account on a menu item, for which we could add a couple of lines of code to resolve the problem.

Change \AOT\Classes\SysWorfklowDocument\assertAsUser method and for each fork of menu item type you may add the entry point in a security privilege or role, for which you need to provide access for on the user account you have been using. That means for each section of workflowPermission you need to add the appropriate menu item in the error log.

    if (workflowPermission.parmDisplayMenuItem())
    {
        permission = SysWorkflowDocument::assertMenuItem(workflowPermission.parmDisplayMenuItem(), MenuItemType::Display);
        if (!permission)
        {
            error(strFmt('Entry point %1', workflowPermission.parmDisplayMenuItem()));
            return [permission, buf2Con(rec)]; // buf2Con() needed to support packing buffers participating in SC/sc hierarchies
        }
    }

Also you need to add the following pieces to \AOT\Classes\SysWorkflowEventDispatcher\onWorkItemCreate() method:

    if (workflowPermission.parmDisplayMenuItem())
    {
        permission = SysWorkflowDocument::assertMenuItem(workflowPermission.parmDisplayMenuItem(), MenuItemType::Display);
        if (!permission)
        {
            error(strFmt('Entry point %1', workflowPermission.parmDisplayMenuItem()));
            return [permission, buf2Con(rec)]; // buf2Con() needed to support packing buffers participating in SC/sc hierarchies
        }
    }

(...)
 
    if (!SysWorkflowHelper::userHasPermission(
        _workItemContext.parmWorkflowCorrelation().parmWorkflowContext(),
        stepTable.workflowElementTable().workflowVersionTable().workflowTable().TemplateName,
        sysDictWorkflowElement.actionMenuItem(completingOutcome),
        menuItemName,
        user))
    {
        error(strFmt('Entry point %1 / Menu item action %2', menuItemName, sysDictWorkflowElement.actionMenuItem(completingOutcome)));
        throw error(strfmt("@SYS109561", user));
    }

Using the above changes you will be able to set up the missing security pieces easily by taking a look at the workflow execution history, where the infolog in the tracking history will contain the entry points causing the issues.

External references for the same issue:

https://workflowax.wordpress.com/2012/05/02/x-exception-workitem-could-not-be-created/

http://axfaq.blogspot.hu/2014/08/workflow-ax-2012-insufficient-rights.html

By |2017-05-18T12:18:42+02:00May 18th, 2017|Categories: AX 2012|Tags: , , |0 Comments

Refresh AX grid color with displayOption

We have a displayOption implemented for the Vendor transactions screen, which highlights the rows in red color where the posted transactions or the journal has a Document attachment. The requirement was that if we create a new DocuRef entry on the vendor transaction, after closing the DocuView form the grid should change color. In order to refresh AX grid color with displayOption only for the currently selected row we can use the FormDataSource.clearDisplayOption method.

Also there is a great comment on the Dynamics Community about refreshing a set of entries, instead of the current cursor:

https://community.dynamics.com/ax/f/33/t/191245

We have edited \Forms\DocuView\Methods\close as per below to achieve refreshing only the currently highlighted row:

public void close()
{
    FormRun         frm = infolog.parmLastActivatedForm().object();
    FormDataSource  fds;
 
    DocuRef::multiSelectRecordDelete();
 
    SysHelp::initWebBrowser(htmlView);
    curUrl = "";
 
    super();
 
    if (infolog.docu() && infolog.docu().docuView() && infolog.docu().docuView().object())
    {
        infolog.docu().clearDocuView(); //ensure that form is removed from infolog object
    }
 
    if (frm
        && frm.form().name() == formStr(VendTrans))
    {
        fds = frm.dataSource(1);
        fds.clearDisplayOption(fds.cursor());
    }
}
By |2017-02-20T13:32:54+01:00February 20th, 2017|Categories: AX 2012|Tags: , , , , |0 Comments

AX 2012 R3 Cumulative Update 12 is available

Microsoft has announced availability for AX 2012 R3 Cumulative Update 12 recently, and just became available for download today. You may find the announcement with the What’s new links here:

https://blogs.technet.microsoft.com/dynamicsaxse/2016/11/21/announcing-cumulative-update-12-for-microsoft-dynamics-ax-2012-r3/

According the the Build numbers website the kernel has version 6.3.5000.138, however a newer build is already available:

https://blogs.msdn.microsoft.com/axsupport/2012/03/29/overview-of-microsoft-dynamics-ax-build-numbers/

 

 

By |2017-09-12T08:02:19+02:00November 22nd, 2016|Categories: AX 2012|Tags: , , |1 Comment

Add AX FormGroupControl runtime

Apparently you cannot populate a form data group runtime in AX. I have been able to overcome the issue by adding the controls dynamically. In this example I have created a form with InventTable as the datasource, and added a FormGroupControl called FormDataGroup with AutoDeclare = Yes. Then I have overridden the executeQuery() on InventTable_ds to call my custom method stored on the form. Here is the source to add AX FormGroupControl runtime from code:

public void WIK_addFieldGroup()
{
    #AOT
    #Properties

    TreeNode            fieldGroupRoot = TreeNode::findNode(strFmt('%1\\%2\\Field Groups\\%3',
        #TablesPath,
        tableStr(InventTable),
        tableFieldgroupStr(InventTable, Name)));
    TreeNodeIterator    tni = fieldGroupRoot.AOTiterator();
    TreeNode            fieldGroupNode = tni ? tni.next() : null;
    int                 fieldNo;
    DictField           dictField;
    FormControlType     controlType;
    Object              formControl;
        
    formDataGroup.hideIfEmpty(false);
    formDataGroup.caption(fieldGroupRoot.AOTgetProperty(#PropertyLabel));
    
    while (fieldGroupNode)
    {
        fieldNo = (select firstOnly AxId from SysModelElement
            where  SysModelElement.ParentId     == tableNum(InventTable)
                && SysModelElement.ElementType  == UtilElementType::TableField
                && SysModelElement.Name         == fieldGroupNode.AOTname()).AxId;
        
        dictField = new DictField(tableNum(InventTable), fieldNo);
        
        if (!dictField)
        {
            // display method in field group
            fieldGroupNode = tni.next();
            continue;
        }
        
        switch (dictField.baseType())
        {
            case Types::Int64 :
                controlType = FormControlType::Int64;
                break;
            case Types::Integer :
                controlType = FormControlType::Integer;
                break;
            case Types::String :
                controlType = FormControlType::String;
                break;
            case Types::Date :
                controlType = FormControlType::Date;
                break;
            case Types::UtcDateTime :
                controlType = FormControlType::DateTime;
                break;
                
            //TODO: Implement all field types
        }

        formControl = formdatagroup.addControl(controlType, strFmt('InventTable_%1', fieldGroupNode.AOTname()));
        formControl.dataSource(InventTable_ds.id());
        formControl.dataField(fieldNo);
        formControl.label(dictField.label());

        fieldGroupNode = tni.next();
    }
}

 

By |2016-09-21T12:20:54+02:00September 21st, 2016|Categories: AX 2012|Tags: , , , , |3 Comments
Go to Top