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')
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.
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.
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));
}
}
It is a common problem that developers create the objects and code in the shortest time possible to meet a deadline, and that is when we meet with the hardcoded label problem, which is a Best Practice violation. Our environment had about 6000 hardcoded label values as well, and I was looking for a general solution to fix them, which resulted in creating a new tool.
This tool is heavily beta version, comes with no warranties, use it at your own risk.
The hardcoded label fix tool is version control compatible, and you should really use it through that, in case you would like to roll back a set of incorrect modifications easily.
Once you open the form, the Scan button will process your entire AOT on the current layer and model and stores TreeNode path, type, text and possible label matches. Processing can take up to several hours.
Then you could multi-select labels and press Commit to replace the values. For blank entries it tries to create a new label in the currently selected (must be checked out in advance!) label file and language. If there are multiple hits for the text, it is marked by 3 asterisks, and you need to pick one on the right pane to use. In case you want to have your own labels created anyway, you could erase the recommended @SYS* or whatever entries, save the record then commit for a new one.
Please note that due to the nature of AX DB being Case Insensitive, it is possible that a label is recommended which has a different case, like you get label for ‘a’ instead of ‘A’.
The project can be downloaded from the DAXRunBase GitHub.
We recently have implemented a new Picking application in our warehouse. Our slow performing statement e-mail has immediately highlighted that with our data volume the WMSOrder and SalesTable records were underperforming, taking 5+ seconds each time when the form was opened (close to 20). We needed to fix the Picking list registration performance.
WMSOrder
Apparently it is a standard AX form and table, but the link which they have been using is incorrectly defined, resulting in an InnerJoin for SalesTable unnecessarily.
Picking list registration performance
Also the field used for the join has no index, you should create one for WMSOrder.InventTransRefId to gain more performance.
After the adjustment it looks much better and performs really fast:
The main communication channel for our ERP users in case we want to tell them something is via the InfoLog messages within Microsoft Dynamics AX. In case we get an error or a warning, the technical staff does not receive the details required to troubleshoot the issue much easier. In this post I would like to show you how to send the X++ or .Net CIL call stack to InfoLog messages. Microsoft already has an article on this for AX 2009, but that was before the AX 2012 IL code execution for server-side code, so it needs slight adjustments.
Users only see the call stack when clicking the message line due to filling it with whitespace.
Specific users could be excluded from receiving the call stack, we are doing this for our service accounts such as the batch execution account. We are also excluding our eCommerce portal customers from seeing a call stack, which are the Claims-based users.
Current database server/name and user account is included in the message, in case we are storing the errors in the Event log and would like to know who had the problem.