D365 JavaScript Web Resource Library Usage

It’s fairly straight forward to find which Forms use a Web Resource with the ‘Show Dependencies’ option, but that will list Forms where the JavaScript library has been added to the form in the Form Libraries. This doesn’t give you any indication whether the functions in the JavaScript file have actually been used in the form.

Obviously, you can scroll through each Tab and Field in the Event Handlers to look for occurrences of an event, but if you are doing maintenance on a form you didn’t originally develop then it could take a while to scroll though all the possible events. The Event Libraries and Event Handlers are stored in the formjson column of the systemform entity so with a simple FetchXML query you can get a list of Forms that actually use functions from a particular library. The example below will return all Forms where the msdyn_/Utils/head.js Web Resource has been used.The key part is the Filter Condition that is looking for a LibraryName that matches our JavaScript library. The formjson content contains an array of EventHandlers that define the EventName, FunctionName and LibraryName.

<fetch>
  <entity name='systemform' >
    <attribute name='name' />
    <attribute name='formjson' />
    <attribute name='formactivationstate' />
    <attribute name='type' />
    <attribute name='objecttypecode' />
    <filter>
      <condition attribute='formjson' operator='like' 
         value='%&quot;LibraryName&quot;:&quot;msdyn_/Utils/head.js%' />
    </filter>
  </entity>
</fetch>

The next step is to figure out which EventHandlers, if any, use that library in those forms. To do that I created a simple LINQPad script to return a list of the functions in each form.

void Main()
{
	string url = "https://myorgname.crm6.dynamics.com"; 
	string username = "my.user@myorgname.onmicrosoft.com";
	string password = Util.GetPassword("d365-admin");
	
	CrmServiceClient conn = new CrmServiceClient($"Url={url};Username={username};Password={password}; AuthType=Office365");

	conn.OrganizationServiceProxy.Timeout = new System.TimeSpan(0, 3, 0);
	
	conn.OrganizationServiceProxy.EnableProxyTypes();
	
	IOrganizationService orgService = conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy;

	string pagingCookie = null;
	
	EntityCollection formsCol = null;
	
	List<Entity> forms = new List<Microsoft.Xrm.Sdk.Entity>();
	
	do
	{
		string fetchXML = $@"<fetch>
		  <entity name='systemform' >
		    <attribute name='name' />
		    <attribute name='formjson' />
		    <attribute name='formactivationstate' />
		    <attribute name='type' />
		    <attribute name='objecttypecode' />
		    <filter>
		      <condition attribute='formjson' operator='like' value='%&quot;LibraryName&quot;:&quot;msdyn_/Utils/head.js%' />
		    </filter>
		  </entity>
		</fetch>";

		formsCol = orgService.RetrieveMultiple(new FetchExpression(fetchXML));
		
		if (formsCol.Entities.Count > 0)
		{
			forms.AddRange(formsCol.Entities);	
		}
		pagingCookie = formsCol.PagingCookie;
	}
	while (formsCol.MoreRecords);

	forms.Select(e => new
	{
		Name = e.GetAttributeValue<string>("name"),
		FormJSON = e.GetAttributeValue<string>("formjson"),
		Functions = System.Text.RegularExpressions.Regex.Matches(e.GetAttributeValue<string>("formjson"), "(\"EventName\":(.*?)},)", RegexOptions.None).Cast<Match>().Select(m => m.Value).ToList()
	}).Dump();
}

Since the formjson content is just a string, I used a Regex pattern to find multiple occurrences of the Event Handlers for each form. The result is a list of the EventNames, FunctionNames and LibraryNames for each form that utilises the JavaScript library in the FetchXML statement.

Azure DevOps – Restore deleted pipelines

If you have deleted a release definition, either intentionally or by mistake, currently there is no way to restore it through the UI. However, there is a way to recover it using a simple PowerShell script. The details for the script can be found here.

A couple of points to note about running the script.

  • The script will only work within 4 weeks of the definition being deleted.
  • You will need to create a Personal Access Token (PAT) within your DevOps account. Details for creating your PAT are available here.
  • You do not need to encode the $accountName, $projectName and $definitionNameToRecover variables. Values containing spaces will work.
  • The script will recover the release definition but not the release history

D365 Portals – Language agnostic Content Snippets

When dealing with multi-language websites Content Snippets are a great way to separate out your language specific portal content from your Web Templates and Page Copy.

If you want to use a single Content Snippet across all of your portal languages, for example with generic HTML that is language agnostic, then all you need to do is ensure the value in the Content Snippet Language field is left blank.