Inject PowerApps Environment Variables into Entity Record Data

PowerApps Environment Variables, a new solution component that provides a great way to transport a definition, default value and override value used in configuration. This provides a lot of opportunity as configuration based in data with a solution was always really complex to do. It solves a great problem but how else can it be applied to existing configuration data elements or how can it be used right in entity record data.

Some scenarios of environment variables with existing configuration data:

  • You want your portal site setting values to be composed by an environment variable.
  • You want your OData Data Source Uri based on an environment variable.

Both of these scenarios as well as tons of others everyone else may have all has to do with existing entity record data. So how can we have environment variable values injected into these items dynamically.

String interpolation is perhaps the closest way to describe, getting variable values into data. If you aren’t familiar with string interpolation it provides “a readable and convenient syntax to create formatted strings”. Allowing you to easily and cleanly format a string with parameter based data.

Input: "Hello {firstname}, you are located at {street1}."

Output: Hello Colin, you are located at 123 Street.

Using the scenarios from above, and thinking of string interpolation with environment variables let’s look at how data could be subbed in.

OData Data Source actually can be included in a solution, but manipulating the Uri of an OData Data Source in the solution is difficult to manage because of how that data is stored (note, it is not pretty at all, not XML or JSON). Instead of setting the Uri for the data source to "https://www.demo.com/odata" could use string interpolation with "https://{env_domain_uri}/odata". Allowing the configuration of the environment variable value to determine the {env_domain_uri}.

For portal site settings, they aren’t solution components at all. They can be moved with the Configuration Migration Tool, but manipulating the values is again challenging especially per environment. Making the value of a setting point to an environment variable would allow that settings value to be the environment variable and the new ALM benefits that come with that. Not all portal site settings make sense for this though, the ones that are environmental, like sign-in provider configuration for B2C would make really good candidates for this type of approach. So you might have site setting Authentication/OpenIdConnect/AzureAdB2C/ClientId, and if you wanted this to go to separate B2C directories for each environment you need a different clientId. With environment variables and string interpolation the value of this site setting might be {azureAdB2CClientId}.

How you can achieve a real-time or run-time injection is with the functionality of a retrieve plugin in CDS. A retrieve and retrieve multiple plugin can manipulate the data as the system retrieves CDS data as a post operation and it can be merged with environment variable values so the output and seen attribute value by the system is the formatted string value.

To help facilitate this I have create a generic plugin to would help it work in multiple scenarios beyond what I have outlined above of the OData and portal settings (these were top of mind for me as I am actively using both). The idea being if you have a formatted string of https://{demo_serviceuri}/odata and the environment variable with a unique name of demo_serviceuri, and a default value of http://www.demo.com that the plugin will replace the output to combine the environment variable as a parameter of the format string. Outputting the value or default value in the string. So https://{demo_serviceuri}/odata becomes https://www.demo.com/odata and anyone that retrieves that OData Data Source including system services that is the value they get.

For this post we will explore the retrieve setup and code to statisify the OData Data Source scenario. Next post

You can view the full plugin code on GitHub, download and deploy as well for your own uses as well.

A quick walk-through of the plugin logic and a guide to implementing this yourself.

Firstly to be generic as I didn’t want to write a plugin for every entity as well be able to cover scenarios that I hadn’t even come up with yet, I couldn’t assume knowing the attribute schema name which we want to effect. For OData I wanted to effect msdyn_uri and for portal site settings I wanted to effect adx_value.

The other item I couldn’t assume was that the attribute I wanted to effect (output) might also not be the input attribute with the formatted string. For the OData scenario I might not have my formatted string in msdyn_uri but have it instead in msdyn_name, but I want the output. The reason behind this is that when you have a retrieve plugin that manipulates the output data it can become confusing to manipulate that data as well in the UI.

For example using msdyn_uri as the place I store my input string of https://{demo_servicesuri}/odata, then be manipulated out as https://demo-dev.com/odata, I don’t really know my formatted string or it becomes harder to track what it is. I can’t see it in the model driven form because the retrieve modifies it. I can though see it in a list or advanced find because the use case here is single retrieve.

To provide this configuration the plugin uses and requires an unsecure configuration. Below is the format of that configuration. At a minimum you must provide the input setting value. If not providing an output the input value will serve as input AND output thus causing the potential confusion mentioned.

<Settings>
  <setting name="input">
    <value>msdyn_name</value>
  </setting>
  <setting name="output">
    <value>msdyn_uri</value>
  </setting>
  <setting name="prefix">
    <value>env</value>
  </setting>
</Settings>

