Skip to content

Place System-Wide Config in the Machine Folder for MSIX Packages#27632

Open
jshigetomi wants to merge 18 commits into
PowerShell:masterfrom
jshigetomi:perMachineDataStore
Open

Place System-Wide Config in the Machine Folder for MSIX Packages#27632
jshigetomi wants to merge 18 commits into
PowerShell:masterfrom
jshigetomi:perMachineDataStore

Conversation

@jshigetomi

@jshigetomi jshigetomi commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

PR Summary

This pull request adds support for a new writable per-machine configuration scope (MachineFolder) for packaged (MSIX) PowerShell installs that opt into the Windows per-machine ApplicationData store. It updates how configuration settings are read and written, introduces new merge logic for policy and preference settings, and ensures backward compatibility for non-packaged installs. The changes also include the necessary manifest and utility updates to detect and use the new data store.

Support for per-machine ApplicationData (MSIX) configuration:

  • Added the MachineFolder configuration scope to ConfigScope and updated the manifest (AppxManifest.xml) to declare and provision the per-machine ApplicationData store for packaged installs. ([1], [2], [3])
  • Implemented logic to detect when running as a packaged app with a provisioned per-machine data store, and to resolve the path to this store using registry and Win32 APIs. ([1], [2])

Configuration file handling and scope precedence:

  • Updated the configuration file path resolution and read/write logic to support the new MachineFolder scope, including redirecting system-wide writes to the writable per-machine location when available. ([1], [2], [3], [4], [5])
  • Changed the merge order for configuration scopes so that user, machine, and product (AllUsers) settings are combined according to whether a setting is a "preference" (user wins) or "policy" (union across scopes). Added helper methods MergePreferenceValue and MergePolicyList to encapsulate this logic. ([1], [2], [3])

Behavior and compatibility updates:

  • Ensured that experimental features remain on legacy scopes and are not redirected to the new per-machine data store for now, maintaining compatibility with non-packaged installs. (PSConfiguration.csR262-R298)
  • Updated the manifest's target device family maximum version to the latest tested version. (AppxManifest.xmlR21-R27)

PR Context

This PR provides the initial infrastructure needed to fix broken features on MSIX packages.
This is planned as one of many PRs that will target various issues with MSIX packages.
Fixes #9278. Please see #27565 for all the issues we are tracking for MSIX packages.

PR Checklist

Justin Chung and others added 3 commits June 24, 2026 14:24
Opt into the WindowsAppSDK per-machine ApplicationData store as an
alternative to mutable package directories. Adds the appdata namespace
(ignorable) plus a Properties/ApplicationData/MachineFolder element with
an SDDL granting Administrators write, and bumps MaxVersionTested to
10.0.26100.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When running as a packaged MSIX app that opts into the per-machine
ApplicationData store (appdata:MachineFolder), relocate the writable
AllUsers locations from the read-only \C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.3.0_x64__8wekyb3d8bbwe install dir into that store:
module path, system-wide powershell.config.json, AllUsers profiles, and
AllUsers updatable help.

A central helper, Utils.GetPackagedMachineDataStorePath(), detects package
identity (GetCurrentPackageFamilyName) and the provisioned store folder,
returning null when not packaged so all behavior is unchanged for normal
installs. The shipped \C:\Program Files\WindowsApps\Microsoft.PowerShell_7.6.3.0_x64__8wekyb3d8bbwe powershell.config.json is preserved by seeding
the store copy on first write and falling back to it for reads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d break

The s_packagedMachineDataStorePath and s_packagedMachineDataStorePathInitialized fields are only referenced in the non-UNIX branch of GetPackagedMachineDataStorePath, so they were reported as unused (CS0169) on Linux/macOS where warnings are treated as errors. Wrap their declarations in #if !UNIX to match their usage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jshigetomi jshigetomi added the CL-Engine Indicates that a PR should be marked as an engine change in the Change Log label Jul 8, 2026
Justin Chung and others added 5 commits July 9, 2026 14:28
AllUsers module installs via PowerShellGet/PSResourceGet already target %ProgramFiles%\PowerShell\Modules (SpecialFolder.ProgramFiles + 'PowerShell'), which is writable under MSIX and independent of $PSHOME. The per-machine data store Modules path (<store>\Modules) was only ever added to PSModulePath for discovery -- nothing installs there, so it stayed an empty, redundant entry.

