Dynamics CRM 365: Build Automation for Create/Update RESX WebResource + Power Automate Desktop

In this post, we already learned how to make a plugin library for getting resx web-resource. Now we will build a simple automation program (a little bit DevOps) to read an excel file and create/update resx web-resource with a single click. Also, we will use Power Automate Desktop which enables us to a more complex scenario in the future.

Create Exe File

Before we set up our Desktop Flow, we need to create first our c# code (What? C# code? Yes. We still need an exe program to helps connect to our CRM instance). Here is the code:

using Entities;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;
using Niam.XRM.Framework;
using Niam.XRM.Framework.Data;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Resources;
using System.Threading;

namespace UpdateResxWebResource
{
    public class ExcelData
    {
        public string Name { get; set; }
        public string Value { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var header = string.Join(" ", args).Split(new[] { "||" }, 
                StringSplitOptions.RemoveEmptyEntries);
            var data = header[1].Split(new[] { Environment.NewLine },
                StringSplitOptions.RemoveEmptyEntries).ToArray();
            var rows = GetExcelData(data).Where(e => !string.IsNullOrEmpty(e.Name))
                .Where(e => !string.IsNullOrEmpty(e.Name)).ToArray();
            var fileName = header[0];

            var conSetting = ConfigurationManager.AppSettings["connectionString"];
            var service = new CrmServiceClient(conSetting);
            var webResource = GetWebResource(service, fileName) ?? CreateWebResource(fileName);

            SetWebResourceContent(webResource, rows);

            if (webResource.Id == Guid.Empty)
            {
                webResource.Id = service.Create(webResource);
                Console.WriteLine($"{fileName} is created with id {webResource.Id }..");
            }
            else
            {
                service.Update(webResource);
                Console.WriteLine($"{fileName} is updated with id {webResource.Id}..");
            }

            service.Execute(new PublishXmlRequest
            {
                ParameterXml =
                    $"<importexportxml><webresources><webresource>{webResource.Id}</webresource>" +
                    "</webresources></importexportxml>"
            });

            Thread.Sleep(5000);
        }

        private static void SetWebResourceContent(WebResource webResource, ExcelData[] rows)
        {
            var temp = ConfigurationManager.AppSettings["fileFolder"] + "temp.resx";
            using (var resx = new ResXResourceWriter(temp))
            {
                foreach (var row in rows)
                {
                    Console.WriteLine($"Add Resx with Name: {row.Name} with Value: {row.Value}");
                    resx.AddResource(row.Name, row.Value);
                }
            }

            var text = File.ReadAllBytes(temp).Base64Encode();
            webResource.Set(e => e.Content, text);
        }

        private static WebResource CreateWebResource(string fileName)
        {
            return new WebResource().Set(e => e.Name, fileName)
                .Set(e => e.DisplayName, fileName)
                .Set(e => e.WebResourceType, WebResource.Options.WebResourceType.StringRESX);
        }

        private static WebResource GetWebResource(IOrganizationService service, string fileName)
        {
            var query = new QueryExpression(WebResource.EntityLogicalName)
            {
                ColumnSet = new ColumnSet<WebResource>(e => e.Content),
                TopCount = 1,
                NoLock = true
            };
            query.Criteria.AddCondition<WebResource>(e => e.Name, ConditionOperator.Equal, fileName);
            var result = service.RetrieveMultiple(query);

            return result.Entities.Any() ? result.Entities[0].ToEntity<WebResource>() : null;
        }

        private static IEnumerable<ExcelData> GetExcelData(string[] data)
        {
            foreach (var text in data)
            {
                var info = text.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries)
                    .Select(e => e.Trim()).ToArray();
                yield return new ExcelData
                {
                    Name = info.Length > 1 ? info[0] : "",
                    Value = info.Length > 2 ? info[1] : ""
                };
            }
        }
    }

    public static class StringExtensions
    {
        public static string Base64Encode(this byte[] bytesData)
        {
            var base64Data = Convert.ToBase64String(bytesData);
            return base64Data;
        }
    }
}

In that code, we will need 2 appSettings:

  • connectionString for connection string for our Dynamics instance
  • fileFolder for store result of resx before updated to our Dynamics instance

So the logic on the exe is pretty simple:

  1. Power Automate Desktop will array strings of the data that we need it.
  2. We compile the data and transform it into the data model that we already prepare (fileName and array of ExcelData).
  3. We connect to our Dynamics Instance.
  4. Get the resx web-resource base on the name.
  5. Create/Update the web-resource.
  6. Publish

Create Excel Format

Here is the excel format. Pretty simple. Make a table with 2 columns: Name and Value.

Excel format

Set-up Power Automate Desktop

You must download and install Power Automate Desktop on this link. Once you already install it, you can get trial access for it (around 90 days). Create a new flow for Power Automate Desktop. Name it whatever suitable for you. And these are my steps:

Steps required in Power Automate Desktop

First, you must create 2 input variables. These inputs will be for excelFilePath(location for your excel file) and fileName(file name for your resx web-resource. Remember must follow the format {name}.{languageCode}.resx). We also need to add pre-defined value for these inputs for now.

Both inputs need to set default the value (filepath+name)

Then we start creating the steps:

Create step Launch Excel > Launch Excel as “and open the following document” > Document Path as “%excelFilePath%” > Save.

Launch Excel step

Create step Read From Excel worksheet > Excel instance as “%ExcelInstance%” > Retrieve as “Values from a Range Cells” > Start column as “A” > Start row as “1” > End column as “B” > End row as “1000” > in Advanced set First line of range contains column names as true > Save.

Read from Excel worksheet step

Create step Close Excel > Excel Instance as “%ExcelInstance%” > Before closing Excel as “Do not save document” > Save.

Close Excel step

Create step Run Application > Application Path to your exe file > Command line arguments as “%fileName%||%ExcelData%” > Save

Run application step

Demonstration

Following those steps above, every time you run the flow the automation will be running for creating/updating the web resource. Here is the demonstration for the result:

Demo time!

Here is my testing result (calling Xrm.Utility.getResourceString on the screen):

Testing the value

Tips

If you working resx file in javascript using function Xrm.Utility.getResourceString, you need to clear your cookies and cached because CRM got a mechanism to cache them. So every time you run the automation process to create/update the resx web-resource you must do this step.

Clear cookies + cached will force browser to get newer resx file.

Also if you calling from javascript, you must remember to add your resx as dependency per this article.

Add resx as dependency in caller javascript

2 thoughts on “Dynamics CRM 365: Build Automation for Create/Update RESX WebResource + Power Automate Desktop

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.