We are preparing for the code upgrade from AX 2012 R3 to D365FO 8.1, the latest version recently released. As you know it is extensions-only, meaning we are not allowed to modify any of the standard objects directly. I would strongly suggest to familiarize yourself with Extensions if you are planning to go down this journey as well. An important part of this is that you need to send in Extensibility requests via LifeCycle Services, for code that cannot be moved to a pre- / post-event handler. Our large number of customized methods came out well above 1000+ overlayered objects when we ran the LCS code upgrade tool on our modelstore. We had to find standard code overlayering of all methods to narrow down the search on what should be inspected for extensibility.
I have been exploring the options to use the MetaData API for extracting package information of methods. We wanted to find any methods, which belongs to both a standard, and a custom package, like ApplicationPlatfrom and ApplicationPlatform.JAD (our module). That did not lead to anything useful other than realizing that you can only access such details up to the root node level (table, class) currently. Here is the code piece for partial results:
using System; using System.Linq; using Microsoft.Dynamics.AX.Metadata.Storage; using Microsoft.Dynamics.AX.Metadata.Providers; using Microsoft.Dynamics.AX.Metadata.MetaModel; using Microsoft.Dynamics.Framework.Tools.MetaModel.Core; namespace WIK_ScanForExtensibility { class Program { static void Main(string[] args) { var environment = Microsoft.Dynamics.ApplicationPlatform.Environment.EnvironmentFactory.GetApplicationEnvironment(); var packagesLocalDirectory = environment.Aos.PackageDirectory; IMetadataProvider diskMetadataProvider = new MetadataProviderFactory().CreateDiskProvider(packagesLocalDirectory); var l = diskMetadataProvider.Tables.ListObjects("ApplicationPlatform"); var le = l.GetEnumerator(); while (le.MoveNext()) { AxTable t = diskMetadataProvider.Tables.Read(le.Current); var mi = DesignMetaModelService.Instance.CurrentMetadataProvider.Tables.GetModelInfo(t.Name); Console.Write("Table " + t.Name + " models: "); for (int j = 0; mi != null && j < mi.Count; j++) { var bla = mi.ElementAt<ModelInfo>(j); Console.Write(bla.Name + " "); } Console.WriteLine(); Microsoft.Dynamics.AX.Metadata.Core.Collections.IKeyedObjectCollection sen = t.Methods; for (int i = 0; sen != null && i < sen.Count; i++) { // Find methods which contain our prefix in its' name Microsoft.Dynamics.AX.Metadata.MetaModel.AxMethod method = (Microsoft.Dynamics.AX.Metadata.MetaModel.AxMethod)sen.getObject(i); if (method.Name.Contains("JAD")) { Console.WriteLine(); Console.WriteLine("\\Data Dictionary\\Tables\\" + t.Name + "\\Methods\\" + method.Name /*+ treeNode.AOTLayers().toString()*/); } } //Console.Write("."); } Console.WriteLine(); Console.WriteLine("------- Press any key -------"); Console.ReadKey(); } } }
We also have found out that the code upgrade tool commits your release in a way that conflicts of your customization end up in a subfolder called Delta per object type. So our changed classes for the platform would be in K:\AosService\PackagesLocal\ApplicationPlatform.JAD\AxClass\Delta folder. Here the Application.XML file contains the changes for the Application class, where we have overlayered 4 methods, and added a couple of new ones. This is clearly visible within the XML source file, here is a shortened version of the important parts:
<?xml version="1.0" encoding="utf-8"?> <AxClassDelta xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <PathElement> <Path>Application</Path> </PathElement> <Conflicts> <AxConflict xmlns="" i:type="AxSourcePropertyConflict"> <Path>dynamics://Class/Application/Method/dbSynchronize?Source</Path> </AxConflict> </Conflicts> <DeltaName>Application</DeltaName> <Methods> <PathElement> <Path>Methods</Path> </PathElement> <Added> <NodeAddedDeltaOfAxMethodNcCATIYq> <DeltaName>JAD_shutdown</DeltaName> </NodeAddedDeltaOfAxMethodNcCATIYq> <NodeAddedDeltaOfAxMethodNcCATIYq> <DeltaName>JAD_startupPost</DeltaName> </NodeAddedDeltaOfAxMethodNcCATIYq> <NodeAddedDeltaOfAxMethodNcCATIYq> <DeltaName>JAD_validateKernelVersion</DeltaName> </NodeAddedDeltaOfAxMethodNcCATIYq> <NodeAddedDeltaOfAxMethodNcCATIYq> <DeltaName>JAD_validateMaintenanceMode</DeltaName> </NodeAddedDeltaOfAxMethodNcCATIYq> </Added> <Changed> <NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <Value> <DeltaName>dbSynchronize</DeltaName> </Value> </NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <Value> <DeltaName>logDelete</DeltaName> </Value> </NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <Value> <DeltaName>logInsert</DeltaName> </Value> </NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> <Value> <DeltaName>logUpdate</DeltaName> </Value> </NamedCollectionChangedDeltaOfAxMethodDeltaNcCATIYq> </Changed> <Deleted /> </Methods> </AxClassDelta>
If you open this XML file with Visual Studio, the D365FO VS extension recognizes the content and automagically formats it accordingly as per below. It means that the add-in has this scanning capability, but I have not been able to correctly use the assemblies involved in the short amount of time.
During the technical conference couple of years back Peter Villadsen has mentioned a great tool called BaseX for processing these XML files in bulk. I have decided to give it a try, and have built a database from the entire PackagesLocal folder. During DB creation you should select Skip corrupt (non-well-formatted) files, untick the Parse files in archives (to ignore any $tf VSTS workspace folders), and in Parsing select Use internal XML parser for best results.
I have built the following XPath / XQuery to find anything which is under the Changed node within the Delta folder:
declare namespace functx = "http://www.functx.com"; declare function functx:wrap-values-in-elements ( $values as xs:anyAtomicType* , $elementName as xs:QName ) as element()* { for $value in $values return element {$elementName} {$value} } ; declare option output:omit-xml-declaration "no"; <Results> { for $XMLnode in AxClassDelta | AxTableDelta order by fn:base-uri($XMLnode), $XMLnode/local-name(.), $XMLnode/PathElement/Path/text() ascending (:where $XMLnode/PathElement/Path='Application' or $XMLnode/PathElement/Path='InventTable':) where fn:not(fn:empty($XMLnode//Methods/Changed//DeltaName)) (: or fn:not(fn:empty($XMLnode//Methods/Deleted//DeltaName)) or fn:not(fn:empty($XMLnode//Methods/Added//DeltaName)) :) return <TreeNode FilePath='{fn:base-uri($XMLnode)}' AOTRoot='{$XMLnode/local-name(.)}' AOTName='{$XMLnode/PathElement/Path/text()}' > { for $method in $XMLnode//Methods return <AOTNode Type='{$method/local-name(.)}'> <Changed> {functx:wrap-values-in-elements( $method/Changed//DeltaName, xs:QName('AOTNode'))} </Changed> </AOTNode> } </TreeNode> } </Results>
This script has returned all customized table and class methods, a total of 214 potential overlayered entries. We can include additional nodes such as maps, form methods, enums and data types for entries different from methods as well easily. The generated results are in XML format, which you could open directly with Excel in a neatly formatted way:
Now we can go through a more manageable list of methods to send in for extensibility requests!
If you liked the idea of using XQuery to find standard code overlayering, have a look at these posts as well to get an idea on additional usage:
http://www.agermark.com/2017/11/xquery-to-use-with-basex-to-find.html
http://dynamicsnavax.blogspot.com/2016/03/dynamics-ax-7-searching-using-basex.html