Skip to content

Conversation

@KirkMunro
Copy link
Contributor

@KirkMunro KirkMunro commented Nov 6, 2019

PR Summary

Like PR's #10984 and #10073, this PR enhances the splatting capabilities in PowerShell.

With this PR in place, users are able to splat members (properties and methods) as well as indexed items into an invocation. It doesn't matter how many levels deep you go with your expression.

Examples:

# Splat a property
Get-Date @data.Parameters

# Splat the results of a method
Get-Date @data.GetParams()

# Splat a nested property
Get-Process @PSCmdlet.MyInvocation.BoundParameters

# Splat a property from the result of a method
Test-Splat @result.GetParameterSets().Value

# Splat an item from a collection
Test-Splat @parameterSet[0]

The only question I have is whether or not this functionality needs to be behind an experimental feature. If you review the code changes, you'll see that adding this support was very, very easy, and I believe the changes are low risk, so I don't know that it is worth the added complexity of wrapping these in an experimental feature.

PR Context

This functionality has already been identified as desirable for PowerShell as part of RFC0002.

PR Checklist

@KirkMunro KirkMunro requested a review from daxian-dbw as a code owner November 6, 2019 22:53
@KirkMunro KirkMunro changed the title Add member splatting support Add property, method, and indexed item splatting support Nov 7, 2019
@KirkMunro

This comment has been minimized.

@PoshChan
Copy link
Collaborator

PoshChan commented Nov 7, 2019

@KirkMunro, successfully started retry of PowerShell-CI-Windows

@iSazonov iSazonov requested a review from lzybkr November 7, 2019 09:58
Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to add more tests that splatting (old and new) works (like Get-Date @DaTa and Get-Date @data.Parameters )

@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Nov 7, 2019
@iSazonov iSazonov added this to the 7.0.0-preview.6 milestone Nov 7, 2019
@KirkMunro
Copy link
Contributor Author

I think it makes sense to add more tests that splatting (old and new) works (like Get-Date @DaTa and Get-Date @data.Parameters )

Agreed. I added some basic splatting tests since we didn't have specific tests in place already.

@iSazonov
Copy link
Collaborator

@KirkMunro Please fix CIs failures.

@KirkMunro
Copy link
Contributor Author

@PoshChan please retry all

@PoshChan
Copy link
Collaborator

@KirkMunro, successfully started retry of PowerShell-CI-static-analysis, PowerShell-CI-Windows, PowerShell-CI-macOS, PowerShell-CI-Linux

@iSazonov
Copy link
Collaborator

Should we update RFC0002 for the syntax?

@KirkMunro
Copy link
Contributor Author

@iSazonov Someone can if they want to. I shared the syntax 3+ years ago, before that RFC was approved, but @lzybkr didn't add it to the RFC at the time.

@iSazonov iSazonov added the Review - Committee The PR/Issue needs a review from the PowerShell Committee label Nov 29, 2019
@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Dec 11, 2019
@joeyaiello
Copy link
Contributor

@PowerShell/powershell-committee reviewed this, and while we don't see a technical risk or breaking change, we're having trouble coming up with a compelling, real-world scenario to justify adding it to the language.

Especially considering Test-Splat @PSBoundParameters works for the 3rd case, are there some scripting conveniences that can actually be gained from the other 4 examples you gave? In all the cases we could think of, what you have seems less clear than if you were to simply assign the value to another variable.

@KirkMunro
Copy link
Contributor Author

KirkMunro commented Dec 12, 2019

The 3rd case is exactly the scenario where this is needed, because it doesn't always work. For example:

function Test-Splat {
    [CmdletBinding()]
    param(
        [parameter(mandatory)]
        [string]
        $Name
    )
    $sb = {Get-Service @PSBoundParameters}
    & $sb
}
Test-Splat -Name wuauserv

If you run that command, you'll get all services. Why? Because $PSBoundParameters variable is current scope only. So if you're using nested scopes or script blocks to reuse code within a function, and you want to splat in bound parameters, you cannot. You instead need to create an intermediate variable to store $PSCmdlet.MyInvocation.BoundParameters and then splat that variable, which is ironic given that I believe $PSBoundParameters was added to make parameter processing (and splatting of parameters) easier. I've had to use a temporary variable to achieve this many, many times in scripts and I continue to have to do it today. This is the most common scenario that drove me to add this to the language.

As a best practice, I don't use $PSBoundParameters in scripts because it is only accurate in the current scope, and I don't like minor refactoring risks to introduce unexpected issues so I use $PSCmdlet.MyInvocation.BoundParameters everywhere because it gives me consistency no matter where I use it, so I can refactor with confidence.

Here is an example demonstrating what I would have to do instead if I wanted to make what I shared above work today:

function Test-Splat {
    [CmdletBinding()]
    param(
        [parameter(mandatory)]
        [string]
        $Name
    )
    $sb = {
        $cmdletParams = $PSCmdlet.MyInvocation.BoundParameters
        Get-Service @cmdletParams
    }
    & $sb
}
Test-Splat -Name wuauserv

The use of intermediate variables here feels unnecessary and it's just extra code to maintain. I want to be able to accomplish the same like this:

