Powershell: Set-ExecutionPolicy reports statement-terminating error instead of warning

Created on 5 Mar 2020  路  13Comments  路  Source: PowerShell/PowerShell

If you're trying to set a _persistent_ execution policy (-Scope LocalMachine or -Scope CurrentUser) while a _process-level_ policy happens to be in effect (-Scope Process or, more likely, -ExecutionPolicy via the CLI), updating the persistent policy succeeds, but for the current process the previous process-level policy remains in effect.

Set-ExecutionPolicy commendably tries to alert the user to that fact, but instead of issuing a _warning_, it emits a _statement-terminating error_.

You can tell from the wording of the message alone that this shouldn't be an _error_ (emphasis added):

Set-ExecutionPolicy: PowerShell updated your execution policy successfully, but the setting is overridden by a policy defined at a more specific scope. Due to the override, your shell will retain its current effective execution policy of Restricted. Type "Get-ExecutionPolicy -List" to view your execution policy settings. For more information please see "Get-Help Set-ExecutionPolicy".

This behavior gets in the way of a common idiom:

# Triggers error
pwsh -ExecutionPolicy Bypass -c 'Set-ExecutionPolicy -Scope CurrentUser RemoteSigned -Force; ...'

Steps to reproduce

On _Windows_:

# This is is the same as calling the CLI with -ExecutionPolicy Bypass
Set-ExecutionPolicy -Scope Process Bypass -Force

try {
  Set-ExecutionPolicy -Scope CurrentUser RemoteSigned -Force
} catch {
  "Error unexpectedly thrown: $_"
}

Expected behavior

The code should run without an error, and a _warning_ should be shown.

Actual behavior

A statement-terminating error occurs in the 2nd Set-ExecutionPolicy call, which triggers the catch block:

Error unexpectedly thrown: PowerShell updated your execution policy successfully, but the setting is overridden by a policy defined at a more specific scope.  Due to the override, your shell will retain its current effective execution policy of AllSigned. Type "Get-ExecutionPolicy -List" to view your execution policy settings. For more information please see "Get-Help Set-ExecutionPolicy".

Environment data

PowerShell Core 7.0.0
Breaking-Change Committee-Reviewed Issue-Question Resolution-Answered Resolution-By Design WG-Security

Most helpful comment

@PowerShell/powershell-committee discussed this and we we're worried about potential breaking changes in an area that's so widely depended upon by the entire ecosystem. It's likely that this try/catch has been deployed very widely, and even if the fix to work around that break is simple, it could be in unmaintained areas of automation.

馃 in most cases where automation is concerned, this cmdlet is not often used from what I've seen. Most tools that are running powershell use the -ExecutionPolicy Bypass mode on the executable itself.

Even if we assume that a decent portion of folks are running the cmdlet itself where the issue occurs, for the proposed change to break something, the _environment_ itself would have to change. (Unless something is relying on _not_ being able to change the execution policy, which... seems like a contrived scenario even at the most optimistic assessment I can muster.) That being the case would be a problem regardless of whether or not this change is taken, so the only time this impacts an automation scenario is it starting to fail... which _would happen anyway_, only more severely and slightly more obviously without the proposed change.

And that, inherently, means the code _is_ being maintained, or at least the server / other system it's on is being updated, and something is bound to break or require a bit of fixing from time to time regardless.

I'm really not seeing any particular value in leaving things the way they are here, and nor do I see a lot of potential value in further complicating the executionpolicy settings. 馃し鈥嶁檪

All 13 comments

/cc @TravisEz13 @PaulHigin Please make a conclusion.

Well, it is pretty clear from the code that whoever implemented this felt this should be a terminating error. My guess is that this was done to support scripts where changing the execution policy setting is critical for the script to succeed. So even if the 'policy' is successfully changed at a specific scope, but the actual state doesn't change because some other scope overrides it, then it is an error.

It seems like it would have been better to have separate cmdlets that had clear responsibility for affecting policy and for affecting current state. But changing this now would be a breaking change (unless we propose new cmdlets and deprecate the old).

Feel free to add the 'Review-Committee' tag if you feel this should be considered.

@PaulHigin sure, but there's a reason -WarningAction exists.

I dont think a situation where "_maybe_ it could be critical" calls for a terminating error all the time. 馃し鈥嶁檪

If a cmdlet's primary action fails then that should be a terminating error. The problem is that this cmdlet does two primary things.

