Question

I'm trying to find some way of flexibly altering/substituting pipeline elements in PowerShell:

Function Where-DirectlyReportsTo {
    Param (
        [Parameter(
            ValueFromPipeline = $true,
            HelpMessage = "The ADUser object to be tested"
        )]
        [Microsoft.ActiveDirectory.Management.ADUser] $ADUser,
        [Parameter( 
            Mandatory = $true,
            ValueFromPipeline = $false,
            Position = 0
        )]
        [String] $mgrDN
    )
    Process {
        If ($ADUser) {
            If ($ADUser.Manager -eq $mgrDN) { Return $ADUser }
        }
    }
}

$Properties = @("Manager")
$users = Get-ADUser -Filter * -SearchBase $OU -Properties $Properties
[ScriptBlock] $sb = {Where-DirectlyReportsTo "CN=Colonel Foobar,$OU"}
$DNs = $users | $sb | %{$_.DistinguishedName}

Which I want to return the DNs of all the users that report to Colonel Foobar, but it gives me the error Expressions are only allowed as the first element of a pipeline.

This is a trivial example, but I'd ultimately like to be able to put the pipeline step inside a loop and pass it different ScriptBlocks to get different sets of users, or use more complicated ScriptBlocks (e.g.: {Where-IsEmployee | Where-IsInDepartment "Finance" | Where-LikesIceCream}).

I realize that I may be going about this all wrong, and I would very much appreciate being pointed in the right direction.

EDIT: To clarify, here's a rough outline of what I'd like to accomplish:

[ScriptBlock[]] $arrBlocks = @( # lots of different cases
)
ForEach ($sb In $arrBlocks) {
    $DNs = $users | $sb | %{$_.DistinguishedName}
    # Then do something with the DNs
}

Realistically, this will probably involve a hash table instead of an array, so that I know what to do with each set of results.

Was it helpful?

Solution 2

There needs to be something at the head of the pipeline in your scriptblock and you have to define your scriptblock as taking pipeline input.g.:

[scriptBlock]$sb = {[CmdletBinding()]param([Parameter(ValueFromPipeline=$true)]$obj) `
                   process {
                       foreach ($o in $obj) {
                           $o | Where-DirectlyReportsTo "CN=Colonel Foobar,$OU"}}}

You also can't throw $sb into the pipeline quite like that, try this:

$users | &$sb | %{$_.DistinguishedName}

OTHER TIPS

The syntax error is simple:

# Wrong:
$DNs = $users | $sb | %{$_.DistinguishedName}

# Correct; note the call operator in front of $sb:
$DNs = $users | &$sb | %{$_.DistinguishedName}

This still leaves the matter of your pipeline hitting a dead end. You don't need to get fancy here; just pipe $Input along to the next function:

[ScriptBlock] $sb = {$Input | Where-DirectlyReportsTo "CN=Colonel Foobar,$OU"}

You shouldn't have $sb itself enumerate the input. That's extra overhead, and if you use param(), bumps the script block up to a cmdlet. You really don't need that.

In fact, you could simplify this whole thing down to four lines, or even one really long line:

$properties = @("Manager")
$managers = @(
    "CN=Colonel Foobar,$OU"
    "CN=Sergeant Foobar,$OU"
    "CN=Random Manager,$OU"
)

$users = Get-ADUser -Filter * -SearchBase $OU -Properties $properties
$DNs = $users | ?{ $managers -contains $_.Manager } | %{ $_.DistinguishedName }

I tested that with this code:

$OU = 'OU=test'
$users = @(
    @{
        Manager = "CN=Colonel Foobar,$OU";
        DistinguishedName = "Show me!"
    }
    @{
        Manager = "CN=Anon Y Mous,$OU";
        DistinguishedName = "Don't show me!"
    }
    'rabbit'
    42
    $null
    @{
        DistinguishedName = "Don't show me, either!"
    }
    @{
        Manager = "CN=Random Manager,$OU";
        DistinguishedName = "Show me, too!"
    }
)

$managers = @(
    "CN=Colonel Foobar,$OU"
    "CN=Sergeant Foobar,$OU"
    "CN=Random Manager,$OU"
)
$DNs = $users | ?{ $managers -contains $_.Manager } | %{ $_.DistinguishedName }

$DNs | Write-Host

You could make it a bit more verbose, if you wanted to:

$properties = @("Manager")
$managers = @(
    "CN=Colonel Foobar,$OU"
    "CN=Sergeant Foobar,$OU"
    "CN=Random Manager,$OU"
)

$filter = { $managers -eq $_.Manager }
$selector = { $_.DistinguishedName }

$users = Get-ADUser -Filter * -SearchBase $OU -Properties $properties
$DNs = $users | ?{ &$filter } | %{ &$selector }

You sound like you want to eventually have multiple filters. This is pretty easy, too:

$properties = @("Manager")
$managers = @(
    "CN=Colonel Foobar,$OU"
    "CN=Sergeant Foobar,$OU"
    "CN=Random Manager,$OU"
)

$filters = @(
    { $managers -contains $_.Manager }
    { ![string]::IsNullOrWhiteSpace($_.DistinguishedName) }
)
$selector = { $_.DistinguishedName }

$users = Get-ADUser -Filter * -SearchBase $OU -Properties $properties
$DNs = $users | ?{ $filters.Invoke() -notcontains $false } | %{ &$selector }

In PS 2.0 this somewhat more concise (readable?) syntax works. (doesn't work in PS 3.0, where setting isFilter throws an exception)

$filterSet = 
  {$_.property -eq "desired value"}, 
  {$_.method() -eq "I like the return value"},
  {$_.gettype() -eq [special.type.I.like]}, 
  {arbritraryBoolPSfunction $_}

$filterSet | % {$_.isFilter = $true} # make 'em all filters

ForEach ($filter In $filterSet) {
  $DNs = $users | & $filter | %{$_.DistinguishedName}
  # Then do something with the DNs
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top