Blog

24 09, 2018

Microsoft Ignite 2018 day 1 recap

By |2018-09-24T19:07:48+02:00September 24th, 2018|Categories: Dynamics 365 for Finance and Operations|Tags: , , , , , , , , |0 Comments

Today Microsoft Ignite 2018 has kicked off with a lot of exciting news.
You may view sessions and on-demand videos on the main site:

Microsoft Ignite 2018

One of the most important content is the fall release documentation referred as 2018 October Release Notes, available on the Docs site

We can use new, more advanced and modern capabilities of the Power platform. Microsoft is also introducing AI to many Dynamics 365 products.

We can access a new library of training courses on a portal called Microsoft Learn. Here we get free learning opportunity for tools like PowerApps, Flow and Common Data Service.

The highlight which I am very excited about now is the soon-to-be-released tool called Regression Test Suite already mentioned in the release documentation. It was one of our requests during a workshop with Microsoft to improve capabilities on automating our manual user interactions. Every Friday after our regular maintenance we go through the same set of tasks for validating stability. This is to ensure that there are no disruptions to our business operation due to deploying code. This tool could be the answer to that by creating chains of small operational tasks, then running the entire test suite containing those test cases and validating the results in an automated fashion.

Microsoft is also improving the technical areas with further updates:

I would highly recommend to take your time and go through the wealthy content and videos shared as part of Microsoft Ignite 2018.

12 09, 2018

How to force running jobs server-side

By |2020-03-23T13:24:54+01:00September 12th, 2018|Categories: AX 2012|Tags: , , , |2 Comments

We frequently write quick-fix jobs, or even some quite heavy ones which are bound to execute client-side by default. The workaround is to create an Action MenuItem pointing at the job, and setting RunOn to be Server. Then next time we need to use the code we forget about that, and it takes awfully lot to complete execution. We need to force running jobs server-side in X++ somehow, for which I do have a neat solution.

We could check if the job is running on a client or on the AOS. Then we can call the menu function to force open it server-side from code.

    // Place validation at the beginning of a job that is supposed to be server-bound
    if (!isRunningOnServer())
    {
        new MenuFunction(menuitemActionStr(YourJobActionMenuItem), MenuItemType::Action).run();
        return;
    }

Here is a quick example showing it in action:

Running Jobs server-side

This will guarantee faster execution times for database calls and resource-intensive processes, granting permissions required to be server-bound, or code that relies on reflection such as traversing AOT. We do not need to worry about remembering whether it should run on the client or not, or do not have to find the correct menu item manually. It just works!

11 09, 2018

Change AOT objects without AX client

By |2020-03-23T13:37:18+01:00September 11th, 2018|Categories: AX 2012|Tags: , , , , , |0 Comments

During the #MSDyn365FO upgrade code cleanup exercise for Best Practice errors one of our forms got corrupted. Compilation has caused a crash, and when I tried to reopen it then it went in an endless compile loop. I had to find a way to change AOT objects without AX client.

This could be achieved by various different solutions. One option is using the SysStartupCmd framework to import a corrected XPO with the AOTimportFile startup command. Also you could try removing the objects from the ModelElement and ModelElementData tables within the AX2012_model ModelStore DB. Another solution is to go around using the client.

I went with the last option, and used a client-less approach via talking directly to the Application Object Server through the Business Connector interface. Here is a simple PowerShell script I have implemented that uses reflection for the AOT elements, where I could access a SysTreeNode object and then delete it:

# Instantiate Business Connector proxy object and sign on
Add-Type -Path "C:\Program Files\Microsoft Dynamics AX\60\BusinessConnector\Bin\Microsoft.Dynamics.BusinessConnectorNet.dll"
$ax = new-object Microsoft.Dynamics.BusinessConnectorNet.Axapta
$ax.logon('','','','','','')

# List commands
$ax | Get-Member

# Get the incorrect form object and delete the treenode
$node = $ax.CallStaticClassMethod('SysTreeNode', 'newTreeNodePath', '\Forms\CCMOrderPadActivityMK2')
$node.Call('name')
$node.Call('delete')
Change AOT objects without AX client using Business Connector in PowerShell

This is how you could change AOT objects without AX client in a fast, safe and easy way. BC is still a very powerful way of running code on-the-fly. A similar approach was applied when we wanted to validate if AIF ports were up and running on our AOS instances earlier.

29 06, 2018

GDPR tool for Microsoft Dynamics AX 2012

By |2020-03-23T13:37:46+01:00June 29th, 2018|Categories: AX 2012|Tags: , , , , |4 Comments

The European Union has introduced strict data protection rules last month, for which companies had to become legally compliant to avoid fines. We have a set of patches to apply to get a GDPR tool for Microsoft Dynamics AX 2012, which has been released to assist us:

  • KB4056903 Privacy Policy update
  • KB4074643 DAPIA Security tool
  • KB4057507 SQM Data collection

