Search code examples
powershellhashtable

PowerShell: putting globally scoped values in a module?


I have multiple scripts that utilize functions that are in a module (psm1). I also have common values (file paths/names) that are currently in global hashtables in the module. MS is now discouraging global objects so I am trying to find a way to include my values in the module and not use another file (XML, CSV, etc.) and not put them in each script. When things change (and they will) I want to simply update in one location - my module.

Any ideas for a globally available 'dictionary' / 'hashtable' of values that can be put in the module and then imported when I import the module at the beginning of each script? I am importing the module with the -global parameter but without specifically making the hashtables global the values don't persist. The functions are fine (by design).

PSSharper throws warnings about global variables, objects, hashtables, etc. PS ScriptAnalyzer calls out the global hashtables, and multiple articles by MS discourage global usage.

TIA


Solution

  • I presumably have a solution for you based on variables being placed in the module visibility scope. It's based on this blogpost

    You create a variable in the module scope and put all the stuff you need for the module to work in it:

    $DynDnsSession = [ordered]@{
        ClientUrl           = 'https://api.dynect.net'
        ApiVersion          = $null
        AuthToken           = $null
        StartTime           = $null
        RefreshTime         = $null
    }
    New-Variable -Name DynDnsSession  -Value $DynDnsSession -Scope Script -Force
    

    As the variable will be defined in the module scope, you'll have no way of changing it with directly addressing it from your scripts - you have to have wrappers (a getter and a setter) for that variable inside your module. For example (that is the whole .psm1 module ; based on that same blogpost)

    $DynDnsSession = [ordered]@{
        ClientUrl   = 'https://api.dynect.net'
        ApiVersion  = $null
        AuthToken   = $null
        StartTime   = $null
        RefreshTime = $null
    }
    
    Function Get-DynDnsSession {
        return $DynDnsSession
    }
    
    Function Set-DynDnsSession {
        [CmdletBinding()]
        param(
    
        )
    
        DynamicParam {
            # define dictionary for dynamic parameters
            $ParamDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() 
    
            # get all the keys from the module variable; please note the .GetEnumerator()
            # it's to separate properties from each other, otherwise you just get all the values as a single object and not an array
            $DynProps = $DynDnsSession.Keys.GetEnumerator() 
            foreach ($Prop in $DynProps) {
                # type of the attribute. You probably can base this
                # on the type of variables you have in your dictionary
                $AttrType = "String"
                $AttrCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
                $ParamAttr = [System.Management.Automation.ParameterAttribute]@{
                    ParameterSetName = "DynamicFields"
                    Mandatory        = $false
                    HelpMessage      = $Prop # you can define this with a switch based on a property name if you want to 
                }
                $AttrCollection.Add($ParamAttr)
                $DynParam = [System.Management.Automation.RuntimeDefinedParameter]::new(
                    $Prop, $AttrType, $AttrCollection
                )
        
                $ParamDictionary.Add($Prop, $DynParam)
            }
            return $ParamDictionary
        }
    
        PROCESS {
            foreach ($Param in $PSBoundParameters.GetEnumerator()) {
                if ($DynDnsSession.Contains($Param.Key)) {
                    Write-Output $Param
                    Write-Verbose "Setting $($Param.Key) to $($Param.Value)" -Verbose
                    $DynDnsSession.$($Param.Key) = $Param.Value
                }
            }
        }
    }
    

    I have also defined dynamic parameters to validate what you want to set. But if you want to add or remove properties from that module variable you'll have to use another approach (which would be easier than DynamicParam though). I use that in my multiple RestAPI modules with no problem and never had PSScriptAnalyzer complain.

    DynamicParam loosely based on this post.