I don't think that's really the case. It does _one_ thing, at multiple possible scopes. If the action taken on the scope targeted succeeds... then it's done its job. The fact that there may be an overriding scope that nullifies the _result_ of that action is, in essence, metadata. A warning sounds the best choice in my opinion.

Well put, @vexx32.

I think that without backward-compatibility considerations, that is the way to go.
To _also, implicitly_ update the Process scope is definitely not the _primary_ purpose when you target -Scope CurrentUser or -Scope LocalMachine.
(This secondary action generally makes sense, but isn't even documented as such.)

As for it being a breaking change:

Hypothetically, someone who relied on a try / catch _with a throw in the catch block_ would be affected, as that is the only way to _abort_ the script - given that _statement_-terminating errors do not abort the script _as a whole_.

Much more likely, try / catch has been used _without_ throw, simply to silence the unexpected error, because the typical use case I've seen is the following:

powershell pwsh -ExecutionPolicy Bypass -c 'Set-ExecutionPolicy -Scope CurrentUser RemoteSigned -Force; ...'

This is a perfectly reasonable command, yet it triggers the statement-terminating error and requires a try / catch simply to silence it.
(Note that, unlike what I've originally said in the OP (since fixed), the error occurs irrespective of whether the persistent policy being set is more or less restrictive than the effective process-level one.)

In short: I would consider this a Bucket 3: Unlikely Grey Area change.

@iSazonov, can you please tag this for committee review?

Tagging for committee review for @vexx32 and @mklement0 's reasoning to be reviewed

@PowerShell/powershell-committee discussed this and we we're worried about potential breaking changes in an area that's so widely depended upon by the entire ecosystem. It's likely that this try/catch has been deployed very widely, and even if the fix to work around that break is simple, it could be in unmaintained areas of automation.

The issue that's really being discussed is whether or not folks depend on (and/or expect) the behavior of Set-ExecutionPolicy to set both the scope specified and the process scope. @BrucePay and @JamesWTruher both raised the fact that the cmdlets were designed and communicated like this early on, as @PaulHigin intuited from the source code.

We do agree that the documentation and/or error message could be improved to be clear about the intended behavior of the cmdlet and why the error is being thrown (specifically that the Process level execution policy could not be changed).

We discussed and ultimately rejected the idea of a Switch parameter (something like a -PolicyOnly) as it would simply add complexity to the cmdlet with no real benefit (this is why we have GP).

We also raised that it would be useful to have some cmdlets that help set configuration values in the powershell.config.json so that you could easily do something like Set-PowerShellConfigurationValue -ExecutionPolicy Bypass and have it only be reflected in the config.

@PowerShell/powershell-committee discussed this and we we're worried about potential breaking changes in an area that's so widely depended upon by the entire ecosystem. It's likely that this try/catch has been deployed very widely, and even if the fix to work around that break is simple, it could be in unmaintained areas of automation.

馃 in most cases where automation is concerned, this cmdlet is not often used from what I've seen. Most tools that are running powershell use the -ExecutionPolicy Bypass mode on the executable itself.

Even if we assume that a decent portion of folks are running the cmdlet itself where the issue occurs, for the proposed change to break something, the _environment_ itself would have to change. (Unless something is relying on _not_ being able to change the execution policy, which... seems like a contrived scenario even at the most optimistic assessment I can muster.) That being the case would be a problem regardless of whether or not this change is taken, so the only time this impacts an automation scenario is it starting to fail... which _would happen anyway_, only more severely and slightly more obviously without the proposed change.

And that, inherently, means the code _is_ being maintained, or at least the server / other system it's on is being updated, and something is bound to break or require a bit of fixing from time to time regardless.

I'm really not seeing any particular value in leaving things the way they are here, and nor do I see a lot of potential value in further complicating the executionpolicy settings. 馃し鈥嶁檪

@PowerShell/powershell-committee continued discussing this and agree that we don't believe the proposed value justifies the breaking change

馃 in most cases where automation is concerned, this cmdlet is not often used from what I've seen. Most tools that are running powershell use the -ExecutionPolicy Bypass mode on the executable itself.

You'd be surprised unfortunately. I've seen scenarios where the entirety of a product's generated code starts with Set-ExecutionPolicy in a try/catch with a different code path for failures. Maybe because -ExecutionPolicy Bypass is a no-op (or at least silently(?) fails) when MachinePolicy or UserPolicy is defined.

This issue has been marked as answered and has not had any activity for 1 day. It has been closed for housekeeping purposes.

Was this page helpful?
0 / 5 - 0 ratings