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.
In case it helps provide context, I use a helper function ConvertFrom-BSBoundParameters to accomplish a similar style of coding now.
This is a neat idea, but I must say this should have a slightly shorter name!
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?
@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:
$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$PSBoundParametersObject the code we write feels cleaner and more clearfunction 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
}
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
}
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"
@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.
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.
@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.
Most helpful comment
I'm not sure I see how this is a win. Because
$PSBoundParametersis essentially just a hashtable, all the normal things you do with hashtables just work. Adding a property is simple assignment:and removing a property is done with the
Remove()method.This seems simpler than messing around with
Select-ObjectandAdd-Memberand will definitely be much faster. What am I missing?