function Test-Splat {
    [CmdletBinding()]
    param(
        [parameter(mandatory)]
        [string]
        $Name
    )
    $sb = {
        Get-Service @PSCmdlet.MyInvocation.BoundParameters
    }
    & $sb
}
Test-Splat -Name wuauserv

The other scenarios are added because it makes much more sense to me syntactically to be able to use the new syntax, which is very clear in intent, than to add an overly complex splat-subexpression syntax such as @$($PSCmdlet.MyInvocation.BoundParameters) to the language, as was suggested in the RFC.

Further, and quite important (so make sure you read/get this), these changes open the door for something else that is needed with splatting. One thing missing from splatting is the ability to perform a partial hashtable splat. Splat in one or more parameters, and only if they are present as keys in the hashtable. Splat in all parameters in the hashtable except for one or more parameters. Those scenarios (manipulating hashtables to get a subset hashtable easily without having to write complicated code) have benefits that extend beyond splatting and are useful elsewhere in scripts. By extending the splatting syntax to support member invocation and indices, we're opening the door for hashtable manipulation to be added independently of splatting, as it should be, such that splatting can just support it once it is added (maybe for free, maybe with a minor addition, depending on the syntax used for hashtable manipulation). If this is not clear, let me know and I'll go into more detail.

This need, and the justification for the syntax shared here, is crystal clear to me, keeping splatting focused on exactly what it was designed to do (take an array or a hashtable and splat those values into an invocation), while leveraging other current functionality (accessing members, accessing an indexed item) and new functionality (hashtable manipulation) using the same syntax that you would use elsewhere.

PowerShell is object-oriented, with properties and methods, as well as indexable collections. When working with variables, we can access properties and methods and indexed items in collections with ease. Why should we have to use a different syntax to do so when splatting, or inject intermediate variables, when we can simply change a $ before a variable name to a @ and as a result be able to splat whatever that would return, in a more natural syntax that we're already familiar with, without having to use subexpressions where subexpressions are normally not required?

For some reason, when it comes to splatting improvements such as inline splatting and subexpression splatting and the discussions surrounding it, there seems to be a strong bias for new, obtuse syntax over syntax that maintains consistency with how things have already been written and used in PowerShell since v1, and I just don't get why. At any rate, I'll keep pushing improvements into the language the way I see them being most consistent and a best fit for what's already there, and this PR is an example of just that.

@dragonwolf83
Copy link

Funny enough, I ran into this just the other day.

With the WinSCP PSModule there are parameters for HostName, Port, SshPrivateKeyPath, etc. They all match perfectly for Splatting. However, when you have multiple servers, you have to namespace them.

Ideally, I would do this:

$config = @{
  QA = @{
    Host = @{
      HostName = 'abc'
      Port = 22
    }
  Prod = @{
    Host = @{
      HostName = 'abc'
      Port = 22
    }
  }
}

New-WinSCPOption @config.QA.Host
New-WinSCPOption @config.Prod.Host

Today, I would have to store both of those configurations into another variable first to use them:

$qaConfig = $config.QA.Host
$prodConfig = $config.Prod.Host

New-WinSCPOption @qaConfig
New-WinSCPOption @prodConfig

It just adds more work when I could have just splat directly to them.

@adityapatwardhan adityapatwardhan added Review - Committee The PR/Issue needs a review from the PowerShell Committee and removed Committee-Reviewed PS-Committee has reviewed this and made a decision labels Apr 13, 2020
@adityapatwardhan
Copy link
Member

@PowerShell/powershell-committee Please re-review as the author has responded.

@joeyaiello
Copy link
Contributor

@dragonwolf83 we in the @PowerShell/powershell-committee don't really understand why, in the 1st example where you set a variable for the params, is unclear

Unfortunately, we didn't have @BrucePay today in our @PowerShell/powershell-committee meeting, would love to get his take as well: as @JamesWTruher raised, this is all very tied to the parameter binder, and he may have an opinion there.

Similarly, @KirkMunro, I'm really not seeing why the 2nd example is fundamentally more clear or concise to justify the additional complexity. I won't pretend to understand exactly in what way this is enabling future hashtable manipulation features, but we should establish first that those features are useful before leading with an implementation that we're not sure is a pre-requisite. You may have posted about those somewhere we haven't yet gotten to, but the value that said features would enable should be the front of the pipeline.

@SteveL-MSFT
Copy link
Member

@PowerShell/powershell-committee reviewed this, we are ok with the intent. We ask that the tests be increased to cover multiple values rather than splatting a single value.

@SteveL-MSFT SteveL-MSFT added Committee-Reviewed PS-Committee has reviewed this and made a decision and removed Review - Committee The PR/Issue needs a review from the PowerShell Committee labels Apr 29, 2020
@ghost ghost added the Review - Needed The PR is being reviewed label May 27, 2020
@ghost
Copy link

ghost commented May 27, 2020

This pull request has been automatically marked as Review Needed because it has been there has not been any activity for 7 days.
Mainainer, Please provide feedback and/or mark it as Waiting on Author

@KirkMunro
Copy link
Contributor Author

Closing. See #10238 (comment).

@KirkMunro KirkMunro closed this Apr 24, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log Committee-Reviewed PS-Committee has reviewed this and made a decision Review - Needed The PR is being reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants