From 50a0f6c0e1da0d09fa255b7f97dedfc26986c3fa Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Thu, 19 Oct 2023 17:00:23 -0500 Subject: [PATCH 1/8] Created RFC0066 --- .../RFC0066-RealTime-Parameter-Bind.md | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md diff --git a/Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md b/Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md new file mode 100644 index 00000000..34eab0aa --- /dev/null +++ b/Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md @@ -0,0 +1,83 @@ +--- +RFC: RFC0066 +Author: Darren Kattan, James Brundage +Status: Draft +Area: Dynamic Parameter Binding +Version: 1.0 +Comments Due: 2023-12-19 +Plan to implement: Yes +--- + +# Incremental Binding of RuntimeDefinedParameter in Dynamicparam + +Facilitate PowerShell's dynamic parameter block by allowing `RuntimeDefinedParameter` objects to bind arguments incrementally. This will enhance the ability to use previously bound values to refine subsequent parameters dynamically. + +## Motivation + + As a PowerShell script or module author, + I can use the bound values of one parameter to filter the choices for subsequent parameters, + so that I can offer dynamic and contextual choices to users and enhance the user experience. + +## User Experience + +Suppose a user wants to interactively install different versions of Microsoft Edge, filtered by Product, Platform, and Architecture. With this proposed change, once the user selects a Product, the subsequent options for Platform are filtered based on that selection. Once a Platform is selected, the Architecture choices are then filtered, and so forth. + +The sample code below demonstrates how this experience might look with the proposed changes: + +```powershell +param() +dynamicparam +{ + New-ParameterCollection -Parameters @( + $EdgeVersions = Invoke-RestMethod https://edgeupdates.microsoft.com/api/products + New-RuntimeDefinedParameter -Name Product -ValidValues $EdgeVersions.Product -Type ([string]) -Mandatory + if($Product) + { + $EdgeVersions = $EdgeVersions | ?{$_.Product -eq $Product} + } + $Platforms = $EdgeVersions | %{ $_.Releases } | select -Unique -Expand Platform + New-RuntimeDefinedParameter -Name Platform -ValidValues $Platforms -Type ([string]) -Mandatory + if($Platform) + { + $EdgeVersions = $EdgeVersions | ?{$_.Releases.Platform -eq $Platform} + } + $Architectures = $EdgeVersions.Releases.Architecture | select -Unique | sort + New-RuntimeDefinedParameter -Name Architecture -ValidValues $Architectures -Type ([string]) -Mandatory + if($Architecture) + { + $EdgeVersions = $EdgeVersions | ?{$_.Releases.Architecture -eq $Architecture} + } + New-RuntimeDefinedParameter -Name VersionToInstall -ValidValues ($EdgeVersions.Releases.ProductVersion | select -Unique) -Type ([string]) -Mandatory + ) +} +end +{ +} +``` + +Upon execution, users will be prompted incrementally: + +```output +Product: [List of Products] +Platform: [Filtered List of Platforms] +Architecture: [Filtered List of Architectures] +VersionToInstall: [Filtered List of Versions] +``` + +## Specification + +1. **Incremental Binding**: As `RuntimeDefinedParameter` objects are written to the output in the `dynamicparam` block, they are immediately bound if an argument is available. This behavior differs from the current state, where all parameters are bound after the entire `dynamicparam` block has executed. + +2. **Variable Creation**: When a `RuntimeDefinedParameter` is bound, a variable with the name of that parameter should be created in the `dynamicparam` block scope. This makes the bound value immediately available for subsequent logic in the `dynamicparam` block. + +3. **Backward Compatibility**: This change must not break scripts or modules that use the `dynamicparam` block but do not expect incremental binding. It's crucial to ensure the new behavior doesn't adversely affect existing implementations. + +## Alternate Proposals and Considerations + +1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce a flag or a switch parameter to `New-RuntimeDefinedParameter` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. + +2. **Dynamic Binding Callbacks**: Another approach might be to allow a scriptblock callback to be attached to a `RuntimeDefinedParameter`. This callback would execute when the parameter is bound, providing more granular control over the binding process. + +3. **Performance Considerations**: Depending on the implementation, there could be performance implications. The exact performance impact should be assessed during the development phase, and optimizations should be considered to ensure that this feature doesn't introduce significant overhead. + +Please note that the above proposal is a starting point and would benefit from feedback, especially concerning backward compatibility and potential pitfalls not identified in this draft. \ No newline at end of file From 783c074910de742fbdcba8016dfe63af29af1216 Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Thu, 19 Oct 2023 17:03:08 -0500 Subject: [PATCH 2/8] Removed number, placed in Draft folder --- .../RealTime-Parameter-Bind.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md => Draft/RealTime-Parameter-Bind.md (99%) diff --git a/Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md b/Draft/RealTime-Parameter-Bind.md similarity index 99% rename from Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md rename to Draft/RealTime-Parameter-Bind.md index 34eab0aa..3e7e5ecb 100644 --- a/Draft-Accepted/RFC0066-RealTime-Parameter-Bind.md +++ b/Draft/RealTime-Parameter-Bind.md @@ -1,5 +1,5 @@ --- -RFC: RFC0066 +RFC: RFC Author: Darren Kattan, James Brundage Status: Draft Area: Dynamic Parameter Binding From d10a68084f23e903fb08e7e9a807337783ac6b8f Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Thu, 19 Oct 2023 17:35:08 -0500 Subject: [PATCH 3/8] Updated example to use current syntax --- Draft/RealTime-Parameter-Bind.md | 113 ++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/Draft/RealTime-Parameter-Bind.md b/Draft/RealTime-Parameter-Bind.md index 3e7e5ecb..d220f475 100644 --- a/Draft/RealTime-Parameter-Bind.md +++ b/Draft/RealTime-Parameter-Bind.md @@ -22,46 +22,107 @@ Facilitate PowerShell's dynamic parameter block by allowing `RuntimeDefinedParam Suppose a user wants to interactively install different versions of Microsoft Edge, filtered by Product, Platform, and Architecture. With this proposed change, once the user selects a Product, the subsequent options for Platform are filtered based on that selection. Once a Platform is selected, the Architecture choices are then filtered, and so forth. -The sample code below demonstrates how this experience might look with the proposed changes: +The sample code below demonstrates how this experience _might_ look with the proposed changes: ```powershell -param() -dynamicparam -{ - New-ParameterCollection -Parameters @( - $EdgeVersions = Invoke-RestMethod https://edgeupdates.microsoft.com/api/products - New-RuntimeDefinedParameter -Name Product -ValidValues $EdgeVersions.Product -Type ([string]) -Mandatory - if($Product) - { - $EdgeVersions = $EdgeVersions | ?{$_.Product -eq $Product} - } - $Platforms = $EdgeVersions | %{ $_.Releases } | select -Unique -Expand Platform - New-RuntimeDefinedParameter -Name Platform -ValidValues $Platforms -Type ([string]) -Mandatory - if($Platform) - { - $EdgeVersions = $EdgeVersions | ?{$_.Releases.Platform -eq $Platform} - } +$EdgeVersions = Invoke-RestMethod https://edgeupdates.microsoft.com/api/products + +$productParamAttributes = [Collection[Attribute]]@( + ([ValidateSet]::new($EdgeVersions.Product)), + ([Parameter]@{ Mandatory = $true }) +) +[RuntimeDefinedParameter]::new('Product', [string], $productParamAttributes) + +if($Product) +{ + $EdgeVersions = $EdgeVersions | ?{$_.Product -eq $Product} + $Platforms = $EdgeVersions | %{ $_.Releases } | select -Unique -Expand Platform + + $platformParamAttributes = [Collection[Attribute]]@( + ([ValidateSet]::new($Platforms)), + ([Parameter]@{ Mandatory = $true }) + ) + [RuntimeDefinedParameter]::new('Platform', [string], $platformParamAttributes) + + if($Platform) + { + $EdgeVersions = $EdgeVersions | ?{$_.Releases.Platform -eq $Platform} $Architectures = $EdgeVersions.Releases.Architecture | select -Unique | sort - New-RuntimeDefinedParameter -Name Architecture -ValidValues $Architectures -Type ([string]) -Mandatory + + $architectureParamAttributes = [Collection[Attribute]]@( + ([ValidateSet]::new($Architectures)), + ([Parameter]@{ Mandatory = $true }) + ) + [RuntimeDefinedParameter]::new('Architecture', [string], $architectureParamAttributes) + if($Architecture) { - $EdgeVersions = $EdgeVersions | ?{$_.Releases.Architecture -eq $Architecture} + $EdgeVersions = $EdgeVersions | ?{$_.Releases.Architecture -eq $Architecture} + $versionToInstallValues = $EdgeVersions.Releases.ProductVersion | select -Unique + + $versionParamAttributes = [Collection[Attribute]]@( + ([ValidateSet]::new($versionToInstallValues)), + ([Parameter]@{ Mandatory = $true }) + ) + [RuntimeDefinedParameter]::new('Version', [string], $versionParamAttributes) } - New-RuntimeDefinedParameter -Name VersionToInstall -ValidValues ($EdgeVersions.Releases.ProductVersion | select -Unique) -Type ([string]) -Mandatory - ) -} -end -{ + } } ``` - Upon execution, users will be prompted incrementally: ```output Product: [List of Products] Platform: [Filtered List of Platforms] Architecture: [Filtered List of Architectures] -VersionToInstall: [Filtered List of Versions] +Version: [Filtered List of Versions] +``` + +The syntax could be even more concise with user-supplied helper function `New-Parameter` +```powershell +function New-Parameter { + param( + [string]$Name, + [Type]$Type = [object], + [string[]]$ValidValues, + [switch]$Mandatory + ) + + $attributes = [Collection[Attribute]]@( + ([ValidateSet]::new($ValidValues)), + ([Parameter]@{ Mandatory = $Mandatory }) + ) + + return [RuntimeDefinedParameter]::new($Name, $Type, $attributes) +} + +Function Get-EdgeUpdates +{ + param() + dynamicparam + { + $EdgeVersions = Invoke-RestMethod https://edgeupdates.microsoft.com/api/products + New-Parameter -Name Product -ValidValues $EdgeVersions.Product -Type ([string]) -Mandatory + if($Product) + { + $EdgeReleases = $EdgeVersions | ?{$_.Product -eq $Product} | select -Expand Releases # Beta,Stable,Dev,Canary + New-Parameter -Name Platform -ValidValues ($EdgeReleases.Platform | select -Unique) -Type ([string]) -Mandatory # Windows,Linux,MacOS,Android + if($Platform) + { + $EdgeReleases = $EdgeReleases | ?{$_.Platform -eq $Platform} + New-Parameter -Name Architecture -ValidValues ($EdgeReleases.Architecture | select -Unique) -Type ([string]) -Mandatory # x86,x64,arm64,universal + if($Architecture) + { + $EdgeReleases = $EdgeReleases | ?{$_.Architecture -eq $Architecture} + New-Parameter -Name Version -ValidValues ($EdgeReleases.ProductVersion | select -Unique) -Type ([string]) -Mandatory + } + } + } + } + end + { + } +} ``` ## Specification From d07a4e7dd6cb94fb28a11bd8cfc9b32fb19473e5 Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Thu, 19 Oct 2023 17:45:36 -0500 Subject: [PATCH 4/8] Renamed to Incremental binding --- ...RealTime-Parameter-Bind.md => Incremental-Parameter-Bind.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Draft/{RealTime-Parameter-Bind.md => Incremental-Parameter-Bind.md} (96%) diff --git a/Draft/RealTime-Parameter-Bind.md b/Draft/Incremental-Parameter-Bind.md similarity index 96% rename from Draft/RealTime-Parameter-Bind.md rename to Draft/Incremental-Parameter-Bind.md index d220f475..a3a3aa4c 100644 --- a/Draft/RealTime-Parameter-Bind.md +++ b/Draft/Incremental-Parameter-Bind.md @@ -135,7 +135,7 @@ Function Get-EdgeUpdates ## Alternate Proposals and Considerations -1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce a flag or a switch parameter to `New-RuntimeDefinedParameter` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. +1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce a. IncrementalBind flag to `CmdletBindingAttribute` or create an `IncrementalBindingAttribute` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. 2. **Dynamic Binding Callbacks**: Another approach might be to allow a scriptblock callback to be attached to a `RuntimeDefinedParameter`. This callback would execute when the parameter is bound, providing more granular control over the binding process. From 14ff229243f89f6134e43bab331177945f561462 Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Thu, 19 Oct 2023 17:48:27 -0500 Subject: [PATCH 5/8] Typo --- Draft/Incremental-Parameter-Bind.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Draft/Incremental-Parameter-Bind.md b/Draft/Incremental-Parameter-Bind.md index a3a3aa4c..a8cf30ca 100644 --- a/Draft/Incremental-Parameter-Bind.md +++ b/Draft/Incremental-Parameter-Bind.md @@ -135,7 +135,7 @@ Function Get-EdgeUpdates ## Alternate Proposals and Considerations -1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce a. IncrementalBind flag to `CmdletBindingAttribute` or create an `IncrementalBindingAttribute` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. +1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce an `IncrementalBind` flag to `CmdletBindingAttribute` or create an `IncrementalBindingAttribute` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. 2. **Dynamic Binding Callbacks**: Another approach might be to allow a scriptblock callback to be attached to a `RuntimeDefinedParameter`. This callback would execute when the parameter is bound, providing more granular control over the binding process. From e1cce53b3b3ef240a0ffaeefed3e4926589bcf21 Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Fri, 20 Oct 2023 05:30:59 -0500 Subject: [PATCH 6/8] Refined example and Considerations --- Draft/Incremental-Parameter-Bind.md | 94 +++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/Draft/Incremental-Parameter-Bind.md b/Draft/Incremental-Parameter-Bind.md index a8cf30ca..686ae525 100644 --- a/Draft/Incremental-Parameter-Bind.md +++ b/Draft/Incremental-Parameter-Bind.md @@ -20,7 +20,65 @@ Facilitate PowerShell's dynamic parameter block by allowing `RuntimeDefinedParam ## User Experience -Suppose a user wants to interactively install different versions of Microsoft Edge, filtered by Product, Platform, and Architecture. With this proposed change, once the user selects a Product, the subsequent options for Platform are filtered based on that selection. Once a Platform is selected, the Architecture choices are then filtered, and so forth. +Suppose a user wants to write a Get-LatestAvailableMicrosoftEdgeVersion function using data available from https://edgeupdates.microsoft.com/api/products + +The data is JSON but is structured as followed + +1. Product: Stable + - Platform: Windows + - Architecture: x64 + - msi: 118.0.2088.57 + - Architecture: arm64 + - msi: 118.0.2088.57 + - Architecture: x86 + - msi: 118.0.2088.57 + - Platform: Linux + - Architecture: x64 + - rpm: 118.0.2088.57 + - deb: 118.0.2088.57 + - Platform: MacOS + - Architecture: universal + - pkg: 118.0.2088.57 + - plist: 118.0.2088.57 + - Platform: Android + - Architecture: arm64 + - Version: 118.0.2088.58 + - Platform: iOS + - Architecture: arm64 + - Version: 118.0.2088.60 + +2. Product: Beta + - Platform: iOS + - Architecture: arm64 + - Version: 118.0.2088.36 + - Platform: Linux + - Architecture: x64 + - rpm: 119.0.2151.12 + - deb: 119.0.2151.12 + - Platform: Windows + - Architecture: arm64 + - msi: 119.0.2151.12 + - Architecture: x64 + - msi: 119.0.2151.12 + - Architecture: x86 + - msi: 119.0.2151.12 + - Platform: MacOS + - Architecture: universal + - plist: 119.0.2151.12 + - pkg: 119.0.2151.12 + - Platform: Android + - Architecture: arm64 + - Version: 119.0.2151.11 + + +To get the version number the function would require the following parameters: +- Product +- Platform +- Architecture (Not Applicable for Android and iOS) +- PackageType (Applicable for Linux to disambiguate rpm/deb and Windows to disambiguate msi/exe) + + +With this proposed change, once the user selects a Product and Platform, Architecture and PackageType options are conditionally required based on the API data. The sample code below demonstrates how this experience _might_ look with the proposed changes: @@ -137,8 +195,36 @@ Function Get-EdgeUpdates 1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce an `IncrementalBind` flag to `CmdletBindingAttribute` or create an `IncrementalBindingAttribute` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. -2. **Dynamic Binding Callbacks**: Another approach might be to allow a scriptblock callback to be attached to a `RuntimeDefinedParameter`. This callback would execute when the parameter is bound, providing more granular control over the binding process. +2. **Expose BindParameter method in dynamicparam block**: Syntax could look like +```powershell +dynamicparam +{ + $ProductParam = [RuntimeDefinedParameter]::new('Product', [string], $productParamAttributes) + + $_.BindParameter($ProductParam) + $Product = $PSBoundParameters['Product'] + # or + $Product = $this.BindParameter($ProductParam) + # or + if($_.TryBindParameter($ProductParam, out $Product)) + { + ... + } +} +``` + +1. **Performance Considerations**: Depending on the implementation, there could be performance implications. The exact performance impact should be assessed during the development phase, and optimizations should be considered to ensure that this feature doesn't introduce significant overhead. + +2. **Discoverability and Documentation Considerations**: Perhaps the most reasonable objection to this RFC is discoverability of incrementally bound parameters by Get-Help. For this I propose we either: + - Do nothing as this is already a problem with dynamic parameters that are conditionally present based off the bound value of static parameters + - `Get-Help` could append `This function has dynamic parameters and may require additional parameters not documented here, supply -ArgumentList parameter for additional details` to commands that implement dynamic/incrementally bound parameters + + The syntax could look like `Get-Help MyIncrementallyBindableFunction -Product Edge` + + This is similar to --help or /h options for many command line tools where you can get details about sub-commands. -3. **Performance Considerations**: Depending on the implementation, there could be performance implications. The exact performance impact should be assessed during the development phase, and optimizations should be considered to ensure that this feature doesn't introduce significant overhead. + This would be relatively easy to wire up as + 1. Get-Help doesn't have any parameters with ValueFromRemainingArguments + 2. We could pass -ArgumentList to Get-Command to within Get-Help so the incrementally bound parameters become available -Please note that the above proposal is a starting point and would benefit from feedback, especially concerning backward compatibility and potential pitfalls not identified in this draft. \ No newline at end of file +Please note that the above proposal is a starting point and would benefit from feedback, especially concerning discoverability and performance. \ No newline at end of file From 5098b61ec77ce73fa4ca9dbe77275afda36923e0 Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Fri, 20 Oct 2023 15:24:21 -0500 Subject: [PATCH 7/8] Fixed formatting --- Draft/Incremental-Parameter-Bind.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Draft/Incremental-Parameter-Bind.md b/Draft/Incremental-Parameter-Bind.md index 686ae525..27a306b3 100644 --- a/Draft/Incremental-Parameter-Bind.md +++ b/Draft/Incremental-Parameter-Bind.md @@ -221,10 +221,11 @@ dynamicparam The syntax could look like `Get-Help MyIncrementallyBindableFunction -Product Edge` - This is similar to --help or /h options for many command line tools where you can get details about sub-commands. + This is similar to `--help` or `/h` options for many command line tools where you can get details about sub-commands. - This would be relatively easy to wire up as - 1. Get-Help doesn't have any parameters with ValueFromRemainingArguments - 2. We could pass -ArgumentList to Get-Command to within Get-Help so the incrementally bound parameters become available + This would be relatively easy to wire up as: -Please note that the above proposal is a starting point and would benefit from feedback, especially concerning discoverability and performance. \ No newline at end of file + 1. `Get-Help` doesn't have any parameters with `ValueFromRemainingArguments` + 2. We could pass `-ArgumentList` to `Get-Command` to within `Get-Help` so the incrementally bound parameters become visible + +Please note that the above proposal is a starting point and would benefit from feedback, especially concerning discoverability and performance. From 06e4b9e063506811bac6d3fba63658a07568bd4f Mon Sep 17 00:00:00 2001 From: Darren Kattan Date: Fri, 20 Oct 2023 15:25:08 -0500 Subject: [PATCH 8/8] Formatting --- Draft/Incremental-Parameter-Bind.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Draft/Incremental-Parameter-Bind.md b/Draft/Incremental-Parameter-Bind.md index 27a306b3..e9a638a9 100644 --- a/Draft/Incremental-Parameter-Bind.md +++ b/Draft/Incremental-Parameter-Bind.md @@ -195,7 +195,7 @@ Function Get-EdgeUpdates 1. **Explicit Flag for Incremental Binding**: Instead of changing the default behavior, introduce an `IncrementalBind` flag to `CmdletBindingAttribute` or create an `IncrementalBindingAttribute` that enables incremental binding. This would allow script authors to opt-in to the new behavior explicitly. -2. **Expose BindParameter method in dynamicparam block**: Syntax could look like +2. **Expose BindParameter() in dynamicparam block**: Syntax could look like ```powershell dynamicparam {