Skip to content

Preserve REG_EXPAND_SZ registry value type when writing and restoring PATH #1

Description

@ekalb81

Problem

Set-TargetPath (Windows-Path-Repair-Full.ps1, ~line 296) uses [Environment]::SetEnvironmentVariable('Path', $value, $Scope), which writes the registry value as REG_SZ. The stock Machine Path (and often the User Path) is REG_EXPAND_SZ.

Consequences:

  1. Entries containing unexpanded variables like %SystemRoot% or %USERPROFILE% — which the script deliberately preserves verbatim — stop expanding after any apply, silently breaking those entries system-wide.
  2. -RestoreBackup restores the string but not the original value type, so even recovery does not undo the downgrade.

Proposed fix

  • Write the value via the registry directly with an explicit kind: RegistryKey.SetValue('Path', $value, [Microsoft.Win32.RegistryValueKind]::ExpandString) against HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment (Machine) and HKCU:\Environment (User).
  • Broadcast WM_SETTINGCHANGE manually afterward (currently SetEnvironmentVariable does this implicitly).
  • Read back with GetValue(..., [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) in Assert-TargetPath so verification compares the unexpanded value; otherwise %-containing entries would spuriously fail read-back.
  • Extend the backup schema to record the original value kind for each scope so -RestoreBackup is fully faithful (bump SchemaVersion; keep reading v1 backups, restoring them as ExpandString).
  • Add regression tests using the injectable SetPathAction/AssertPathAction seams.

Related: rollback flag must be set before the setter runs

In Invoke-PathUpdate (~line 341), $machineWritten/$userWritten are set only after the setter returns. A setter that mutates the registry and then throws skips rollback for that scope, while the final error message still claims all changes were rolled back. This is unlikely with the current single-call SetEnvironmentVariable, but becomes a real hazard once this issue replaces it with a multi-step write (registry set + broadcast). Fix as part of this change:

  • Set the written flag before invoking the setter (conservative: rollback may rewrite an unchanged value, which is harmless).
  • Add a seam-based test where the setter records the mutation and then throws, asserting rollback still runs for that scope.

Priority

Highest — correctness issue that affects every -Apply run, in a tool whose whole purpose is safe writes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions