More Tied Variables in PowerShell
Thursday, 26 March 2009
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)
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 | ## 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)) |


Subscribe to this blog.
No. 1 — March 29th, 2009 at 5:09 pm
This is really interesting. I am wondering when to prefer this approach.
Looking forward to your 2nd Edition.
No. 2 — April 16th, 2009 at 3:11 pm
I get the following error when I try to use this. This seems like a very useful script if I can get it to work. I am using CTP3 if that matters.
Unrecognized token in source text.
At C:\Users\dmonder\Documents\WindowsPowerShell\Scripts\New-ScriptVariable.ps1:4 char:10
+ Add-Type <<<< @"
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnrecognizedToken
No. 3 — April 20th, 2009 at 9:26 pm
David: It looks like IE added an extra space at the end of line 4. Remove that space, and you’re good to go.
No. 4 — April 30th, 2009 at 9:15 am
Hi Lee.
Great stuff. But have two problems:
1) Calling it with global:something results in a variable having a name including the global. Works, but ugly.
2) The objects returned are of type System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]. This means that useful stuff (see example below) is not that useful
New-ScriptVariable global:Jan1st {get-date -hour 0 -min 0 -sec 0 -mon 1 -day 1}
dir | where {$_.lastwritetime -gt $jan1st}
Bad argument to operator ‘-gt’: Could not compare "13-02-2009 10:07:58" to "System.Collections.ObjectModel.Collection`1
[System.Management.Automation.PSObject]". Error: "Cannot convert the "System.Collections.ObjectModel.Collection`1[Syste
m.Management.Automation.PSObject]" value of type "System.Collections.ObjectModel.Collection`1[[System.Management.Automa
tion.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]" to ty
pe "System.DateTime".".
At line:1 char:34
+ dir | where {$_.lastwritetime -gt <<<< $jan1st}
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : BadOperatorArgument
Any fixes for these things?
No. 5 — May 17th, 2009 at 10:39 pm
Thanks, Per — updated the script to act more like you would expect!
No. 6 — April 12th, 2011 at 2:05 am
[...] posted a question to the PowerShell MVP private list and soon enough Lee Holmes, pointed me to this blog post. Here's the code I used to create [...]