diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index 297bc10628..270e5c925e 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -306,19 +306,19 @@ namespace System.Management.Automation public enum XDG_Type { /// XDG_CONFIG_HOME/powershell - CONFIG, + CONFIG = 0, /// XDG_CACHE_HOME/powershell - CACHE, + CACHE = 1, /// XDG_DATA_HOME/powershell - DATA, + DATA = 2, /// XDG_DATA_HOME/powershell/Modules - USER_MODULES, + USER_MODULES = 3, /// /usr/local/share/powershell/Modules - SHARED_MODULES, - /// /etc/powershell - SYSTEM_CONFIG, + SHARED_MODULES = 4, /// XDG_CONFIG_HOME/powershell - DEFAULT + DEFAULT = 5, + /// /etc/powershell + SYSTEM_CONFIG = 6, } /// diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index caf2cc52b5..6c86d1470b 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -63,7 +63,6 @@ namespace System.Management.Automation.Configuration // Legacy system-wide config file path in $PSHOME, used for fallback reads. private readonly string legacySystemWideConfigFile; - private bool systemConfigOverridden; // The json file containing the per-user configuration settings. private readonly string perUserConfigFile; @@ -108,7 +107,9 @@ namespace System.Management.Automation.Configuration fileLock = new ReaderWriterLockSlim(); } - private string GetConfigFilePath(ConfigScope scope) + // Writes always target the new platform-specific location (systemWideConfigFile). + // Reads may fall back to the legacy $PSHOME location via GetEffectiveSystemWideConfigFile(). + private string GetConfigFilePathForWrite(ConfigScope scope) { return (scope == ConfigScope.CurrentUser) ? perUserConfigFile : systemWideConfigFile; } @@ -120,11 +121,6 @@ namespace System.Management.Automation.Configuration private string GetEffectiveSystemWideConfigFile() { - if (systemConfigOverridden) - { - return systemWideConfigFile; - } - if (File.Exists(systemWideConfigFile)) { return systemWideConfigFile; @@ -138,7 +134,7 @@ namespace System.Management.Automation.Configuration return systemWideConfigFile; } - internal string AllUsersConfigFilePath => systemWideConfigFile; + internal string AllUsersConfigFilePath => GetEffectiveSystemWideConfigFile(); internal string CurrentUserConfigFilePath => perUserConfigFile; @@ -160,7 +156,6 @@ namespace System.Management.Automation.Configuration FileInfo info = new FileInfo(value); systemWideConfigFile = info.FullName; systemWideConfigDirectory = info.Directory.FullName; - systemConfigOverridden = true; } /// @@ -513,7 +508,7 @@ namespace System.Management.Automation.Configuration { try { - string fileName = GetConfigFilePath(scope); + string fileName = GetConfigFilePathForWrite(scope); fileLock.EnterWriteLock(); // Since multiple properties can be in a single file, replacement is required instead of overwrite if a file already exists. @@ -632,7 +627,7 @@ namespace System.Management.Automation.Configuration /// The string key of the value. private void RemoveValueFromFile(ConfigScope scope, string key) { - string fileName = GetConfigFilePath(scope); + string fileName = GetConfigFilePathForWrite(scope); // Optimization: If the file doesn't exist, there is nothing to remove if (File.Exists(fileName)) { diff --git a/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 b/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 index 2258b321c3..d26bc41dd5 100644 --- a/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 +++ b/test/powershell/engine/ExperimentalFeature/EnableDisable-ExperimentalFeature.Tests.ps1 @@ -3,26 +3,6 @@ Import-Module HelpersCommon -function Test-CanWriteToSystemConfigDir { - $dir = Split-Path (Get-PowerShellConfiguration -Scope AllUsers).Path - if (!(Test-Path $dir)) { - try { - $null = New-Item -ItemType Directory -Path $dir -Force -ErrorAction Stop - return $true - } catch { - return $false - } - } - try { - $testFile = Join-Path $dir ".pester-write-test" - Set-Content -Path $testFile -Value '' -ErrorAction Stop - Remove-Item $testFile -Force -ErrorAction SilentlyContinue - return $true - } catch { - return $false - } -} - Describe "Enable-ExperimentalFeature and Disable-ExperimentalFeature tests" -tags "Feature","RequireAdminOnWindows" { BeforeAll { diff --git a/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 b/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 index f3a39f312d..47ad3c8413 100644 --- a/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 +++ b/test/powershell/engine/ExperimentalFeature/Get-ExperimentalFeature.Tests.ps1 @@ -3,26 +3,6 @@ Import-Module HelpersCommon -function Test-CanWriteToSystemConfigDir { - $dir = Split-Path (Get-PowerShellConfiguration -Scope AllUsers).Path - if (!(Test-Path $dir)) { - try { - $null = New-Item -ItemType Directory -Path $dir -Force -ErrorAction Stop - return $true - } catch { - return $false - } - } - try { - $testFile = Join-Path $dir ".pester-write-test" - Set-Content -Path $testFile -Value '' -ErrorAction Stop - Remove-Item $testFile -Force -ErrorAction SilentlyContinue - return $true - } catch { - return $false - } -} - Describe "Get-ExperimentalFeature Tests" -tags "Feature","RequireAdminOnWindows" { BeforeAll { diff --git a/test/powershell/engine/Help/HelpSystem.Tests.ps1 b/test/powershell/engine/Help/HelpSystem.Tests.ps1 index e7b36d2a67..0da509ab97 100644 --- a/test/powershell/engine/Help/HelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/HelpSystem.Tests.ps1 @@ -16,7 +16,7 @@ $script:cmdletsToSkip = @( "Get-ExperimentalFeature", "Enable-ExperimentalFeature", "Disable-ExperimentalFeature", - "Get-PowerShellConfiguration", + "Get-PowerShellConfiguration", # New cmdlet, help content pending "Get-PSSubsystem", "Switch-Process" ) diff --git a/test/powershell/engine/PSConfiguration/Get-PowerShellConfiguration.Tests.ps1 b/test/powershell/engine/PSConfiguration/Get-PowerShellConfiguration.Tests.ps1 index 66e79dafe5..6f7bb5e408 100644 --- a/test/powershell/engine/PSConfiguration/Get-PowerShellConfiguration.Tests.ps1 +++ b/test/powershell/engine/PSConfiguration/Get-PowerShellConfiguration.Tests.ps1 @@ -22,13 +22,31 @@ Describe "Get-PowerShellConfiguration Tests" -Tags "CI" { $config.Scope | Should -Be "CurrentUser" } - It "AllUsers path points to platform-specific system config directory" { + It "AllUsers path points to system config directory or legacy PSHOME location" { $config = Get-PowerShellConfiguration -Scope AllUsers + $config.Path | Should -BeLike "*powershell.config.json" if ($IsWindows) { - $config.Path | Should -BeLike "*ProgramData*Microsoft*PowerShell*powershell.config.json" + $isNewPath = $config.Path -like "*ProgramData*Microsoft*PowerShell*" + $isLegacyPath = $config.Path -like "$PSHOME*" + ($isNewPath -or $isLegacyPath) | Should -BeTrue } else { - $config.Path | Should -Be "/etc/powershell/powershell.config.json" + $isNewPath = $config.Path -eq "/etc/powershell/powershell.config.json" + $isLegacyPath = $config.Path -like "$PSHOME*" + ($isNewPath -or $isLegacyPath) | Should -BeTrue + } + } + + It "AllUsers path prefers new location over PSHOME when config exists there" { + $config = Get-PowerShellConfiguration -Scope AllUsers + if ($IsWindows) { + $newPath = Join-Path $env:ProgramData "Microsoft\PowerShell\powershell.config.json" + } + else { + $newPath = "/etc/powershell/powershell.config.json" + } + if (Test-Path $newPath) { + $config.Path | Should -Be $newPath } } @@ -45,11 +63,6 @@ Describe "Get-PowerShellConfiguration Tests" -Tags "CI" { } } - It "AllUsers path does not point to PSHOME" { - $config = Get-PowerShellConfiguration -Scope AllUsers - $config.Path | Should -Not -BeLike "$PSHOME*" - } - It "Output type is PowerShellConfigurationInfo" { $config = Get-PowerShellConfiguration -Scope AllUsers $config.GetType().Name | Should -Be "PowerShellConfigurationInfo" diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 index dd7cd7a52a..68ffdba913 100644 --- a/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 +++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psd1 @@ -29,6 +29,7 @@ FunctionsToExport = @( 'Set-TesthookResult' 'Start-NativeExecution' 'Test-CanWriteToPsHome' + 'Test-CanWriteToSystemConfigDir' 'Test-IsElevated' 'Test-IsPreview', 'Test-IsReleaseCandidate' diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 index 02588b612d..f90b275bba 100644 --- a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 +++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 @@ -345,6 +345,37 @@ function Test-CanWriteToPsHome $script:CanWriteToPsHome } +$script:CanWriteToSystemConfigDir = $null +function Test-CanWriteToSystemConfigDir { + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '', Justification = "an error message is not appropriate for this function")] + param () + if ($null -ne $script:CanWriteToSystemConfigDir) { + return $script:CanWriteToSystemConfigDir + } + + $script:CanWriteToSystemConfigDir = $false + $dir = Split-Path (Get-PowerShellConfiguration -Scope AllUsers).Path + if (!(Test-Path $dir)) { + try { + $null = New-Item -ItemType Directory -Path $dir -Force -ErrorAction Stop + $script:CanWriteToSystemConfigDir = $true + } catch { + ; # do nothing + } + } else { + try { + $testFile = Join-Path $dir ".pester-write-test" + Set-Content -Path $testFile -Value '' -ErrorAction Stop + Remove-Item $testFile -Force -ErrorAction SilentlyContinue + $script:CanWriteToSystemConfigDir = $true + } catch { + ; # do nothing + } + } + + $script:CanWriteToSystemConfigDir +} + # Creates a password meeting Windows complexity rules function New-ComplexPassword { diff --git a/test/xUnit/csharp/test_PSConfiguration.cs b/test/xUnit/csharp/test_PSConfiguration.cs index 6451c8c71b..8dbbef4913 100644 --- a/test/xUnit/csharp/test_PSConfiguration.cs +++ b/test/xUnit/csharp/test_PSConfiguration.cs @@ -34,6 +34,7 @@ namespace PSTests.Sequential private readonly PowerShellPolicies currentUserPolicies; private readonly bool originalTestHookValue; + private readonly string originalTestAllUsersConfigDirectory; private readonly string originalSingletonSystemConfigDir; private readonly string originalSingletonSystemConfigFile; @@ -43,6 +44,7 @@ namespace PSTests.Sequential // to /etc/powershell (Unix) or %ProgramData%\Microsoft\PowerShell (Windows) in CI systemWideConfigDirectory = Path.Combine(Path.GetTempPath(), "PSTestSystemConfig_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(systemWideConfigDirectory); + originalTestAllUsersConfigDirectory = InternalTestHooks.TestAllUsersConfigDirectory; InternalTestHooks.TestAllUsersConfigDirectory = systemWideConfigDirectory; currentUserConfigDirectory = Platform.UserConfigDirectory; @@ -58,12 +60,10 @@ namespace PSTests.Sequential // Redirect the PowerShellConfig singleton to use the temp system config directory var sysConfigDirField = typeof(PowerShellConfig).GetField("systemWideConfigDirectory", BindingFlags.NonPublic | BindingFlags.Instance); var sysConfigFileField = typeof(PowerShellConfig).GetField("systemWideConfigFile", BindingFlags.NonPublic | BindingFlags.Instance); - var overriddenField = typeof(PowerShellConfig).GetField("systemConfigOverridden", BindingFlags.NonPublic | BindingFlags.Instance); originalSingletonSystemConfigDir = (string)sysConfigDirField.GetValue(PowerShellConfig.Instance); originalSingletonSystemConfigFile = (string)sysConfigFileField.GetValue(PowerShellConfig.Instance); sysConfigDirField.SetValue(PowerShellConfig.Instance, systemWideConfigDirectory); sysConfigFileField.SetValue(PowerShellConfig.Instance, systemWideConfigFile); - overriddenField.SetValue(PowerShellConfig.Instance, true); if (File.Exists(currentUserConfigFile)) { @@ -124,11 +124,9 @@ namespace PSTests.Sequential // Restore the PowerShellConfig singleton to the original system config paths var sysConfigDirField = typeof(PowerShellConfig).GetField("systemWideConfigDirectory", BindingFlags.NonPublic | BindingFlags.Instance); var sysConfigFileField = typeof(PowerShellConfig).GetField("systemWideConfigFile", BindingFlags.NonPublic | BindingFlags.Instance); - var overriddenField = typeof(PowerShellConfig).GetField("systemConfigOverridden", BindingFlags.NonPublic | BindingFlags.Instance); sysConfigDirField.SetValue(PowerShellConfig.Instance, originalSingletonSystemConfigDir); sysConfigFileField.SetValue(PowerShellConfig.Instance, originalSingletonSystemConfigFile); - overriddenField.SetValue(PowerShellConfig.Instance, false); - InternalTestHooks.TestAllUsersConfigDirectory = null; + InternalTestHooks.TestAllUsersConfigDirectory = originalTestAllUsersConfigDirectory; if (Directory.Exists(systemWideConfigDirectory)) {