Main | Contact | Blog | Documentation

PowerShell Framework

The project dedicated to empowering your PowerShell scripting.

Schema: MetaJson

Back to Configuration

Synopsis

The MetaJson configuration schema is a json-based format designed for a human being to author configuration files in. It provides the ability to dynamically evaluate settings, decide based on extensible filter conditions which settings to include or to chain an entire hierarchy of configuration files to merge into one setting.

Description

This configuration schema has been optimized for human authoring. As such, it fully relies on Json serialization when reading data values for most parts.

Basic Layout

In its json structure, the overall layout is simple enough:

{
    "Version": 1,
    "ModuleName": "",
    "<category1>": {

    },
    "<category2>": {

    }
}

Version

The Version is used to identify, which version of the schema it was authored for. If at a later time, PSFramework decides to change the way it parses configuration files with the MetaJson schema, it will determine based off this property on whether to still use the legacy processing for compatibility reasons. This enables us to avoid breaking the config when updating the PSFramework.

The current latest version is 1.

ModuleName

The module name entry is used by the categories Static, Object and Dynamic. It is used as a prefix to each configuration setting name. It is optional and if omitted, all settings must include their full name, rather than the name without the module bit.

Categories

There are a total of six different categories / node types you can specify. Each category is a different way of providing one or more name/value pairs for the configuration.

You can use them in any combination, all of them optional (but if no category is specified, no setting is imported). Some categories are intentionally unflexible but precise, while others are highly dynamic and allow for flexible values and complex decision-making, so use whichever fits best for you.

Each category is later described in full detail with example configuration in later chapters, but as a summary:

Static A static mapping of configuration names and values, values read straight from/as json
Object A static mapping of configuration names and values, but each value is provided as a compressed Clixml string
Dynamic A flat mapping of configuration names and values, using json serialization, but both subject to insertion from environment variables
Tree Names provided through nested namespaces, supports conditionally including/excluding parts of its settings. Uses Json Serialization and neither name nor value are further interpreted
DynamicTree Names provided through nested namespaces, supports conditionally including/excluding parts of its settings. Uses Json Serialization and name nor value are further interpreted via environment variables
Include An additional array of files to also process as configuration files

Category: Static

Static is about the easiest way to provide configuration data under the metajson schema. You provide a static list of name/value pairs and that is it.

Example 1: Plain

{
    "Version": 1,
    "Static": {
        "Bartender.Fridge.Size": 200,
        "Bartender.Fridge.Content": 180,
        "Bartender.Fridge.Manufacturer": "Siemens"
    }
}

Example 2: ModuleName

{
    "Version": 1,
    "ModuleName": "Bartender",
    "Static": {
        "Fridge.Size": 200,
        "Fridge.Content": 180,
        "Fridge.Manufacturer": "Siemens",
        "Cellar.Barrels": 42
    }
}

Category: Object

Fundamentally, this works the same way the static category does. The only difference is the way in which values are provided.

This may seem intimidating, but it allows for complex PowerShell objects to be provided without losing their integrity. To generate the string data use ConvertTo-PSFClixml, e.g.:

Get-Item C:\Windows | ConvertTo-PSFClixml

Example 1: “Plain”

{
    "Version": 1,
    "Object": {
        "Bartender.Fridge.Size": "H4sIAAAAAAAACm2O3wqCMBTG74PeYewBdqZ5FXPQpRAGKd1EF7YmGs7JNijfvmMiBXV1Pn7fH444XO+enLTzre1TGrGIcRZR8jRd71PahDBsAbxqtKk8M61y1ts6MGUNDPaBvUZ3HcScJ8ATKtcrQgRukqOus1tK+YwQlvkPm6gsRh+0YdjRKpwvAso/7s65avxvzcWPhypf5L74amSbWOKfAiaxZJeEANxB9b5evgBTCBTYFwEAAA==",
        "Bartender.Fridge.Content": "H4sIAAAAAAAACm2M0QqCMBSG74PeYewBdjbzImIOuhTCIKWb6MLWRMM52Qbl2zcTKcir8/H//3f48fZw6Kysa0yXYEYYoYRh9NJt5xJce9/vAJyslS4d0Y20xpnKE2k09OYZvFq1LUSUxkBjLNYrhHj4iU6qSu8JplMUwiL7y8ZU5IPzSpPgKOkvVw7FQru3thyWq0n8doGyGQ/5j5FuIsG2lMMI83ZecAh/An2uE29Q73PrFwEAAA==",
        "Bartender.Fridge.Manufacturer": "H4sIAAAAAAAACm2MQQrCMBBF94J3CDlAJpWuJA24FKSCKW7ERY0prTRNyQS0tzdaqoJdzef9/0bsLzckR+OxcV1GE5YwzhJKHrbtMKN1CP0aAHVtbInMNto7dFVg2lno3T16tWlbWHGeAk+pXC4IEfEnOZhqe80oH1GERf7HXlSqAYOxLDpGh9NZQDHTbrwvh/lqFL9dTPkUd+rHUFI1xpoOBajPdloIiH9iel+UTzoDi/UXAQAA"
    }
}

