functions/_Set-VariableFromEnvVar.ps1

# <copyright file="Set-VariableFromEnvVar.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>

<#
.SYNOPSIS
    Updates the value of a variable based on the value of an environment variable.
.DESCRIPTION
    Supports updating typed PowerShell variables using the string value from an environment variable.
    
    The PowerShell variable must already be defined for this function to udpate its value.
    
    Specific support is included for casting 'boolean' and 'hashtable' types, other types rely on the
    underlying casting support in PowerShell (e.g. integers).

    Hashtable variables can be overridden via an environment variable containing a JSON
    object definition.
.EXAMPLE
    PS C:\> $MyVar = 1
    PS C:\> $MyVar.GetType()
    IsPublic IsSerial Name BaseType
    -------- -------- ---- --------
    True True Int32 System.ValueType

    PS C:\> $env:SomeEnvVar = "50"
    PS C:\> $($env:SomeEnvVar).GetType()
    IsPublic IsSerial Name BaseType
    -------- -------- ---- --------
    True True String System.Object

    PS C:\> Set-VariableFromEnvVar -VariableName "MyVar" -EnvironmentVariableName "SomeEnvVar"
    True

    PS C:\> Write-Host $MyVar
    50
    PS C:\> $MyVar.GetType()
    IsPublic IsSerial Name BaseType
    -------- -------- ---- --------
    True True Int32 System.ValueType
.PARAMETER VariableName
    The name of the PowerShell variable that will be updated.
.PARAMETER EnvironmentVariableName
    The name of the environment variable that holds the new value.
.PARAMETER UseGlobalScope
    When true the variable will be updated in the 'global' scope, otherwise the 'script' scope will be used.

    Ref: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_scopes
.OUTPUTS
    Returns $true if the variable was updated, otherwise returns $false
#>

function Set-VariableFromEnvVar {
    [CmdletBinding()]
    [OutputType([bool])]
    param (
        [Parameter(Mandatory=$true)]
        [string] $VariableName,

        [Parameter(Mandatory=$true)]
        [string] $EnvironmentVariableName,

        [switch] $UseGlobalScope
    )

    if ( !(Test-Path variable:/$VariableName) ) {
        Write-Warning "Unable to override the variable '$VariableName' as it is not currently defined - skipping"
        return $false
    }

    $envVar = Get-Item env:/$EnvironmentVariableName

    # get the type of the existing value of the variable, defaulting to 'string' if null
    $existingValue = (Get-Variable -Name $VariableName).Value
    $varType = $existingValue ? $existingValue.GetType() : "".GetType()

    # Update the value of the variable, ensuring that the existing type is preserved and
    # the string value from the environment variable is cast/coerced/converted accordingly.
    switch ($varType.Name)
    {
        "Boolean" {
            # booleans require special handling, due to '-as' not always behaving as required
            # e.g. "false" -as [boolean] returns 'True'
            $varValue = [System.Convert]::ToBoolean($envVar.Value)
        }
        "Hashtable" {
            # support deserializing a JSON string into a hashtable
            try {
                $varValue = $envVar.Value | ConvertFrom-Json -AsHashtable
            }
            catch {
                Write-Warning "Unable to process environment variable '$($envVar.Name)' - the value was not valid JSON"
                return $false
            }
        }
        default {
            $varValue = $envVar.Value -as $varType
        }
    }

    Write-Verbose "Overriding '$VariableName' from environment variable [Type=$($varType.Name)]"
    Set-Variable -Scope ($UseGlobalScope ? "global" : "script") -Name $VariableName -Value $varValue

    return $true
}