HiveBrain v1.2.0
Get Started
← Back to all entries
patternshellMinor

Enforcing Paired Statements Using ScriptBlock

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
enforcingstatementspairedusingscriptblock

Problem

I really like c#'s Using statement as it allows you to pair Open statements with their corresponding Close/Dispose statements in an elegant way which matches C#'s syntax.
I realised that something similar can be achieved in PowerShell; e.g. Push-Location should be paired with a Pop-Location, and this can be enforced by creating a function which allows you to use a USING-like syntax.

e.g.

function PushDPopD {
    [CmdletBinding()]
    param (
        [string]$Path
        , 
        [scriptblock]$Code
    )
    process {
        if (Push-Location $Path -PassThru) { #only proceed with this function's logic if our pushd were successful
            try {
                $Code.Invoke()
            } finally { #ensure that whatever happens in user code, we always popd after our pushd
                Pop-Location
            }
        }
    }
}
function ShowCurrentLocation {
    ("You are in: {0}" -f (Get-Location).Path)
}

Clear-Host
[string]$localVariable = "We keep variables even in scriptblock context"

write-host "`n`n==Healthy Demo==`n`n" -ForegroundColor cyan
ShowCurrentLocation 
PushDPopD 'c:\Users' {
    "hello"
    ShowCurrentLocation
    $localVariable  
    "bye"
}
ShowCurrentLocation 

write-host "`n`n==Exception Demo==`n`n" -ForegroundColor cyan
ShowCurrentLocation 
PushDPopD 'c:\ThisPathDoesNotExist' {
    "hello"
    ShowCurrentLocation 
    $localVariable 
    "bye"
}
ShowCurrentLocation


To me this seems like a logical approach / looks neater than manually checking that all pushes are correctly paired with pops.
The only potential issue is if someone includes a popd in the scriptblock itself; but that's no worse than the original scenario, so isn't a big drawback.
This also gives us the advantage that code won't blindly run on thinking it's in a different directory to the one specified if the pushd fails.

As a concept does this seem logical / have I missed anything.

NB: I'm aware it's not currently following verb-noun conve

Solution

To be honest I feel this might be overcomplicating things.

I use Push-Location/Pop-Location in try/finally blocks when needed (like when loading the SQLPS module), but that's almost never.

For the most part I think one should avoid changing the current location and instead reference the desired path (either relative or absolute). So something like:

$p = $BasePath | Join-Path -ChildPath 'subfolder'
Get-ChildItem -Path $p


rather than:

$p = $BasePath | Join-Path -ChildPath 'subfolder'
Push-Location $p
try {
    Get-ChildItem
} finally {
    Pop-Location
}


As you've seen, there are also some issues with using scriptblocks in that way. You might have scope issues with variables, and call stack/execution context will be a bit different. This might get complicated further when you consider pipelines.

So in summary I'd rather avoid changing the working directory and then not obfuscate the process when I do so it's clear what's going on.

Code Snippets

$p = $BasePath | Join-Path -ChildPath 'subfolder'
Get-ChildItem -Path $p
$p = $BasePath | Join-Path -ChildPath 'subfolder'
Push-Location $p
try {
    Get-ChildItem
} finally {
    Pop-Location
}

Context

StackExchange Code Review Q#110550, answer score: 4

Revisions (0)

No revisions yet.