Example 2: ModuleName

{
    "Version": 1,
    "ModuleName": "Bartender",
    "Object": {
        "Fridge.Size": "H4sIAAAAAAAACm2O3wqCMBTG74PeYewBdqZ5FXPQpRAGKd1EF7YmGs7JNijfvmMiBXV1Pn7fH444XO+enLTzre1TGrGIcRZR8jRd71PahDBsAbxqtKk8M61y1ts6MGUNDPaBvUZ3HcScJ8ATKtcrQgRukqOus1tK+YwQlvkPm6gsRh+0YdjRKpwvAso/7s65avxvzcWPhypf5L74amSbWOKfAiaxZJeEANxB9b5evgBTCBTYFwEAAA==",
        "Fridge.Content": "3f48fZw6Kysa0yXYEYYoYRh9NJt5xJce9/vAJyslS4d0Y20xpnKE2k09OYZvFq1LUSUxkBjLNYrhHj4iU6qSu8JplMUwiL7y8ZU5IPzSpPgKOkvVw7FQru3thyWq0n8doGyGQ/5j5FuIsG2lMMI83ZecAh/An2uE29Q73PrFwEAAA==",
        "Fridge.Manufacturer": "H4sIAAAAAAAACm2MQQrCMBBF94J3CDlAJpWuJA24FKSCKW7ERY0prTRNyQS0tzdaqoJdzef9/0bsLzckR+OxcV1GE5YwzhJKHrbtMKN1CP0aAHVtbInMNto7dFVg2lno3T16tWlbWHGeAk+pXC4IEfEnOZhqe80oH1GERf7HXlSqAYOxLDpGh9NZQDHTbrwvh/lqFL9dTPkUd+rHUFI1xpoOBajPdloIiH9iel+UTzoDi/UXAQAA",
        "Cellar.Barrels": "H4sIAAAAAAAACm2M0QqCMBiF74PeYewB9k/zKuagSyEMUrqJLmxNNJyT/YPy7ZuJFNTVOZxzviMO1zuSk3bY2j6lEYsYZxElT9P1mNLG+2ELgKrRpkJmWuUs2tozZQ0M9hG4RncdxJwnwBMq1ytCRPgkR11nt5TyOQphmf9kUyqLEb02LDBa+fNFQPmn3TlXjf+rGfx0weWL3RdfRLaJZRILmHSZLgMB4Sa4t6J8AXAdfnsWAQAA"
    }
}

Category: Dynamic

Dynamic is again similar to the Static category. Key difference is that both name and values of the setting are subject to string replacement from environment variables.

Example 1: Plain

{
    "Version": 1,
    "Dynamic": {
        "Bartender.Fridge.Size": 200,
        "Bartender.Fridge.Content": 180,
        "Bartender.Fridge.Manufacturer": "%Manufacturer%"
    }
}

Example 2: ModuleName

{
    "Version": 1,
    "ModuleName": "Bartender",
    "Dynamic": {
        "Fridge.Size": 200,
        "Fridge.Content": 180,
        "Fridge.Manufacturer": "%Manufacturer%",
        "%Location%.Barrels": 42
    }
}

In these examples, we assume the existence of two environment variables: Manufacturer and Location.

Category: Tree

With the three simple table categories out of the way, let’s move to the fun and more convenient ones. The tree category allows nesting the namespaces of configuration entries, making configuration sets easier to read and group.

As a downside, it can only contain simple json types, such as string, int or bool (with the previous three categories, it is possible to implement settings with values that are custom objects with arbitrary properties, or even full PowerShell objects).

Example 1: Simple Tree

