How to use Environment Variables in your plugins

by Sep 5, 2022

Environment Variables

Dynamics 365 / Power Apps solution can have Environment Variables. Often a Settings table (=entity) would be created to store configuration settings that differ between environments. We now can replace these with environment variables.

These environment variables can be added to a solution, be exported, and imported in another environment. If you do it correctly it even asks for the values for the environment variables when you import.

They can have distinct types, but the simplest one is Text. Other options are Decimal number, Two options and JSON. JSON is the most flexible one because you can store multiple settings, so you don’t need to create an environment variable for each one. It is also future proof because you can add more settings later.

There are also two more advanced data types, Data source and Secret (preview) which make it possible to store the values outside of Dataverse. With Data source you need to add a connector and Secret values are stored in Azure Key Vault.

The variables are stored in the tables environmentvariabledefinition and environmentvariablevalue, which can be secured using Dataverse Users and Roles.

You can use these environment variables in Client-side code (JavaScript), Plugins and in Power Automate but there is no out-of-the-box way to retrieve them. You need to query the tables to get the values.

new environment variable

Retrieving Environment Variables

So, there is not a straightforward way to retrieve them in plugins. Therefore, I added a method to my base plugin class to get the variables in a dictionary. You can also add this code in your specific plugin, instead of your base plugin.

I have added the methode GetEnvironmentVariables which will retrieve all the environment variables in the environment with the Current Values. It will pick the first Current Value if there are more than one. If there are no Current Values, then it will retrieve the Default Value. I don’t check for the status (active/Inactive), but this can be added easily if needed.

It does this by querying the table environmentvariabledefinition, which contains the name, type and default value. The current values are stored in a separated table named environmentvariablevalue. The code assumes the values are of the data type Text. I haven’t tested this code with other data types yet, but I assume JSON will also work.

The environment variables are retrieved on every execution of a plugin, so I have added a temporary cache which helps when the plugin is run multiple times under high load.

public class Plugin : IPlugin
{
    static IReadOnlyDictionary<string, string> EnvVariables;
    static readonly object Lock = new();
    
    protected static IReadOnlyDictionary<string, string> GetEnvironmentVariables(LocalPluginContext ctx)
    {
        ctx.Trace($"Entered GetEnvironmentVariables");
            
        // Singleton pattern to load environment variables less
        if (EnvVariables == null)
        {
            lock (Lock)
            {
                if (EnvVariables == null)
                {
                    ctx.Trace($"Load environment variables");
                    var envVariables = new Dictionary<string, string>();

                    var query = new QueryExpression("environmentvariabledefinition")
                    {
                        ColumnSet = new ColumnSet("statecode", "defaultvalue", "valueschema",
                          "schemaname", "environmentvariabledefinitionid", "type"),
                        LinkEntities =
                        {
                            new LinkEntity
                            {
                                JoinOperator = JoinOperator.LeftOuter,
                                LinkFromEntityName = "environmentvariabledefinition",
                                LinkFromAttributeName = "environmentvariabledefinitionid",
                                LinkToEntityName = "environmentvariablevalue",
                                LinkToAttributeName = "environmentvariabledefinitionid",
                                Columns = new ColumnSet("statecode", "value", "environmentvariablevalueid"),
                                EntityAlias = "v"
                            }
                        }
                    };

                    var results = ctx.SystemUserService.RetrieveMultiple(query);
                    if (results?.Entities.Count > 0)
                    {
                        foreach (var entity in results.Entities)
                        {
                            var schemaName = entity.GetAttributeValue<string>("schemaname");
                            var value = entity.GetAttributeValue<AliasedValue>("v.value")?.Value?.ToString();
                            var defaultValue = entity.GetAttributeValue<string>("defaultvalue");

                            ctx.Trace($"- schemaName:{schemaName}, value:{value}, defaultValue:{defaultValue}");
                            if (schemaName != null && !envVariables.ContainsKey(schemaName))
                                envVariables.Add(schemaName, string.IsNullOrEmpty(value) ? defaultValue : value);
                        }
                    }

                    EnvVariables = envVariables;
                }
            }
        }

        ctx.Trace($"Exiting GetEnvironmentVariables");
        return EnvVariables;
    }

    // Other code of the base Plugin...
}

How to use it in your plugins?

If you added the code above to your base plugin (or your specific plugin), you should be able to use the method GetEnvironmentVariables which will return a Dictionary with all the environment variables and their values in the environment.

You can then use the Dictionary to lookup the variable you need. See the example below:

ctx.Trace($"Try getting av_myvariable from environment variables");
if (!GetEnvironmentVariables(ctx).TryGetValue("av_myvariable", out string myVariable))
        throw new Exception("Couldn't read environmentvariable av_myvariable!");

Remy van Duijkeren

Remy van Duijkeren

Power Platform Advisor

Microsoft Power Platform Advisor with over 25 years of experience in IT with a focus on (marketing) automation and integration.

Helping organizations with scaling their business by automating processes on the Power Platform (Dynamics 365).

Expert in Power Platform, Dynamics 365 (Marketing & Sales) and Azure Integration Services. Giving advice and strategy consultancy.

Services:
– Strategy and tactics advise
– Automating and integrating

Subscribe to
The Daily Friction

A daily newsletter on automation and eliminating friction

Related Content

Power Pages Secure?

Power Pages Secure?

Power Pages! They tell me it's easy and is very secure, it's built in! I am not fully convinced when I hear this. Why? Simple said: "Easy and Security don't mix". Making something more secure will make it less easy to use. Data separation The thing is, Dataverse is...

read more
Improving the Form Switcher

Improving the Form Switcher

In Microsoft Dynamics 365, Model Driven Apps in the Power Platform, you can create multiple main forms for the same table or TOFKAE (The Object Formally Known As Entity). When to use multiple forms This can be handy because you can assign different security roles to...

read more
The attribute was not found in the MetadataCache

The attribute was not found in the MetadataCache

The Problem I was trying to export a Solution from Dynamics 365 CE / Dynamics CRM to deploy into the next environment, but instead I got a Query Builder Error: The specified field does not exist in Microsoft Dynamics 365. Looking into the log file I saw the...

read more