Removing it restores the canonical module path, matching non-packaged and stable builds. Config, profile, and updatable-help redirection to the per-machine store are unaffected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reverts the help search/write redirection (HelpUtils, CommandHelpProvider, HelpFileHelpProvider, UpdateHelpCommand) to master, scoping this PR to the config and AllUsers-profile relocation. AllUsers updatable-help redirection to the per-machine store will be handled in a follow-up PR, alongside the WinRM session-config relocation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the redirect-and-seed approach with a distinct ConfigScope.MachineFolder (the writable per-machine data store of a packaged app; absent otherwise). The shipped PSHOME config stays the read-only product defaults; system-wide (AllUsers) writes are redirected to the machine folder and store only the changed keys -- no full seed, so product defaults never go stale.

Merge precedence (collapses to the legacy AllUsers/CurrentUser behavior when unpackaged): Policy settings = MachineFolder (admin) > PSHOME (product) > CurrentUser (user), with Group Policy still above all; the GPO/registry lookup skips MachineFolder since it has no registry representation. Preference settings = CurrentUser > MachineFolder > PSHOME. The Microsoft.PowerShell:ExecutionPolicy key is treated as a policy when packaged: LocalMachine resolves MachineFolder then PSHOME and is ordered ahead of CurrentUser.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The appdata:MachineFolder SDDL previously granted only Administrators (A;OICI;GA;;;BA), which also blocked non-admin/non-elevated users from READING the store, so admin-set system-wide config (execution policy, experimental features) never reached standard users. Add an inheritable Built-in Users read ACE (A;OICI;GR;;;BU) so all users can read the config while writes remain admin-only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The enabled-list approach was fragile under the multi-scope merge: an all-disabled state produced an empty list indistinguishable from 'unset' that fell back to the PSHOME product list (re-enabling everything), and SetExperimentalFeatures recomputed and rewrote the whole merged list into the target scope, which could re-enable a previously disabled feature when disabling another.

Store explicit per-feature overrides instead: each scope has an ExperimentalFeatures (enabled) and a DisabledExperimentalFeatures set. GetExperimentalFeatures resolves each feature across scopes (CurrentUser > MachineFolder > AllUsers/PSHOME), defaulting features with no explicit override to the product enabled list, so a newly shipped feature stays on until explicitly disabled. SetExperimentalFeatures reads and writes the same write-resolved scope so disables accumulate and never re-enable an earlier one; enabling a feature clears its disable override.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread assets/AppxManifest.xml
<desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization>
<desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization>
<appdata:ApplicationData>
<appdata:MachineFolder Sddl="(A;OICI;GA;;;BA)(A;OICI;GR;;;BU)" />

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

First set of sddl's grants built in admin to write to all files and directories
Second set of sddl's grants built in users to read all files and directories

Comment thread src/System.Management.Automation/engine/PSConfiguration.cs Outdated
Comment thread src/System.Management.Automation/security/SecuritySupport.cs Outdated

private const int AppModelErrorNoPackage = 15700;

[DllImport("kernel32.dll", EntryPoint = "GetCurrentPackageFamilyName", CharSet = CharSet.Unicode)]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Comment thread src/System.Management.Automation/engine/PSConfiguration.cs Outdated
…semantics

Revert the experimental-feature enable/disable-set redesign and keep experimental
features on the legacy config scopes so they behave exactly as on non-packaged
installs. Reads use CurrentUser, falling back to AllUsers ($PSHOME); writes no
longer redirect to the per-machine data store (allowMachineFolderRedirect: false),
so -Scope AllUsers under MSIX fails against the read-only $PSHOME as it does
today. This fixes the Get-ExperimentalFeature "user config takes precedence over
system config" CI failure, where the redesign merged the system list even when a
user config existed.

