It’s always bothered me that there isn’t a clean way to deal with IDisposables in PowerShell. It seems Adam Weigert came to the same conclusion and implemented a using
function much like the statement found in C# and VB. Note that he also makes use of his implementation of PowerShell try..catch..finally
, which is pretty slick. Meanwhile, I’m told Raymond Mitchell has his own using
function that he uses to load assemblies, which certainly makes sense to me.
I figure the next evolution is to provide a generic using
that covers all the bases:
function using { param ( $inputObject = $(throw "The parameter -inputObject is required."), [ScriptBlock] $scriptBlock ) if ($inputObject -is [string]) { if (Test-Path $inputObject) { [system.reflection.assembly]::LoadFrom($inputObject) } elseif($null -ne ( new-object System.Reflection.AssemblyName($inputObject) ).GetPublicKeyToken()) { [system.reflection.assembly]::Load($inputObject) } else { [system.reflection.assembly]::LoadWithPartialName($inputObject) } } elseif ($inputObject -is [System.IDisposable] -and $scriptBlock -ne $null) { Try { &$scriptBlock } -Finally { if ($inputObject -ne $null) { $inputObject.Dispose() } Get-Variable -scope script | Where-Object { [object]::ReferenceEquals($_.Value.PSBase, $inputObject.PSBase) } | Foreach-Object { Remove-Variable $_.Name -scope script } } } else { $inputObject } }
Some notes on the code:
- If
$inputObject
is a string, I assume it’s an assembly reference…- If the string is a path, load as a path
- Rather than parse the string, I figure the framework knows best; the presence of a PublicKeyToken means it’s probably a full assembly name
- I considered adding support for this alternative to
LoadWithPartialName
, but I don’t feel like managing a global “assembly map”; the deprecated shortcut will have to do for now
- If
$inputObject
isIDisposable
and a script block was supplied…- Wrap script execution in
Try..Finally
to make sure we get toDispose()
- Here I disagree with Adam – if the PSObject’s Dispose method was overridden, we should assume it was done for good reason (more on this in a later post) and that the override will respect the object’s disposability.
- After disposal, I thought it might be nice to take the variable out of scope like C#/VB. Using
-scope script
will look at variables in the scope where our function was called, and since we don’t know what$inputObject
was named before it was passed in, I just compare references instead.
- Wrap script execution in
- Otherwise just punt the object along in the pipeline
Usage
Loading assemblies is pretty straightforward:
using System.Windows.Forms using 'System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' using C:\Dev\DisposeTest\bin\Debug\DisposeTest.dll
To test IDisposable
handling, I use a simple disposable object which returns its hash code in ToString()
, instantiated by a helper function:
function new-disp { new-object DisposeTest.Disposable }
To verify that variable scope is handled properly, we need two test scripts. gv
is an alias for Get-Variable
.
NestedTest.ps1
gv x using ($x = new-disp) { gv x } gv x
UsingTest.ps1
$x = 'X' .\NestedTest.ps1 using ($y = new-disp) { gv y } gv y
From the behavior in C#/VB, we expect that the object being ‘used’ will only be available within the scope of the script block. So when we enter NestedTest.ps1, we should see the $x
remains ‘X’, inherited from the parent scope, both before and after the using
statement. Similarly, we expect $y
will not be accessible outside of the using
block:
SharePoint Example
using Microsoft.SharePoint using ($s = new-object Microsoft.SharePoint.SPSite('http://localhost/')) { $s.AllWebs | %{ using ($_) { $_ | select Title, Url } } } if($s -eq $null) { 'Success!' }
It’s not exceedingly friendly for interactive mode, particularly for tab completion, but it should aid script readability.
December 27, 2008 at 10:51 pm
[…] Keith expands these techniques to flush out a more general purpose solution. […]
February 16, 2009 at 10:42 pm
[…] My Using Function […]
June 1, 2009 at 12:18 pm
thanks homey!!
-d
April 12, 2010 at 7:54 am
I am attempting to use your “Using” function with Powershell v2.0 and continually get an error with the following message:
“The ‘using’ keyword is not supported in this version of the language.”
I have put the “Using” function in my local profile; any ideas?
April 12, 2010 at 6:33 pm
I’ve meant to update this post for a while… PowerShell 2 added a number of reserved, but unimplemented, keywords, including
using
. You will need to name the function something different, perhapsUse-Object
with an alias ofuse
?April 13, 2010 at 10:22 am
Thank you, worked great!
June 14, 2010 at 7:39 am
Also, in PowerShell 2.0 the Add-Type cmdlet has an -Assembly parameter set which is an implementation of Lee Holmes’ suggestion for resolving assemblies with a map instead of using LoadWithPartialName, so you could try falling back to that before using the deprecated LoadWithPartialName.