Skip to content

Add $PSBoundParametersObject and support for Splatting PSCustomObjects #6889

Closed

Description

There are often scenarios where you need $PSBoundParameters but with some parameter(s) excluded, some renamed, and others possibly added to get the correct parameters needed for another function your calling.

There are many existing ways to manipulate $PSBoundParameters but they are not intuitive due to the type of $PSBoundParameters.

If we had $PSBoundParametersObject we could do things like:

$ParametersNeededToCallFunction = $PSBoundParametersObject | 
Select-Object -Property <Properties> -ExcludeProperty <ExcludedProperties> | 
Add-Member -MemberType NoteProperty -Name Path -Value "C:\Path" -PassThru |
Add-Member -MemberType AliasProperty -Name ComputerName -Value IPAddress

Adding the ability to splat PSCustomOjbects we could then do:

Invoke-Something @ParametersNeededToCallFunction

This would allow better knowledge reuse as someone who takes the time to learn Select-Object and Add-Memeber now can intuitively deal with bound parameters.

This would be better than having to learn about and deal with a lot of the quirks related to System.Management.Automation.PSBoundParametersDictionary which are not as generally applicable.

Activity

  1. ChrisMagnuson commented on May 17, 2018

    @ChrisMagnuson
    Author

    In case it helps provide context, I use a helper function ConvertFrom-BSBoundParameters to accomplish a similar style of coding now.

  2. added
    WG-Languageparser, language semantics
    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif
    on May 18, 2018
  3. vexx32 commented on May 19, 2018

    @vexx32
    Collaborator

    This is a neat idea, but I must say this should have a slightly shorter name!

  4. BrucePay commented on May 24, 2018

    @BrucePay
    Collaborator

    I'm not sure I see how this is a win. Because $PSBoundParameters is essentially just a hashtable, all the normal things you do with hashtables just work. Adding a property is simple assignment:

    $PSBoundParameters.Name = "c:\Path"
    $PSBoundParameters.ComputerName = $ipaddress
    

    and removing a property is done with the Remove() method.

    $PSBoundParameters.Remove("propertytoremove")
    

    This seems simpler than messing around with Select-Object and Add-Member and will definitely be much faster. What am I missing?

  5. ChrisMagnuson commented on Aug 10, 2018

    @ChrisMagnuson
    Author

    Bruce Payette (@BrucePay) I have taken what you have said to heart and been writing code since you posted this in the style you proposed as I figured doing it this way would help me better explain the difficulties with this pattern.

    Some observations:

    1. $PSBoundParameters.Remove("propertytoremove") returns True or False depending on whether the property was present so in practice you always have to remember to do something like | Out-Null or you will likely break code calling your function
    2. If we had $PSBoundParametersObject the code we write feels cleaner and more clear
      1. Below are two examples of the same function that ends up calling another function that makes a web service call that needs to have some parameters in the body of the request, some in the query string, and some to change the behavior of the function making the request (like setting authorization headers).

    Using $PSBoundParameters as it is today

    function New-FreshDeskTicket {
        param (
            $name,
            $requester_id,
            $email,
            $facebook_id,
            $phone,
            $twitter_id,
            $unique_external_id,
            $subject,
            #.. 17 more parameters go here but removed for brevity
            $Credential,
            $SubDomain
        )
    
        $InvokeAPIParameters = @{}
        $PSBoundParameters.Keys | 
        Where-Object Name -In "Credential","SubDomain" | 
        ForEach-Object { 
            $InvokeAPIParameters.Add($_, $PSBoundParameters.$_)
        }
    
        $QueryStringParameters = @{}
        $PSBoundParameters.Keys | 
        Where-Object Name -In "requester_id","unique_external_id" | 
        ForEach-Object { 
            $QueryStringParameters.Add($_, $PSBoundParameters.$_)
        }
    
        $PSBoundParameters.Remove("Credential") | Out-Null
        $PSBoundParameters.Remove("Domain") | Out-Null
        
        Invoke-FreshDeskAPI -Body $PSBoundParameters -QueryString $QueryStringParameters -Resource tickets -Method Post @InvokeAPIParameters
    }
    

    Using $PSBoundParametersObject

    function New-FreshDeskTicket {
        param (
            $name,
            $requester_id,
            $email,
            $facebook_id,
            $phone,
            $twitter_id,
            $unique_external_id,
            $subject,
            #.. 17 more parameters go here but removed for brevity
            $Credential,
            $SubDomain
        )
        $InvokeAPIParameters = $PSBoundParametersObject | Select-Object -Property Credential,SubDomain    
        $QueryStringParameters = $PSBoundParametersObject | Select-Object -Property requester_id,unique_external_id
        $BodyParameters = $PSBoundParametersObject | Select-Object -ExcludeProperty Credential,Domain,requester_id,unique_external_id
    
        #When https://github.com/PowerShell/PowerShell/issues/7049 is resolved we won't need the following if statement
        if (-Not $InvokeAPIParameters) {
            $InvokeAPIParameters = [PSCustomObject]@{} 
        }
        
        Invoke-FreshDeskAPI -Body $BodyParameters -QueryString $QueryStringParameters -Resource tickets -Method Post @InvokeAPIParameters
    }
    
  6. dragonwolf83 commented on Aug 11, 2018

    @dragonwolf83

    You can pretty much do everything in your PSBoundParametersObject example if you use .GetEnumerator(). The code is just as simple as yours:

    function New-FreshDeskTicket {
        param (
            $name,
            $requester_id,
            $email,
            $facebook_id,
            $phone,
            $twitter_id,
            $unique_external_id,
            $subject,
            #.. 17 more parameters go here but removed for brevity
            $Credential,
            $SubDomain
        )
    	
        $InvokeAPIParameters = $PSBoundParameters.GetEnumerator() | Where-Object Key -In "Credential", "SubDomain"
        $QueryStringParameters = $PSBoundParameters.GetEnumerator() | Where-Object Key -In "requester_id", "unique_external_id"
        $BodyParameters = $PSBoundParameters.GetEnumerator() | Where-Object Key -NotIn @($InvokeAPIParameters.key + $QueryStringParameters.Key)
    	
        if( -not $InvokeAPIParameters ) {
            Invoke-FreshDeskAPI -Body $BodyParameters -QueryString $QueryStringParameters -Resource tickets -Method Post @InvokeAPIParameters
        }
    }
    
    New-FreshDeskTicket -Credential "TestCred" -SubDomain "TestDomain" -requester_id "ID1" -unique_external_id "EXID1" -name "TestName" -email "TestEmail"
  7. ChrisMagnuson commented on Aug 13, 2018

    @ChrisMagnuson
    Author

    Troy Campbell (@dragonwolf83) I am having trouble getting an example working the way your describing things should:

    PS > function Test-PSBoundGetEnumerator { param ($test1,$test2) $PSBoundParameters.GetEnumerator() | where key -in Test1,Test2 }
    PS > $Variable = Test-PSBoundGetEnumerator -test1 value1 -test2 value2                                           
    PS > $Variable                                                                                                   
    
    Key   Value
    ---   -----
    test1 value1
    test2 value2
    
    PS > $Variable | gm                                                                                              
    
    
       TypeName: System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=7cec85d7bea7798e]]
    
    Name        MemberType Definition
    ----        ---------- ----------
    Deconstruct Method     void Deconstruct([ref] string key, [ref] System.Object value)
    Equals      Method     bool Equals(System.Object obj)
    GetHashCode Method     int GetHashCode()
    GetType     Method     type GetType()
    ToString    Method     string ToString()
    Key         Property   string Key {get;}
    Value       Property   System.Object Value {get;}
    
       TypeName: System.Object[]
    
    Name           MemberType            Definition
    ----           ----------            ----------
    Add            Method                int IList.Add(System.Object value)
    Address        Method                System.Object&, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7c...
    Clear          Method                void IList.Clear()
    Clone          Method                System.Object Clone(), System.Object ICloneable.Clone()
    CompareTo      Method                int IStructuralComparable.CompareTo(System.Object other, System.Collections.IComparer compa...
    Contains       Method                bool IList.Contains(System.Object value)
    CopyTo         Method                void CopyTo(array array, int index), void CopyTo(array array, long index), void ICollection...
    Equals         Method                bool Equals(System.Object obj), bool IStructuralEquatable.Equals(System.Object other, Syste...
    Get            Method                System.Object Get(int )
    GetEnumerator  Method                System.Collections.IEnumerator GetEnumerator(), System.Collections.IEnumerator IEnumerable....
    GetHashCode    Method                int GetHashCode(), int IStructuralEquatable.GetHashCode(System.Collections.IEqualityCompare...
    GetLength      Method                int GetLength(int dimension)
    GetLongLength  Method                long GetLongLength(int dimension)
    GetLowerBound  Method                int GetLowerBound(int dimension)
    GetType        Method                type GetType()
    GetUpperBound  Method                int GetUpperBound(int dimension)
    GetValue       Method                System.Object GetValue(Params int[] indices), System.Object GetValue(int index), System.Obj...
    IndexOf        Method                int IList.IndexOf(System.Object value)
    Initialize     Method                void Initialize()
    Insert         Method                void IList.Insert(int index, System.Object value)
    Remove         Method                void IList.Remove(System.Object value)
    RemoveAt       Method                void IList.RemoveAt(int index)
    Set            Method                void Set(int , System.Object )
    SetValue       Method                void SetValue(System.Object value, int index), void SetValue(System.Object value, int index...
    ToString       Method                string ToString()
    Item           ParameterizedProperty System.Object IList.Item(int index) {get;set;}
    Count          Property              int Count {get;}
    IsFixedSize    Property              bool IsFixedSize {get;}
    IsReadOnly     Property              bool IsReadOnly {get;}
    IsSynchronized Property              bool IsSynchronized {get;}
    Length         Property              int Length {get;}
    LongLength     Property              long LongLength {get;}
    Rank           Property              int Rank {get;}
    SyncRoot       Property              System.Object SyncRoot {get;}
    
    PS > Test-PSBoundGetEnumerator @Variable                                                                         
    
    Key   Value
    ---   -----
    test1 [test1, value1]
    test2 [test2, value2]
    
    PS > $VariableHash = @{test1 = "value1"; test2 = "value2"}                                                                                                                                      
    PS > Test-PSBoundGetEnumerator @VariableHash                                                                     
    
    Key   Value
    ---   -----
    test2 value2
    test1 value1
    
    

    It doesn't appear that splatting a System.Object[] of System.Collections.Generic.KeyValuePair``2[[System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]] behaves the same way as splatting a System.Collections.Hashtable.

  8. dragonwolf83 commented on Aug 13, 2018

    @dragonwolf83

    Your right, it is including the key with the value as the value. In theory, the ToDictionary LINQ Extension Method should convert it back to a workable state but don't know if that is working yet in 6.0+.

    Maybe Splatting just needs to be updated to handle passing the KeyValuePair type directly and handle that weirdness for us.

  9. ChrisMagnuson commented on Aug 13, 2018

    @ChrisMagnuson
    Author

    Troy Campbell (@dragonwolf83) Just for information's sake, the above test was with PowerShell version 6.1.0-preview.2.

    I think your intuition about how this should work not aligning with how it actually works is further evidence that this does not align with the principle of least astonishment and that things would be better/less surprising if we had an object to work with and could splat objects instead of dealing with the idiosyncrasies of dictionaries/hashtables for this particular purpose.

  10. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  11. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  12. microsoft-github-policy-service commented on Nov 16, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

  13. microsoft-github-policy-service commented on Nov 23, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifResolution-No ActivityIssue has had no activity for 6 months or moreWG-Languageparser, language semantics

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions