Place System-Wide Config in the Machine Folder for MSIX Packages#27632
Place System-Wide Config in the Machine Folder for MSIX Packages#27632jshigetomi wants to merge 18 commits into
Conversation
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>
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>
| <desktop6:RegistryWriteVirtualization>disabled</desktop6:RegistryWriteVirtualization> | ||
| <desktop6:FileSystemWriteVirtualization>disabled</desktop6:FileSystemWriteVirtualization> | ||
| <appdata:ApplicationData> | ||
| <appdata:MachineFolder Sddl="(A;OICI;GA;;;BA)(A;OICI;GR;;;BU)" /> |
There was a problem hiding this comment.
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
|
|
||
| private const int AppModelErrorNoPackage = 15700; | ||
|
|
||
| [DllImport("kernel32.dll", EntryPoint = "GetCurrentPackageFamilyName", CharSet = CharSet.Unicode)] |
There was a problem hiding this comment.
This is how PowerShell knows it's an AppX package
https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getcurrentpackagefamilyname
…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
…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); |
There was a problem hiding this comment.
remove experimental feature merging for another PR
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
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
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
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.MachineFolderand 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. |
| 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"); |
There was a problem hiding this comment.
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
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:
MachineFolderconfiguration scope toConfigScopeand updated the manifest (AppxManifest.xml) to declare and provision the per-machine ApplicationData store for packaged installs. ([1], [2], [3])Configuration file handling and scope precedence:
MachineFolderscope, including redirecting system-wide writes to the writable per-machine location when available. ([1], [2], [3], [4], [5])MergePreferenceValueandMergePolicyListto encapsulate this logic. ([1], [2], [3])Behavior and compatibility updates:
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
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright header