More Tied Variables in PowerShell

Thu, Mar 26, 2009 2-minute read

Ibrahim just posted something to the PowerShell blog about how to create tied variables in PowerShell. If you extend this approach with script blocks, you have a very powerful dynamic scripting technique.

PS C:\temp> cd \temp
PS C:\temp> New-ScriptVariable.ps1 GLOBAL:lee { $myTestVariable } { $GLOBAL:myTestVariable = 2 * $args[0] }
PS C:\temp> $lee
PS C:\temp> $lee = 10
PS C:\temp> $lee
20
PS C:\temp> New-ScriptVariable.ps1 GLOBAL:today { (Get-Date).DayOfWeek }
PS C:\temp> $today
Wednesday
PS C:\temp> New-ScriptVariable.ps1 GLOBAL:random -Get { Get-Random } -Set { Get-Random -SetSeed $args[0] }
PS C:\temp> $random
1740776676
PS C:\temp> $random
1507521897
PS C:\temp> $random = 10
PS C:\temp> $random
1613858733
PS C:\temp> $random = 10
PS C:\temp> $random
1613858733

He alluded to it in the post – here is the full text of the script:

(Edit 05/17: Updated to make the getters more like PowerShell pipelines: return a single object, or collection of PSObject)

(Edit 04/21: This got a lot better with the introduction of PowerShell Classes. Check out “Program: Create a Dynamic Variable” in the PowerShell Cookbook)

## New-ScriptVariable.ps1
param($name, [ScriptBlock] $getter, [ScriptBlock] $setter)

Add-Type @"
using System;
using System.Collections.ObjectModel;
using System.Management.Automation;

namespace Lee.Holmes
{
    public class PSScriptVariable : PSVariable
    {
        public PSScriptVariable(string name,
            ScriptBlock scriptGetter, ScriptBlock scriptSetter)
            : base(name, null, ScopedItemOptions.AllScope)
        {
            getter = scriptGetter;
            setter = scriptSetter;
        }
        private ScriptBlock getter;
        private ScriptBlock setter;

        public override object Value
        {
            get
            {
                if(getter != null)
                {
                    Collection<PSObject> results = getter.Invoke();
                    if(results.Count == 1)
                    {
                        return results[0];
                    }
                    else
                    {
                        PSObject[] returnResults = new PSObject[results.Count];
                        results.CopyTo(returnResults, 0);
                        return returnResults;
                    }
                }
                else { return null; }
            }
            set
            {
                if(setter != null) { setter.Invoke(value); }
            }
        }
    }
}
"@

if(Test-Path variable:\$name)
{
    Remove-Item variable:\$name -Force
}
$executioncontext.SessionState.PSVariable.Set(
    (New-Object Lee.Holmes.PSScriptVariable $name,$getter,$setter))