Is it possible to pipe conditionally in Powershell, i.e. execute an element of a pipeline only if a condition is met?

Adi Inbar picture Adi Inbar · Jul 24, 2012 · Viewed 11.8k times · Source

I want to do something like this:

<statement> | <filter1> | <filter2> if <condition> | <filter3> | <filter4> | <filter5>

The results of <statement> run through <filter1>, then they run through <filter2> only if <condition> is met, then through the remaining filters regardless of whether <filter2> was applied. This is the equivalent of:

if (<condition>) {
  <statement> | <filter1> | <filter2> | <filter3> | <filter4> | <filter5>
} else {
  <statement> | <filter1> | <filter3> | <filter4> | <filter5>
}

This would be useful in functions where a given filter is applied to the result set only if a certain switch was invoked. If the conditional filter occurs early in a long pipeline, writing it with an outer if-block results in a lot of repetition of code, especially if there is more than one conditional filter.

Here's an example. The following function shows the permissions a given account has in a given directory subtree (e.g. Show-AccountPerms \\SERVERX\Marketing DOMAIN\jdoe gives a report of permissions that the user DOMAIN\jdoe has in the directory tree under \SERVERX\Marketing).

function Show-AccountPerms {
    param (
        [parameter(mandatory = $true)]$rootdir,
        [parameter(mandatory = $true)]$account,
        [switch]$files,
        [switch]$inherited
    )
    gci -r $rootdir `
    |where {$_.psiscontainer} `
    |foreach {
        $dir = $_.fullname
        (get-acl $_.pspath).access `
        | where {$_.isinherited -eq 'False'} `
        |foreach {
            if ($_.identityreference -eq $account) {
                "{0,-25}{1,-35}{2}" -f $_.identityreference, $_.filesystemrights, $dir
            }
        }
    }
}

By default, it only shows explicit permissions (enforced by the | where {$_.isinherited -eq 'False'} filter), and only on directories (enforced by the |where {$_.psiscontainer} filter).

However, I want to ignore |where {$_.psiscontainer} if the -files switch is invoked, and ignore | where {$_.isinherited -eq 'False'} if the -inherited switch is invoked. Accomplishing this with outer if blocks would quadruple the code, and almost 75% of it would be repetition. Is there a way to keep these filters in-line but instruct powershell to only apply them of the corresponding switch is false?

Please note that this is just an example, so I'm not interested in any workarounds specific to this function. I'm looking for an answer to my general question regarding piping conditionally, not a solution for how to accomplish this particular task.

Answer

zdan picture zdan · Jul 24, 2012

You can test for both conditions in your filter allowing the object down the pipeline if either one is true. If your "condition" is on the left side of the -or operator, make it result to $true if you don't want your filter condition tested.

To use your example:

| where {$_.psiscontainer}

becomes:

| where {$files -or $_.psiscontainer}

and

| where {$_.isinherited -eq 'False'}

becomes

| where {$inherited -or $_.isinherited -eq 'False'}

To generalise:

<statement> | <filter1> | <filter2> if <condition> | <filter3> | <filter4> | <filter5>

becomes:

<statement> | <filter1> | <-not condition -or filter2> | <filter3> | <filter4> | <filter5>