DarkLite1
DarkLite1

Reputation: 14705

Parameter ValueFromPipeline and changing position

I'm having some issues with defining the position of parameters. For example, when I pipe a $Folder to my function, the positions of the other parameters change.

Normally the paramter $Folder has position=0, but when something is piped to the function it's not taking into account that position 0 is before the pipeline symbol an no longer behind it.

The function can be used in all cases with or without the switch $Recurse and the $Inheritance set to On/Off. The main purpose is to set permissions or to set the inheritance on a folder. A combination of both is also possible.

[CmdletBinding(SupportsShouldProcess=$True,DefaultParametersetName="SetPermissions")]
Param(
    [parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({Test-Path $_ -PathType Container})]
    [String[]]$Folder,

    [parameter(Mandatory=$true,Position=1,ParameterSetName='SetPermissions')]
    [parameter(Mandatory=$false,ParameterSetName='SetInheritance')]
    [ValidateNotNullOrEmpty()]
    [String]$SAMaccountName,

    [parameter(Mandatory=$true,Position=2,ParameterSetName='SetPermissions')]
    [parameter(Mandatory=$false,ParameterSetName='SetInheritance')]
    [ValidateNotNullOrEmpty()]
    [ValidateSet('ReadAndExecute','Modify','FullControl','ListFolderContents')]
    [String]$Grant,

    [parameter(Mandatory=$false)]
    [ValidateNotNullOrEmpty()]
    [String]$Domain = 'MyDomain',

    [parameter(Mandatory=$false,Position=3,ParameterSetName='SetPermissions')]
    [parameter(Mandatory=$true,Position=1,ParameterSetName='SetInheritance')]
    [ValidateNotNullOrEmpty()]
    [ValidateSet('On','Off')]
    [String]$Inheritance,

    [Switch]$Recurse
)

All the options that are possible:

# SetPermissions
Set-ACLspecial -Folder 'C:\Folder' -SAMaccountName 'Bob' -Grant ReadAndExecute
Set-ACLspecial -Folder 'C:\Folder' -SAMaccountName 'Bob' -Grant ReadAndExecute -Recurse
Set-ACLspecial -Folder 'C:\Folder' -SAMaccountName 'Bob' -Grant ReadAndExecute -Inheritance On
Set-ACLspecial -Folder 'C:\Folder' -SAMaccountName 'Bob' -Grant ReadAndExecute -Inheritance On -Recurse

Set-ACLspecial 'C:\Folder' 'Bob' ReadAndExecute
Set-ACLspecial 'C:\Folder' 'Bob' ReadAndExecute -Recurse
Set-ACLspecial 'C:\Folder' 'Bob' ReadAndExecute On
Set-ACLspecial 'C:\Folder' 'Bob' ReadAndExecute On -Recurse

'C:\Folder' | Set-ACLspecial 'Bob' ReadAndExecute
'C:\Folder' | Set-ACLspecial 'Bob' ReadAndExecute -Recurse
'C:\Folder' | Set-ACLspecial 'Bob' ReadAndExecute On
'C:\Folder' | Set-ACLspecial 'Bob' ReadAndExecute On -Recurse

# SetInheritance
Set-ACLspecial 'C:\Folder' -Inheritance Off
Set-ACLspecial 'C:\Folder' -Inheritance Off -Recurse
'C:\Folder' | Set-ACLspecial -Inheritance Off
'C:\Folder' | Set-ACLspecial -Inheritance Off -Recurse

The current syntax:

Set-HCaclTEST [-Folder] <String[]> [-SAMaccountName] <String> [-Grant] <String> [-Domain <String>] [[-Inheritance] <String>] [-Recurse] [-WhatIf] [-Confirm] [<Common
Parameters>]

Set-HCaclTEST [-Folder] <String[]> [-SAMaccountName <String>] [-Grant <String>] [-Domain <String>] [-Inheritance] <String> [-Recurse] [-WhatIf] [-Confirm] [<CommonPa
rameters>]

The error:

'C:\Folder' | Set-ACLspecial 'Bob' ReadAndExecute
# Test-Path on 'Bob' is not working... PowerShell sees Bob as the variable `$Folder`, but it's not.

Upvotes: 2

Views: 3651

Answers (2)

Matt
Matt

Reputation: 46710

Hopefull you can use this to follow the logic used in parameter binding. Its an excerpt from Windows Powershell in Action By Bruce Payette.

  1. Bind all named parameters - Find all unquoted tokens on the command line that start with a dash. If the token ends with a colon, an argument is required. If there’s no colon, look at the type of the parameter and see if an argument is required. Convert the type of actual argument to the type required by the parameter, and bind the parameter.
  2. Bind all positional parameters -If there are any arguments on the command line that haven’t been used, look for unbound parameters that take positional parameters and try to bind them.
  3. Bind from the pipeline by value with exact match- If the command is not the first command in the pipeline and there are still unbound parameters that take pipeline input, try to bind to a parameter that matches the type exactly.
  4. If not bound, then bind from the pipe by value with conversion. - If the previous step failed, try to bind using a type conversion.
  5. If not bound, then bind from the pipeline by name with exact match - If the previous step failed, look for a property on the input object that matches the name of the parameter. If the types exactly match, bind the parameter.
  6. If not bound, then bind from the pipeline by name with conversion. If the input object has a property whose name matches the name of a parameter, and the type of the property is convertible to the type of the parameter, bind the parameter.

Following this I hope you can determine what is happening, and in what order, with your routine.

In the example 'C:\Folder' | Set-ACLspecial 'Bob' ReadAndExecute 'Bob' should be assigned to $Folder since positional is bound before pipeline.

Upvotes: 2

Aaron Jensen
Aaron Jensen

Reputation: 26749

The Position value is only used to match unnamed parameters. Parameters passed via a pipeline are named, so are ignored when doing this calculation. In your example, the first unnamed parameter is Bob, so that gets assigned to parameter $Folder, because that parameter's position is 0. Best practice is to always use parameter names in your scripts. It makes them easier to understand. Use positional parameters when on the console to save yourself keystrokes.

From what I've seen in the PowerShell base cmdlets, parameters that take pipeline input usually aren't also positional. (Probably for just this reason.) Parameters that can be piped in must be passed by name when not passed via the pipeline. Remove Position=0 from the $Folder parameter.

    [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
    [ValidateNotNullOrEmpty()]
    [ValidateScript({Test-Path $_ -PathType Container})]
    [String[]]
    $Folder,

Upvotes: 6

Related Questions