{
    "Version": 1,
    "Tree": {
        "Bartender": {
            "Fridge": {
                "Size": 200,
                "Content": 180,
                "Manufacturer": "Siemens"
            },
            "Cellar.Barrels": 42
        }
    }
}

This configuration file will result in the same four configuration settings as seen before:

Bartender.Fridge.Size         : 200
Bartender.Fridge.Content      : 180
Bartender.Fridge.Manufacturer : "Siemens"
Bartender.Cellar.Barrels      : 42

Much easier to read, and less effort to write.

Example 2: Include Filter Conditionals

In addition to the previous tree structure, we can also make use of Filter Expressions! This allows us to selectively chose which part of the configuration to include:

{
    "Version": 1,
    "Tree": {
        "Deployment": {
            "Mode": "Static"
        },
        "|AzureDevops": {
            "!Condition": "EnvAzDevPipeline -or -not OSWindows",
            "Deployment.Path": "/var/etc/whatever"
        },
        "|GithubActions": {
            "!Condition": "EnvGithubAction",
            "Deployment.Path": "/var/etc/whateverElse"
        },
        "|GitLab": {
            "!Condition": "EnvGitlab",
            "!ConditionSet": "ContosoCI ContosoEnv",
            "Deployment.Path": "/var/etc/lab"
        }
    }
}

In this example, we always define the setting

Deployment.Mode : "Static"

However, the Deployment.Path setting is defined with different values, depending on which filter condition applies:

By default, each Filter Expression defined under !Condition is executed against the Environment Condition Set provided by the PSFramework. If you want your expression to be evaluated using a different Condition Set, you also need to specify !ConditionSet, which consists of two parts:

The Condition Set must be defined before using it during the Configuration Schema.

Multiple valid conditions

If multiple conditions are valid, each valid node is executed. If multiple conditional nodes define the same setting, the last one wins.

Conditional node names

In the example, the conditional nodes were named |AzureDevOps, |GithubActions and |GitLab. These names are actually arbitrary and will never become part of the configuration settings name. I.e. Whatever, <AzureDevOps>, %1 or Crud! would have been equally valid.

Category: DynamicTree

The DynamicTree category combines the nested namespacing (and filter condition) capabilities of the Tree mode with the name & value insertion of the Dynamic category.

Example: Simple

{
    "Version": 1,
    "Tree": {
        "Bartender": {
            "Fridge": {
                "Size": 200,
                "Content": 180,
                "Manufacturer": "%Manufacturer%"
            },
            "%Location%.Barrels": 42
        }
    }
}

Nothing more to cover here really, for details on the tree structure, see above for the Tree category description.

Filter conditions & Dynamisms

The Filter Expression statements defined in !Condition are not subject to dynamic insertion from environment variables.

Category: Include

In opposite to the other categories, the Include category does not directly define configuration settings. With this setting you instead specify additional configuration files to load.

Example 1: Simple includes

{
    "Version": 1,
    "Static": {
        "Bartender.Fridge.Size": 200,
        "Bartender.Fridge.Content": 180,
        "Bartender.Fridge.Manufacturer": "Siemens"
    },
    "Include": [
        "C:\\config\\global.json",
        "furniture.json"
    ]
}

In this example - besides setting a few static settings - we first load the global.json file from its absolute path, then furniture.json from the same folder as the original input configuration file.

Example 2: Dynamic Path Evaluation

{
    "Version": 1,
    "Static": {
        "Bartender.Fridge.Size": 200,
        "Bartender.Fridge.Content": 180,
        "Bartender.Fridge.Manufacturer": "Siemens"
    },
    "Include": [
        "C:\\config\\global.json",
        "furniture.json",
        "%stage%/settings.json"
    ]
}

Same as in example 1, only this time we further add another configuration file from a subfolder relative to the original file. The name of the subfolder is evaluated from environment variables, equal to how in the Dynamic category setting names and values were interpreted.

Conflict Resolution

Any other categories are processed first, before executing the include files. The include files are then processed in order from top to bottom.

Throughout that entire processing order, all settings are merged. If more than one category or include files writes the same setting, the last one in the processing chain wins.

Missing File

If one of the referenced files does not exist, it will generate a warning but processing continues. To interrupt processing, call Import-PSFConfig with the -EnableException switch.

Combining Categories

All categories can be freely combined, the last category to write a specific setting wins in case multiple categories try defining the same setting.

Back to Configuration