You will notice also a prefix setting option. This is so you can prefix your parameters in the format string. Allows a specific all out if you plan on using the style of format strings in your content as well. For example you wanted {blah_{env:demo_servicename}}. By default the format string would look for the environment variable based on the first curly bracket set. By providing the prefix in the definition though you can tell the plugin to only look for when the prefix is also included. So {env:demo_servicename} would be what is replaced.

What goes inside the curly brackets is the unique name of your environment variable. So if you create the environment variable “Service Name” it will be the unique name based on your solution publisher prefix and the display name. “Service Name” with demo_ solution publisher becomes demo_servicename. You can find your environment variable unique name in the solution explorer when viewing the environment variable.

Environment Variable unique name

Let’s register the plugin on the OData v4 Data Source as the first example. Assuming you have downloaded and compiled/built the plugin project. Pop open your plugin registration tool of choice and connect to your environment. Register the assembly created by the plugin project (Cds.EnvironmentVariable.Interpolation.Plugins.dll). Full steps with screenshots using the SDK Plugin Registration Tool are available here – https://docs.microsoft.com/en-us/powerapps/developer/common-data-service/tutorial-write-plug-in#register-plug-in

Once you have the plugin registered you need to register a new step. Right clicking your assembly in the Plugin Registration Tool and then selecting Register New Step. On the new step screen select your message as Retrieve, the primary entity in our scenarios case is msdyn_odatav4ds, and also needed is to set it to a PostOperation pipeline execution stage (so that it occurs after the data has been retrieved from the CDS database. Finally put in the unsecure configuration and set the appropriate values. For the OData scenario, input: msdyn_name, output: msdyn_uri, prefix: env.

The plugin basically now will take the attribute defined in the input value if it starts with a dollar sign ($), this is a standard shortcut key for string interpolation in C# I have followed, and then apply a simple regular expression to it to pick out each of the environment variables used in the string. Yes you could include MULTIPLE environment variables if you wanted.

var regexString = @"(\{" + prefixString + @"\w+})";
var rx = new Regex(regexString, RegexOptions.Compiled | RegexOptions.IgnoreCase);

var matches = rx.Matches(input);

For each environment variable it picks out with the regular expression it then attempts to find an environment variable definition for the unique name (schemaName in the code). If it finds that it checks to see if there is a corresponding environment variable value. Based on how environment variables is designed the value will override the definitions default value. The default value is the fallback if no value is defined.

foreach (var item in matches)
            {
                var schemaName = item.ToString().Replace("{" + prefixString, "").Replace("}", "");

                var environmentVariableDef = orgContext.CreateQuery("environmentvariabledefinition").FirstOrDefault(a => a.GetAttributeValue<string>("schemaname").Equals(schemaName));

                if (environmentVariableDef == null)
                {
                    continue;
                }

                var defaultValue = environmentVariableDef.GetAttributeValue<string>("defaultvalue");

                var environmentVariableValue = orgContext.CreateQuery("environmentvariablevalue").FirstOrDefault(a => a.GetAttributeValue<EntityReference>("environmentvariabledefinitionid").Id == environmentVariableDef.Id);

                var value = defaultValue ?? item.ToString();
                if (environmentVariableValue != null)
                {
                    value = environmentVariableValue.GetAttributeValue<string>("value");
                }

                input = input.Replace(item.ToString(), value);
            }

When all done it sets the OutputParameters[“BusinessEntity”], which is the object that is the entity that will be output to the client, and it sets the output attribute.

Entity entity = (Entity)context.OutputParameters["BusinessEntity"];
entity.Attributes[outputAttribute] = input;

Now we need our environment variable created. Head to the modern solution explorer, create your variable and set a default value, optional set a value as well. For me I want the domain of the Uri so I will set http://www.demo.com as the default value.

Now let’s go create an OData v4 Data Source. For the Name I will input my format string, $https://{env:demo_test}/odata. In the Uri when creating I will put https://www.helloworld.com, but on retrieve because of my plugin I should end up seeing https://www.demo.com/odata.

Creating new OData v4 Data Source with environment variable interpolation string in name field
Retrieved OData v4 Data Source with interpolated string in URL

The plugins that use the OData v4 Data Source will now RETRIEVE the data source entity record and get the manipulated value back instead of the one previously set.

The other scenario of the PowerApps Portal, this one is a little different as it needs the retrieve multiple not just the retrieve message. As well you can’t get away with using a different input and output field based on the portal does very narrow queries for data so that it performs as best as possible. But it does work! Updated plugin with retrieve multiple and a portal example next week.

Those are my 2 current scenarios, I definitely invite anyone to take the plugin code and deploy it for use in their scenarios and also curious to hear what they are.

Checkout the GitHub repo project, the readme has a shortened version of this post and look forward to feedback in the comments here or on Git.

A last note…The interpolation format string if used as the input and output can be overwritten if record modification in the UI of any fields occurs and save or auto save is triggered.