MachineFolder support for experimental features is tracked in PowerShell#27702.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
@jshigetomi jshigetomi changed the title Move System Wide Files to MachineFolder Place System-Wide Config in the Machine Folder for MSIX Packages Jul 20, 2026
Justin Chung and others added 3 commits July 20, 2026 12:59
…deny list

Per the MSIX machine-folder meeting decisions, narrow this PR to
configuration-file merging.

- Remove the AllUsers profile relocation to the packaged machine-folder
  data store. GetAllUsersFolderPath() again resolves to $PSHOME via
  Utils.GetApplicationBase(shellId) on every install type. Profile
  relocation is deferred to a dedicated follow-up tracked by PowerShell#27564.

- Merge the Windows PowerShell compatibility module deny list as a
  policy: the effective list is the union of the CurrentUser,
  MachineFolder, and AllUsers ($PSHOME) scopes, so deny entries added by
  a product update in $PSHOME are still honored when a machine or user
  config also sets the key. The no-clobber list stays a preference
  (highest-precedence scope wins outright) and is documented as such.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
Per the machine-folder meeting decision, the execution-policy scope
precedence must stay unchanged for backward compatibility and public API
stability: the current user's preference continues to win over the
system-wide (LocalMachine) preference.

Remove the packaged-app reorder that had LocalMachine trump CurrentUser
and restore the single legacy order. Add comments distinguishing the
policy scopes (MachinePolicy, UserPolicy - Group Policy) from the
preference scopes (Process, CurrentUser, LocalMachine). The machine-folder
admin override is still honored within the system-wide LocalMachine
preference (MachineFolder before $PSHOME) via GetLocalPreferenceValue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
Per reviewer feedback: despite the "ExecutionPolicy" name, an execution
policy set through configuration (the Process, CurrentUser and LocalMachine
scopes - the PSExecutionPolicyPreference environment variable or a
powershell.config.json file) is a preference. Only Group Policy
(MachinePolicy / UserPolicy, from the registry) is a true policy. Make the
ExecutionPolicyScopePreferences comment state this explicitly so future
maintainers do not reintroduce a machine-trumps-user reorder for the
config-backed scopes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
{
features.Add(featureName);
WriteValueToFile<string[]>(scope, "ExperimentalFeatures", features.ToArray());
WriteValueToFile<string[]>(scope, "ExperimentalFeatures", features.ToArray(), allowMachineFolderRedirect: false);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

remove experimental feature merging for another PR

Justin Chung and others added 3 commits July 20, 2026 14:11
Factor the two cross-scope config merge strategies out of the individual
getters into reusable, documented helpers so future settings can opt into
the same behavior without re-implementing it:

- MergePolicyList(key): case-insensitive union of a list setting across
  every scope (policy semantics - entries accumulate, nothing is dropped).
- MergePreferenceValue<T>(key): value from the highest-precedence scope
  that defines the key (preference semantics - a more specific scope wins).

Both share s_configScopePreferenceOrder (CurrentUser > MachineFolder >
AllUsers/$PSHOME). Route the three existing call sites through them:
GetWindowsPowerShellCompatibilityModuleDenyList (policy),
GetWindowsPowerShellCompatibilityNoClobberModuleList (preference) and
IsImplicitWinCompatEnabled (preference). Behavior is unchanged and was
verified at runtime (deny=union, no-clobber=highest-precedence,
implicit-WinCompat bool).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
Trim the deny-list and no-clobber getters to a one-line classification and
move the "why union" / "why highest-precedence wins" explanations onto
MergePolicyList and MergePreferenceValue, where the general merge behavior
is defined. Documentation-only; no behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
Cover the policy-union and preference-precedence merge behavior added for
the per-machine data store (MachineFolder) config scope:

- WindowsPowerShellCompatibilityModuleDenyList unions across CurrentUser,
  MachineFolder, and AllUsers scopes (policy), dedups case-insensitively,
  and returns null when undefined.
- WindowsPowerShellCompatibilityNoClobberModuleList resolves to the
  highest-precedence scope that defines it (preference), including
  MachineFolder overriding AllUsers.
- IsImplicitWinCompatEnabled honors the highest-precedence
  DisableImplicitWinCompat value.

The fixture injects a temp MachineFolder config file via the mutable
machineFolderConfigFile field so the 3-scope merge (otherwise null on
non-packaged installs) is exercised, and restores it on cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
pwshBot and others added 2 commits July 21, 2026 15:46
Address 18 CodeFactor findings in the new WinCompat merge tests:
- Remove the #region/#endregion wrapper (SA1124).
- Split combined [Fact, Priority(N)] attributes onto separate lines (SA1133).
- Add missing <param> docs to SetupWinCompatConfig (SA1611).
- Make SetupWinCompatConfig internal so a public member no longer
  follows private members (SA1202).
- Drop blank lines between explanatory comments and the following
  test attribute (SA1512).

No behavioral change; all 21 PowerShellPolicyTests still pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
CodeFactor still flagged SetupWinCompatConfig (internal) as following a
private member (SA1202). Move it and its private helpers below the
existing internal ForceReadingFromFile so an internal member no longer
comes after a private one. Pure reordering; no behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
@jshigetomi
jshigetomi marked this pull request as ready for review July 22, 2026 14:25
@jshigetomi
jshigetomi requested a review from a team as a code owner July 22, 2026 14:25
Copilot AI review requested due to automatic review settings July 22, 2026 14:25
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 adds a new writable per-machine configuration scope (ConfigScope.MachineFolder) intended for MSIX-packaged PowerShell installs that opt into Windows’ per-machine ApplicationData store, and updates config read/write + merge behavior to incorporate this scope while preserving legacy behavior for non-packaged installs.

Changes:

  • Introduces ConfigScope.MachineFolder and redirects system-wide (AllUsers) config writes to the packaged per-machine data store when available.
  • Updates configuration merge behavior to distinguish preferences (highest-precedence wins) vs policy lists (union across scopes), and extends execution policy resolution accordingly.
  • Updates the MSIX manifest to declare the per-machine ApplicationData store and bumps the MaxVersionTested.

Reviewed changes

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

Show a summary per file
File Description
test/xUnit/csharp/test_PSConfiguration.cs Adds tests and fixture infrastructure to validate 3-scope merge behavior (CurrentUser/MachineFolder/AllUsers) for WinCompat-related settings.
src/System.Management.Automation/security/SecuritySupport.cs Updates execution policy resolution to consult MachineFolder before falling back to AllUsers for the system-wide preference.
src/System.Management.Automation/engine/Utils.cs Adds packaged identity + per-machine data store path probing and updates config-scope merge orders to include MachineFolder.
src/System.Management.Automation/engine/PSConfiguration.cs Adds the MachineFolder scope, redirects AllUsers writes when packaged, and implements preference/policy-list merge helpers.
assets/AppxManifest.xml Declares appdata:MachineFolder with ACLs and updates MaxVersionTested.

Comment thread src/System.Management.Automation/engine/Utils.cs Outdated
Comment thread src/System.Management.Automation/engine/PSConfiguration.cs
if (key?.GetValue("PackageRepositoryRoot") is string repositoryRoot && !string.IsNullOrEmpty(repositoryRoot))
{
// Mirrors how the Windows App SDK locates the per-machine data store.
string path = Path.Combine(repositoryRoot, "Families", "ApplicationData", packageFamilyName, "Machine");

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There is an API that determines this, but we don't want to add a new dependency.

Address PR review: Registry.LocalMachine.OpenSubKey is subject to WOW64
redirection, so a 32-bit pwsh on 64-bit Windows would read WOW6432Node and
miss PackageRepositoryRoot. Open the LocalMachine base key with
RegistryView.Registry64 on 64-bit systems (Default on 32-bit), matching the
existing pattern in RemoteSessionHyperVSocket.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: ab738c1b-4d18-491b-a40f-0515b586857b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

Some cmdlets and config require writing to $PSHOME\SessionConfig folder

3 participants