Home > database >  Powershell: How to access iterated object in catch-block
Powershell: How to access iterated object in catch-block

Time:02-15

I'm trying to simplify the situation as much as possible.

I have a set of files in that needs to be processed in a certain order because some of them are dependant on each other. But I don't have any reliable means to find out if a file has its dependencies fullfilled before processing it. - What i do have is an external function that throws an error if I try to process a file too early. Thats why I'm trying to iterate though those files until all of them have been processed.

(those files contain so called "extensions" if you wonder about the variable names.)

What I'm trying to do is, catching the Files that are not able to get published to the server, yet in the "catch" area and start the while loop over with the remaining set until all files are processed or the script reached its deadloop limiter of 20 loops.

$path = 'C:\abcd'
  
$ExtList = (Get-ChildItem $path -Filter '*.app' -Recurse).FullName

$i = 0 #stop deadloop

while (($ExtList.Count -gt 0) -and ($i -lt 20)) {
    $i  
    [array]$global:FailedExt = @()
    [array]$global:FailedExtError = @()

    $ExtList | % {
        try {
            Publish-Extension -Path $_ 
        } catch {
            $global:FailedExt  = $_
            $global:FailedExtError  = $Error[0]
        }
    }

    #Set ExtList to list of remaining Extensions
    $ExtList = $global:FailedExt
}

if ($global:FailedExt.Count -gt 0) { #dependencies weren't the Problem
    $ErrorMsg = "Could not publish the following Extensions:`n`n"
    for ($i = 0; $i -lt $FailedExt.Count; $i  ) {
        $ErrorMsg  = "Error in: "   ($global:FailedExt[$i])   ":`n"
        $ErrorMsg  = " - Error: "   ($global:FailedExtError[$i])   "`n`n"
    }
    throw $ErrorMsg
}

MY Problem: Apparently the value of $_ in the catch-block isn't the same as in the try-block because i get This error output:

Could not publish the following Extensions:

Error in: Cannot bind parameter 'Path' to the target. Exception setting "Path": "Illegal characters in path.":
 - Error: Cannot bind parameter 'Path' to the target. Exception setting "Path": "Illegal characters in path."

Error in: Cannot bind parameter 'Path' to the target. Exception setting "Path": "Illegal characters in path.":
 - Error: Cannot bind parameter 'Path' to the target. Exception setting "Path": "Illegal characters in path."

Error in: Cannot bind parameter 'Path' to the target. Exception setting "Path": "Illegal characters in path.":
 - Error: Cannot bind parameter 'Path' to the target. Exception setting "Path": "Illegal characters in path."

And then, on the second iteration, the script tries to use the Error-Message as a Path to the file I'm trying to publish. - Which results in a set of "illegal characters in path"-Messages.

p.s. If you know any other improvements to my code, let me know (I'm new to PS)

CodePudding user response:

Fwiw, I'm curious to know the same thing: if there's a way to avoid the $_ replacement or preserve or access the prior value in the exception context.

That said, I do know you can work around it by assigning $_ to a new variable as the first line of the loop:

$ExtList | % {
    try {
        $loopItem = $_
        Publish-Extension -Path $loopItem 
    } catch {
        # $_ is the exception, $loopItem is still good
        $global:FailedExt  = $loopItem
        $global:FailedExtError  = $Error[0]
    }
}

CodePudding user response:

In a catch block, the $_ automatic variable refers to the current error record, so it hides the $_ variable that refers to the current item processed by ForEach-Object (alias %).

You may use a foreach statement to control the name of the loop variable:

foreach( $CurrentPath in $ExtList ) {
    try {
        Publish-Extension -Path $CurrentPath 
    } catch {
        $global:FailedExt  = $CurrentPath
        $global:FailedExtError  = $_    # Add current error record
    }
}

Note I also replaced $Error[0] by $_ which is the idiomatic way to access error information in a catch block.


A (less readable) way that works with ForEach-Object is to use the automatic variable $input, which refers to the current pipeline object of a process {} block (actually it is a single-element array as discovered by mklement0). This works here because the script block passed to ForEach-Object runs in the process {} block by default (unless you use parameter -begin or -end).

$ExtList | % {
    try {
        Publish-Extension -Path $_ 
    } catch {
        $global:FailedExt  = $Input    # Add current ForEach-Object item
        $global:FailedExtError  = $_   # Add current error record
    }
}

Without the comment the code isn't self-explanatory as the $input variable is rarely used. If you really have to stick with ForEach-Object, I recommend to assign $_ to another variable, as explained by Joel Coehoorn's helpful answer.


Just for educational purposes I tried to find another way using Get-Variable with parameter -Scope, which allows you to get a variable of the same name from a parent scope. As catch doesn't create a new child scope, we have to artificially create a child scope using a script block (& {}):

$ExtList | % {
    & {         # A script block starts a new child scope
        try {
            Publish-Extension -Path $_ 
        } catch {
            # Get the $_ variable from the parent scope (the loop)
            $global:FailedExt  = Get-Variable _ -Scope 1 -ValueOnly
            $global:FailedExtError  = $_   # Add current error record
        }
    }           
}

While this works, I don't recommend to use this technique. Compared to the first solution, the code looks almost obfuscated and is very hard to follow.

  • Related