Skip to content

Commit 9de8db2

Browse files
Reject RFC0058 - Optional Features (#220)
* pull RFC into separate PR * moved key into PSData for backcompat * Prepare RFC0058 - Optional Features for rejection Co-authored-by: Joey Aiello <[email protected]>
1 parent 49688c1 commit 9de8db2

File tree

1 file changed

+345
-0
lines changed

1 file changed

+345
-0
lines changed
+345
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
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

Comments
 (0)