|
| 1 | +--- |
| 2 | +RFC: '0058' |
| 3 | +Author: Kirk Munro |
| 4 | +Status: Rejected |
| 5 | +SupercededBy: |
| 6 | +Version: 0.1 |
| 7 | +Area: Engine |
| 8 | +Comments Due: September 15, 2019 |
| 9 | +Plan to implement: Yes |
| 10 | +--- |
| 11 | + |
| 12 | +# Optional features in PowerShell |
| 13 | + |
| 14 | +How do you resolve longstanding issues in PowerShell that either cause bugs to |
| 15 | +show up in unexpected places or that make PowerShell much more difficult to |
| 16 | +program with than it should be, when resolving those issues would introduce |
| 17 | +breaking changes that would impact existing automation solutions? |
| 18 | + |
| 19 | +You could use Semantic Versioning, where breaking changes can be added as |
| 20 | +long as the major version is incremented; however, in an interpreted language |
| 21 | +with a community of users that expects backwards compatibility, those types of |
| 22 | +changes are often unappreciated. |
| 23 | + |
| 24 | +Another approach is to take an opt-in approach to breaking changes, where users |
| 25 | +can optionally accept breaking changes for their automated solutions. |
| 26 | + |
| 27 | +This RFC is about enabling those scenarios where breaking changes are deemed |
| 28 | +valuable enough to offer as an option in PowerShell. |
| 29 | + |
| 30 | +NOTE: It may appear up front that the Experimental Features functionality in |
| 31 | +PowerShell solves this need, but that is not the case. Experimental Features |
| 32 | +are features under development while feedback on the design and usage data is |
| 33 | +gathered. While they may include breaking changes, they are not intended as a |
| 34 | +way to enable breaking changes. |
| 35 | + |
| 36 | +As an example of a features that would most likely be optional if implemented, |
| 37 | +consider how command execution preferences could be propagated beyond script or |
| 38 | +script module scope, or how to make terminating errors properly terminate |
| 39 | +without requiring try/catch scaffolding to get the correct behavior. Both of |
| 40 | +those issues have separate RFCs that include implementation as an optional |
| 41 | +feature. |
| 42 | + |
| 43 | +## Motivation |
| 44 | + |
| 45 | +As a script, function, or module author,<br/> |
| 46 | +I can enable optional features for specific users or in specific scopes,<br/> |
| 47 | +so that I can leverage new functionality that may break existing scripts without risk. |
| 48 | + |
| 49 | +## User experience |
| 50 | + |
| 51 | +```powershell |
| 52 | +# Create a module manifest, specifically enabling or disabling one or more optional |
| 53 | +# features in the manifest. An optional feature cannot be in both -OptionalFeatures |
| 54 | +# and -DisabledOptionalFeatures. |
| 55 | +$manifest = New-ModuleManifest ` |
| 56 | + -Path ./test.psd1 ` |
| 57 | + -OptionalFeatures OptionalFeature1 ` |
| 58 | + -DisabledOptionalFeatures OptionalFeature2 ` |
| 59 | + -PassThru |
| 60 | +$manifest | Get-Content |
| 61 | +
|
| 62 | +# Output: |
| 63 | +# |
| 64 | +# @{ |
| 65 | +# |
| 66 | +# <snip> |
| 67 | +# |
| 68 | +# # Private data to pass to the module specified in RootModule/ModuleToProcess. |
| 69 | +# # This may also contain a PSData hashtable with additional module metadata |
| 70 | +# # used by PowerShell. |
| 71 | +# PrivateData = @{ |
| 72 | +# |
| 73 | +# <snip> |
| 74 | +# |
| 75 | +# PSData = @{ |
| 76 | +# |
| 77 | +# # Optional features enabled in this module. |
| 78 | +# OptionalFeatures = @( |
| 79 | +# 'OptionalFeature1' |
| 80 | +# ) |
| 81 | +# |
| 82 | +# # Optional features disabled in this module. |
| 83 | +# DisabledOptionalFeatures = @( |
| 84 | +# 'OptionalFeature2' |
| 85 | +# ) |
| 86 | +# |
| 87 | +# <snip> |
| 88 | +# |
| 89 | +# } # End of PSData hashtable |
| 90 | +# |
| 91 | +# <snip> |
| 92 | +# |
| 93 | +# } # End of PrivateData hashtable |
| 94 | +# |
| 95 | +# } |
| 96 | +
|
| 97 | +# Create a script file, enabling or disabling one or more optional features in the file |
| 98 | +@' |
| 99 | +#requires -OptionalFeature OptionalFeature1 |
| 100 | +#requires -OptionalFeature OptionalFeature2 -Disabled |
| 101 | +
|
| 102 | +<snip> |
| 103 | +'@ | Out-File -FilePath ./test.ps1 |
| 104 | +
|
| 105 | +# Get a list of optional features, whether or not they are automatically enabled, |
| 106 | +# their source, and their descriptions |
| 107 | +Get-OptionalFeature |
| 108 | +
|
| 109 | +# Output: |
| 110 | +# |
| 111 | +# Name AutoEnable Source Description |
| 112 | +# ---- ---------- ------ ----------- |
| 113 | +# OptionalFeature1 CurrentUser PSEngine Description of optional feature 1 |
| 114 | +# OptionalFeature2 AllUsers PSEngine Description of optional feature 2 |
| 115 | +# OptionalFeature3 No PSEngine Description of optional feature 3 |
| 116 | +# OptionalFeature4 No PSEngine Description of optional feature 4 |
| 117 | +
|
| 118 | +# Enable an optional feature in current PowerShell session. |
| 119 | +Enable-OptionalFeature -Name OptionalFeature1 |
| 120 | +
|
| 121 | +# Output: |
| 122 | +# None |
| 123 | +
|
| 124 | +# Enable an optional feature in the current PowerShell session |
| 125 | +# and future PowerShell sessions for all users. |
| 126 | +Enable-OptionalFeature -Name OptionalFeature1 -AutoEnable AllUsers |
| 127 | +
|
| 128 | +# Output: |
| 129 | +# None |
| 130 | +
|
| 131 | +# Enable an optional feature in the current PowerShell session |
| 132 | +# and future PowerShell sessions for the current user. |
| 133 | +Enable-OptionalFeature -Name OptionalFeature1 -AutoEnable CurrentUser |
| 134 | +
|
| 135 | +# Output: |
| 136 | +# None |
| 137 | +
|
| 138 | +# Disable an optional feature in the current PowerShell session. |
| 139 | +Disable-OptionalFeature -Name OptionalFeature2 |
| 140 | +
|
| 141 | +# Output: |
| 142 | +# None |
| 143 | +
|
| 144 | +# Enable and/or disable an optional feature the duration of the |
| 145 | +# script block being invoked. |
| 146 | +Use-OptionalFeature -Enable OptionalFeature1 -Disable OptionalFeature2 -ScriptBlock { |
| 147 | + # Do things using OptionalFeature1 here |
| 148 | + # OptionalFeature2 cannot be used here |
| 149 | +} |
| 150 | +# If OptionalFeature1 was not enabled before this invocation, it |
| 151 | +# is still no longer enabled here. If OptionalFeature2 was enabled |
| 152 | +# before this invocation, it is still enabled here. All to say, |
| 153 | +# their state before the call is preserved. |
| 154 | +
|
| 155 | +# Returns true if the optional feature is enabled in the scope in |
| 156 | +# which the command was invoked; false otherwise. |
| 157 | +Test-OptionalFeature -Name OptionalFeature1 |
| 158 | +``` |
| 159 | + |
| 160 | +## Specification |
| 161 | + |
| 162 | +Unlike experimental features, which can only be enabled or disabled in |
| 163 | +PowerShell sessions created after enabling or disabling them, optional features |
| 164 | +can be: |
| 165 | + |
| 166 | +- enabled or disabled in the current PowerShell session; |
| 167 | +- enabled in future PowerShell sessions; |
| 168 | +- enabled or disabled in a specific module or script scope; |
| 169 | + |
| 170 | +This allows certain functionality to be "lit up" in packaged modules or scripts. |
| 171 | + |
| 172 | +Below you will find details describing how this functionality will be implemented. |
| 173 | + |
| 174 | +### System and User powershell.config.json configuration files |
| 175 | + |
| 176 | +Enabling optional features automatically in future PowerShell sessions requires |
| 177 | +creating or updating one of two `powershell.config.json` configuration files |
| 178 | +that are read on startup of a new PowerShell session: |
| 179 | + |
| 180 | +* one in `$PSHOME`, which applies to all user sessions |
| 181 | +* one in `$HOME\Documents\PowerShell\powershell.config.json` on Windows or |
| 182 | +`$HOME/.config/powershell/powershell.config.json` on Linux and macOS, which |
| 183 | +applies only to current user sessions. |
| 184 | + |
| 185 | +This RFC will enable optional feature defaults to be read from these |
| 186 | +configuration files, with current user configuration taking precedence over |
| 187 | +system (all users) configuration. System config is not policy so this should be |
| 188 | +acceptable and expected. |
| 189 | + |
| 190 | +### Add parameters in New-ModuleManifest |
| 191 | + |
| 192 | +`[-OptionalFeatures <string[]>]` |
| 193 | + |
| 194 | +This parameter would enable specific optional features in the new module |
| 195 | +manifest that is generated. |
| 196 | + |
| 197 | +The values provided to this parameter would be added to a module manifest under |
| 198 | +a new `OptionalFeatures` key. This key will be part of `PSData` to maintain |
| 199 | +backwards compatibility. When the module is loaded in a version of PowerShell |
| 200 | +that supports optional features, any optional features named in this key will |
| 201 | +be enabled in the module scope. |
| 202 | + |
| 203 | +A terminating error is generated if the same optional feature name is used |
| 204 | +twice in the collection passed into the `-OptionalFeatures` parameter. |
| 205 | + |
| 206 | +`[-DisabledOptionalFeatures <string[]>]` |
| 207 | + |
| 208 | +This parameter would disable specific optional features in the new module |
| 209 | +manifest that is generated. |
| 210 | + |
| 211 | +The values provided to this parameter would be added to a module manifest under |
| 212 | +a new `DisabledOptionalFeatures` key. This key will be part of `PSData` to |
| 213 | +maintain backwards compatibility. When the module is loaded in a version of |
| 214 | +PowerShell that supports optional features, any optional features named in this |
| 215 | +key will be disabled in the module scope. |
| 216 | + |
| 217 | +Allowing a feature to be disabled within a module is necessary if an older |
| 218 | +module does not support a newer optional feature yet, and the module author |
| 219 | +wants to ensure that module can be used even when an incompatible optional |
| 220 | +feature is enabled in the session. |
| 221 | + |
| 222 | +A terminating error is generated if the same optional feature name is used |
| 223 | +twice across the `-OptionalFeatures` and `-DisabledOptionalFeatures` |
| 224 | +parameters. |
| 225 | + |
| 226 | +### Add parameter set to #requires statement |
| 227 | + |
| 228 | +`#requires -OptionalFeatures <string[]> [-Disabled]` |
| 229 | + |
| 230 | +This parameter set would enable, or disable if `-Disabled` is used, optional |
| 231 | +features identified by `-Name` in the current script file. |
| 232 | + |
| 233 | +### New command: Get-OptionalFeature |
| 234 | + |
| 235 | +```none |
| 236 | +Get-OptionalFeature [[-Name] <string[]>] [<CommonParameters>] |
| 237 | +``` |
| 238 | + |
| 239 | +This command will return a list of the optional features that are available in |
| 240 | +PowerShell, along with their auto-enable configuration, source and description. |
| 241 | + |
| 242 | +The properties on the `S.M.A.OptionalFeature` object would be `Name`, |
| 243 | +`AutoEnable`, `Source`, `Description`, defined as follows: |
| 244 | + |
| 245 | +|Property Name|Description| |
| 246 | +|--|--| |
| 247 | +|`Name`|A string value that identifies the optional feature name| |
| 248 | +|`AutoEnable`|An enumeration that identifies if the optional feature is auto-enabled. Values include `No`, `CurrentUser`, and `AllUsers`| |
| 249 | +|`Source`|A string value that identifies the area of PowerShell that is affected by this optional feature| |
| 250 | +|`Description`|A string value that describes the optional feature| |
| 251 | + |
| 252 | +The default output format would be of type table with the properties `Name`, |
| 253 | +`AutoEnable`, `Source`, and `Description`. |
| 254 | + |
| 255 | +### Enabling and disabling optional features in current and future PowerShell sessions |
| 256 | + |
| 257 | +```none |
| 258 | +Enable-OptionalFeature [-Name] <string[]> [-AutoEnable { No | CurrentUser | AllUsers }] |
| 259 | +[-WhatIf] [-Confirm] [<CommonParameters>] |
| 260 | +
|
| 261 | +Disable-OptionalFeature [-Name] <string[]> [-WhatIf] [-Confirm] [<CommonParameters>] |
| 262 | +``` |
| 263 | + |
| 264 | +The `Enable-OptionalFeature` command will enable an optional feature in current |
| 265 | +and future PowerShell sessions. To stop enabling an optional feature by default |
| 266 | +in future PowerShell sessions, use `Enable-OptionalFeature -AutoEnable No`. |
| 267 | + |
| 268 | +The `Disable-OptionalFeature` command will disable an optional feature in |
| 269 | +the current PowerShell session. |
| 270 | + |
| 271 | +### New command: Use-OptionalFeature |
| 272 | + |
| 273 | +```none |
| 274 | +Use-OptionalFeature [-Enable] <string[]> [[-Disable] <string[]>] [-ScriptBlock] |
| 275 | +<ScriptBlock> [-Confirm] [<CommonParameters>] |
| 276 | +
|
| 277 | +Use-OptionalFeature -Disable <string[]> [-ScriptBlock] <ScriptBlock> [-Confirm] |
| 278 | +[<CommonParameters>] |
| 279 | +``` |
| 280 | + |
| 281 | +This command would enable or disable the optional features whose names are |
| 282 | +identified in the `-Enable` and `-Disable` parameters for the duration of the |
| 283 | +`ScriptBlock` identified in the `-ScriptBlock` parameter, and return the |
| 284 | +features to their previous state afterwards. This allows for easy control of |
| 285 | +optional features over a small section of code. |
| 286 | + |
| 287 | +### New command: Test-OptionalFeature |
| 288 | + |
| 289 | +```none |
| 290 | +Test-OptionalFeature [-Name] <string> [<CommonParameters>] |
| 291 | +``` |
| 292 | + |
| 293 | +This command would return true if the optional feature is enabled in the |
| 294 | +current scope in the current session; false otherwise. |
| 295 | + |
| 296 | +### Checking optional feature states within the PowerShell runtime |
| 297 | + |
| 298 | +Much like Experimental Features, optional feature states will be managed using |
| 299 | +a variable. A Read-Only `$EnabledOptionalFeatures` automatic variable will |
| 300 | +contain the optional features that are currently enabled in the current scope. |
| 301 | + |
| 302 | +## Alternate proposals and considerations |
| 303 | + |
| 304 | +### Extend experimental features to support the enhancements defined in this RFC |
| 305 | + |
| 306 | +At a glance, experimental features and optional features are very similar to |
| 307 | +one another, so it was proposed that it may make sense to have them both use |
| 308 | +the same functionality when it comes to enabling/disabling them in scripts and |
| 309 | +modules; however, experimental features have a completely different intent (to |
| 310 | +try out new functionality in a PowerShell session), are only for future |
| 311 | +PowerShell sessions, and they only have a single on/off state. On the other |
| 312 | +hand, optional features are for the current and future PowerShell sessions, for |
| 313 | +the current user or all users, and may be enabled or disabled in various scopes |
| 314 | +within those sessions. For that reason, this approach doesn't seem like a |
| 315 | +viable solution. If we want to change that, perhaps someone should file an RFC |
| 316 | +against experimental features to make that change. |
| 317 | + |
| 318 | +### Enable/disable optional features in jobs according to their current state when the job is launched |
| 319 | + |
| 320 | +Jobs run in the background have their own session, and therefore will not |
| 321 | +automatically have optional features enabled or disabled according to the |
| 322 | +current state when the job is launched. We should consider updating how jobs |
| 323 | +are launched in the engine such that they do "inherit" optional feature |
| 324 | +configuration to allow them to use optional features without additional code. |
| 325 | + |
| 326 | +You might think that you can accomplish this using `#requires` in a script |
| 327 | +block launched as a job, but that doesn't work -- the `#requires` statement is |
| 328 | +ignored when you do this at the moment. For example, the see the results of the |
| 329 | +following script when launched from PowerShell Core: |
| 330 | + |
| 331 | +```PowerShell |
| 332 | +Start-Job { |
| 333 | + #requires -PSEdition Desktop |
| 334 | + $PSVersionTable |
| 335 | +} | Receive-Job -Wait |
| 336 | +``` |
| 337 | + |
| 338 | +The result of that command shows that the version of PowerShell where the job |
| 339 | +was run was Core, not Desktop, yet the job ran anyway despite the `#requires` |
| 340 | +statement. This may be a bug. If it is, and if that bug is corrected, then you |
| 341 | +could use #requires to enable/disable features, but regardless it would still |
| 342 | +be preferable (and more intuitive) for jobs to "inherit" the current optional |
| 343 | +feature configuration when they are invoked. This includes jobs launched with |
| 344 | +`Start-Job`, `Start-ThreadJob`, the `&` background operator, parallelized |
| 345 | +`ForEach-Object` commands, or the generic `-AsJob` parameter. |
0 commit comments