A while back, I asked a question on the D365FO forums about being able to generate a list of custom objects within an environment at runtime via X++. I never followed up on my answer there so I wanted to give a detailed response to how I ended up solving this.
Before We Begin
In the discussion on my initial question post it was brought up that you should be using a source control system (TFS, Git, etc) to generates reports like this. I absolutely still agree that this is the best way to generate reports like this. Unfortunately as many of us know, the ‘best’ way to perform something isn’t always possible within business. The use case for me initially was that internal/external auditors and some advanced functional business users wanted to be able to generate this report without having to rely on developer and/or DevOp teams.
As a side note, any code I share below will be written in C# and not X++, since the Microsoft libraries being utilized can be consumed from C# or X++ converting these should be fairly straightforward if needed.
As I started my process of solving this problem I ran into an issue, I could get the objects that pertained to a particular model but there was no good way to determine if that object was custom or not. But then I remembered that D365FO does not allow for over-layering of Microsoft objects. Which means that all custom objects must be within models that exist in a model/deployable package that was installed by the end user.
In D365FO, while the idea of a development ‘layer’ does still exist it is not utilized in the same way as it was in AX 2012. The extension development model in D365FO instead of over-layering that existed in AX 2012 we don’t have the scenario where an object exists in one layer and is completely overwritten in another layer. Looking at the Microsoft documentation we can see the different layers that exist. Because the layers are hierarchy based and we know the lowest layer we can introduce new code into is the SLN layer, if we determine what layer the SLN layer is we can use this to determine which models exist in this layer and above and then determine which objects exist in these models. These objects would then be the custom objects in our environment.
So using the above we utilize the following points to determine our custom objects:
– End users can only ‘extend’ objects in D365FO and cannot over-layer like they could in AX 2012
– Development layers exist in D365FO and are still hierarchy based
– End users can only create objects in the SLN layer and above
– Because the above points are true, then if we determine all development models that exist in the SLN layer or above and then determine the objects that exist in those models we can then generate a report to show all custom objects in a D365FO environment
Get Custom Models
First we need to be able to get all current models in an environment, we can utilize the Metadata Support libraries in D365FO (these are found in the Microsoft.Dynamics.Ax.Xpp library):
var moduleList = MetadataSupport.GetInstalledModuleInfo();
Through some testing I found that models created in the SLN layer had a layer parameter of 7 but for my use case I wanted to just get code added at the ISV layer and above. The ISV layer had a layer parameter of 8. So we then can apply some LINQ to remove any Microsoft created models and just focus on those created in the SLN, ISV, VAR, CUS, or USR layers.
var customModels = moduleList.Where(m => m.Layer >= 8 && m.Publisher != "Microsoft" && m.Publisher != "Microsoft Corporation" && m.Publisher != "Microsoft Corporation Internal") .Select(cm => cm.Name);
Creating the Metadata Provider
Now that we have a listing of custom models, we need to get the objects that are contained in those models. This process requires us to create a MetadataProvider. There is not much documentation surrounding this process but I was able to find a blog from a huge AX community contributor Ievgen Miroshnikov. In this blog post, he provides a template for how to create and interact with the MetadataProvider interface. Using this I was able to create a helper class that would allow me to create any type of MetadataProvider:
The type we will be utilizing in this blog post will be ‘Runtime’.
Now we can actually get the objects from the MetadataProvider by providing the metadata type (Runtime) and a list of modules we want to get objects for (the custom models from above). In the example below I am just getting the menu item displays from the models provided.
This process would also work for the following object types:
- Tables (Table Extensions)
- Data Entities (Data Entity Extensions)
- Forms (Form Extensions)
- Menu Item Displays (Menu Item Display Extensions)
- Menu Item Actions (Menu Item Action Extensions)
- Menu Item Outputs (Menu Item Output Extensions)
- Service Groups
- Roles (Role Extensions)
- Duty (Duty Extensions)
- Privilege (Privilege Extensions)
- Security Policies
- Along with other object types
Using the above I am able to create a report like this for our Fastpath Assure platform: