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
$inputObjectis 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
$inputObjectisIDisposableand a script block was supplied…- Wrap script execution in
Try..Finallyto 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 scriptwill look at variables in the scope where our function was called, and since we don’t know what$inputObjectwas 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.


