Skip to content

Tolerate IO/JSON errors reading the per-user powershell.config.json#27373

Open
jshigetomi wants to merge 4 commits intoPowerShell:masterfrom
jshigetomi:startUpConfigFix
Open

Tolerate IO/JSON errors reading the per-user powershell.config.json#27373
jshigetomi wants to merge 4 commits intoPowerShell:masterfrom
jshigetomi:startUpConfigFix

Conversation

@jshigetomi
Copy link
Copy Markdown
Collaborator

@jshigetomi jshigetomi commented Apr 28, 2026

PR Summary

This pull request improves how PowerShell handles unreadable or malformed per-user configuration files. Instead of failing to start, PowerShell now falls back to default settings and emits a warning when the per-user config is unreadable, while maintaining strict failure for system-wide config issues. This addresses a startup-time crash caused by transient cloud file provider failures (e.g., OneDrive not yet running on a fresh login). The PR also adds new tests to verify this behavior.

Configuration file handling improvements:

  • Updated PSConfiguration.cs so the per-user (ConfigScope.CurrentUser) config read fails open in two places:
    • File-level fallback: opening or root-parsing the file. Catches IOException, UnauthorizedAccessException, SecurityException, and JsonException (including the OneDrive-cloud-file-provider IOException that motivated PS7 should revert to defaults if it can't load user powershell.config.json #27370).
    • Per-key fallback: when a syntactically valid file contains a value that can't be materialized as the target type (e.g., PowerShellPolicies.ScriptExecution is a string instead of an object). Catches JsonException / JsonSerializationException from jToken.ToObject<T>(serializer) so a single malformed setting doesn't crash startup.
  • Both fallback paths route through a single TryWriteConfigWarning helper that writes a best-effort warning to Console.Error. Console.Error is intentional at this layer: ReadValueFromFile runs very early in startup, before any host, runspace, or PowerShell warning stream exists. This matches existing startup-time stderr writes in ConsoleHost.cs (e.g., CannotLoadPSReadline, SlowProfileLoadingMessage). Centralizing the call gives future hosts a single seam to swap the channel.
  • Neither fallback applies to the system-wide (ConfigScope.AllUsers) config file, which remains a hard failure for security reasons (admin-owned, sole source of security-relevant policies on non-Windows).
  • Added two new localized resource strings to PSConfigurationStrings.resx:
    • CanNotReadConfigurationFile — for the file-level fallback.
    • CanNotReadConfigurationValue — for the per-key fallback.
    • Strings are prefix-free; the helper applies the WARNING: label so the resource content stays usable if a future host routes these through the warning stream.

Testing enhancements:

  • Added two fixture helpers in test_PSConfiguration.cs:
    • SetupBrokenCurrentUserConfigOnly — broken per-user config, no system config (file-level path).
    • SetupTypeMismatchedCurrentUserConfig — valid JSON with a wrong nested shape (per-key path).
  • Added a CaptureStderr(Action) helper that redirects Console.Error so tests can assert the warning was actually emitted.
  • New / updated xUnit tests:
    • PowerShellConfig_BrokenSystemConfig_Throws_BrokenCurrentUserConfig_FallsBack (renamed from PowerShellConfig_GetPowerShellPolicies_BrokenSystemConfig) — asserts both fail-closed for AllUsers and fail-open for CurrentUser.
    • PowerShellConfig_BrokenCurrentUserConfig_FallsBackToDefaults — broken per-user file returns defaults via GetExecutionPolicy, GetModulePath, GetExperimentalFeatures.
    • PowerShellConfig_TypeMismatchInCurrentUserConfig_FallsBackToDefaults — valid JSON with a type-mismatched value returns null instead of throwing.
    • PowerShellConfig_BrokenCurrentUserConfig_EmitsWarningToStderr — pins that the file-level warning is actually written.
    • PowerShellConfig_TypeMismatchInCurrentUserConfig_EmitsWarningToStderr — pins that the per-key warning is actually written.

PR Context

Fixes #27370

The original report showed pwsh hard-failing at startup with IOException: The cloud file provider is not running when the user's powershell.config.json lives in a OneDrive-synced Documents\PowerShell folder and OneDrive hasn't started yet. The same code path also blew up on permission-denied and corrupt-JSON cases. After this PR, all of those scenarios warn to stderr and fall back to default settings (so $profile, execution policy, module path, etc. work as if the file were absent), while the admin-owned AllUsers config still fails closed.

PR Checklist

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates PowerShell’s configuration loading so unreadable/malformed per-user powershell.config.json no longer prevents startup: it falls back to defaults and emits a warning, while system-wide config failures remain fatal for security reasons.

Changes:

  • Added exception handling for ConfigScope.CurrentUser config reads to fail open (with a warning) on IO/permission/JSON errors.
  • Introduced a new localized warning string for unreadable per-user config.
  • Added/updated xUnit coverage to validate fail-open for per-user config and fail-closed for system-wide config.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/System.Management.Automation/engine/PSConfiguration.cs Adds per-user config fail-open exception handling and warning emission during config root load.
src/System.Management.Automation/resources/PSConfigurationStrings.resx Adds localized warning text for unreadable per-user config.
test/xUnit/csharp/test_PSConfiguration.cs Adds fixture helper and tests to validate per-user fallback vs system-wide failure behavior.

Comment thread src/System.Management.Automation/engine/PSConfiguration.cs
@jshigetomi jshigetomi added the CL-Engine Indicates that a PR should be marked as an engine change in the Change Log label Apr 30, 2026
- Wrap jToken.ToObject<T>(serializer) in try/catch that fail-opens for ConfigScope.CurrentUser on JsonException. Addresses Copilot review feedback on PR PowerShell#27373: a syntactically valid per-user powershell.config.json with a wrong-typed value (e.g. PowerShellPolicies.ScriptExecution as a string) would still crash startup via JsonSerializationException. AllUsers stays fail-closed.

- Add SecurityException to the file-open catch filter so an ACL/policy denial on the per-user file behaves the same as UnauthorizedAccessException.

- Add CanNotReadConfigurationValue resource string for the per-key warning.

- Add PowerShellConfig_TypeMismatchInCurrentUserConfig_FallsBackToDefaults xUnit test, with a fixture helper that writes valid JSON containing a nested wrong shape.

Refs PowerShell#27370

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jshigetomi jshigetomi requested a review from Copilot April 30, 2026 22:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment thread src/System.Management.Automation/engine/PSConfiguration.cs Outdated
Comment thread src/System.Management.Automation/engine/PSConfiguration.cs Outdated
Comment thread src/System.Management.Automation/resources/PSConfigurationStrings.resx Outdated
Comment thread test/xUnit/csharp/test_PSConfiguration.cs Outdated
Comment thread src/System.Management.Automation/engine/PSConfiguration.cs Outdated
Addresses five Copilot review comments on PR PowerShell#27373:

1. Extract TryWriteConfigWarning helper to centralize the best-effort

   warning emission. Both fallback paths now call this helper instead of

   duplicating try/catch around Console.Error.WriteLine.

2. Document why Console.Error is the right channel here: ReadValueFromFile

   runs very early in pwsh startup (before any host, runspace, or warning

   stream exists). The helper provides a single seam for future hosts to

   redirect. This matches existing startup-time stderr writes in

   ConsoleHost.cs (e.g. CannotLoadPSReadline, SlowProfileLoadingMessage).

3. Remove the literal 'WARNING:' prefix from the localized resource

   strings; the helper prepends it. This keeps the resource content

   prefix-free for future routing through the warning stream.

4. Rename PowerShellConfig_GetPowerShellPolicies_BrokenSystemConfig to

   PowerShellConfig_BrokenSystemConfig_Throws_BrokenCurrentUserConfig_FallsBack

   to make the dual fail-closed/fail-open assertions explicit.

5. Add two new xUnit tests that capture Console.Error and assert the

   warning is actually emitted (not silently dropped):

     - PowerShellConfig_BrokenCurrentUserConfig_EmitsWarningToStderr

     - PowerShellConfig_TypeMismatchInCurrentUserConfig_EmitsWarningToStderr

Refs PowerShell#27370

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backport-7.4.x-Consider Backport-7.5.x-Consider Backport-7.6.x-Consider CL-Engine Indicates that a PR should be marked as an engine change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PS7 should revert to defaults if it can't load user powershell.config.json

2 participants