The part relevant for us is the tool, which allows capturing which interactive users have logged on to AX, who are using a security role that may access sensitive information.

GDPR tool for Microsoft Dynamics AX 2012
User log for roles with sensitive information access
GDPR tool for Microsoft Dynamics AX 2012
Setup of roles with sensitive data access

Unfortunately Microsoft only provides a high-level guideline on what shall be included and provides very little tangible assistance. Due to this I have felt we needed some way to identify what security roles could really be accessing sensitive data, so I came up with an X++ job that does exactly this. You may pass in menu items for forms, reports and also tables that may access details such as Customers, Global Address Book, Vendors, Address and Contact details. The tool is using the Security framework to determine which roles can edit such data, but you may change filter criteria to also include View access.

static void WIK_GDPR_enable_roles(Args _args)
{
    #AOT
    
    // List of tables which might contain sensitive data
    container           tables = [
        [menuitemDisplayStr(CustTable), UtilElementType::DisplayTool]
        ,[menuitemDisplayStr(CustTableListPage), UtilElementType::DisplayTool]
        ,[menuitemDisplayStr(CustTableEdit), UtilElementType::DisplayTool]
        ,[menuitemDisplayStr(CustTableDetails), UtilElementType::DisplayTool]
        ,[menuitemDisplayStr(GlobalAddressBookListPage), UtilElementType::DisplayTool]
        ,[menuitemDisplayStr(DirPartyTable), UtilElementType::DisplayTool]
        ,[menuitemDisplayStr(DirPartyTableEdit), UtilElementType::DisplayTool]
        ];
    
    // Replace role settings?
    boolean                 update = NoYes::Yes;
    
    UtilElementType         objectType;
    str                     objectName;
    int                     i = 1;
    SysSecFlatDataTable     objects;
    SysSecFlatDataTable     allObjects;
    SysUserLogRoleSettings  roleSettings;
    SecurityRole            securityRole;
    
    allObjects.setTmp();
    
    while (i <= conLen(tables))
    {
        objectName = conPeek(conPeek(tables, i), 1);
        objectType = conPeek(conPeek(tables, i), 2);
        
        switch (objectType)
        {
            // Implemented from \Forms\SysSecObjectsInRole\init
            case UtilElementType::DisplayTool:
                SysSecObjectsFromEntryPoint::GenerateData(
                    SysSecObjectsAnalyzeType::SecViewRelatedRoles,
                    objectName,
                    enum2int(objectType));
                break;
                
            case UtilElementType::OutputTool:
                SysSecObjectsFromEntryPoint::GenerateData(
                    SysSecObjectsAnalyzeType::SecViewRelatedRoles,
                    objectName,
                    enum2int(objectType));
                break;
                
            case UtilElementType::ActionTool:
                SysSecObjectsFromEntryPoint::GenerateData(
                    SysSecObjectsAnalyzeType::SecViewRelatedRoles,
                    objectName,
                    enum2int(objectType));
                break;
                
            case UtilElementType::Table:
                SysSecObjectsFromSecurableObject::GenerateData(
                    objectName,
                    enum2int(objectType));
                break;
        }
            
        while select objects
        {
            allObjects.clear();
            buf2Buf(objects, allObjects);
            allObjects.doInsert();
        }
        
        i++;
    }
 
    if (update)
    {
        i = 0;
        ttsBegin;
        
        update_recordSet roleSettings
            setting HasAccessToSensitiveData = NoYes::No;
        
        // No join for Tmp object, must use nested loop
        while select allObjects
            group by Role//, IsOverride
            where allObjects.IsOverride    == NoYes::No
                && ((allObjects.AccessRight != AccessRight::View && allObjects.AccessRight != AccessRight::NoAccess)
                    && (allObjects.EntryPointAccess !=  AccessRight::View && allObjects.EntryPointAccess != AccessRight::NoAccess))
        {   
            select firstOnly forUpdate roleSettings
                join RecId from securityRole
                    where  securityRole.AotName      == allObjects.Role
                        && roleSettings.SecurityRole == securityRole.RecId;
            
            if (roleSettings)
            {
                roleSettings.HasAccessToSensitiveData = NoYes::Yes;
                roleSettings.doUpdate();
                i++;
            }
        }
        
        ttsCommit;
        
        info(strFmt('%1 security roles have been updated', i));
    }
 
    while select Role, RoleName
        from allObjects
        group by RoleName, Role//, AccessRight, EntryPointAccess
        where allObjects.IsOverride     == NoYes::No
            && ((allObjects.AccessRight != AccessRight::View && allObjects.AccessRight != AccessRight::NoAccess)
                && (allObjects.EntryPointAccess !=  AccessRight::View && allObjects.EntryPointAccess != AccessRight::NoAccess))
    {
        info(strFmt('%1 (%2)', allObjects.Role, allObjects.RoleName));
    }
}

The XPO could be downloaded from GitHub.

https://github.com/DAXRunBase/AX-2012-R3/tree/master/GDPR%20security%20roles
Go to Top