Powershell: Get-Culture and $PSCulture do not (consistently) reflect in-session changes to another culture

Created on 20 May 2017  路  19Comments  路  Source: PowerShell/PowerShell

Note: Perhaps this is by design (although there's at least a buggy inconsistency), given that there's currently no _PowerShell_ mechanism for _in-session_ switching to another culture. If so, it should at least be documented.

Steps to reproduce

[pscustomobject] @{ Test = 'initial'; Name = (Get-Culture).Name}
[cultureinfo]::currentculture = 'fr-FR'; [pscustomobject] @{ Test = '$PSCulture'; Name =$PSCulture }, [pscustomobject] @{ Test = 'GetCulture'; Name = (Get-Culture).Name }, [pscustomobject] @{ Test = '$host.CurrentCulture'; Name = $host.CurrentCulture.Name }, [pscustomobject] @{ Test = '.NET'; Name = [cultureinfo]::currentculture.name }

Note:

  • If you try the above _interactively_, be sure to execute the 2nd line above _on a single line_.
  • The output below:

    • assumes that en-US is the initial culture at session startup time.

    • differs between PowerShell Core and Windows PowerShell: in Core, Get-Culture and $host.CurrentCulture reflect the switch, but $PSCulture doesn't, whereas in Windows PowerShell none of the PS cmdlets/properties do.

Expected behavior

Test                 Name 
----                 ---- 
initial              en-US
$PSCulture           fr-FR
GetCulture           fr-FR
$host.CurrentCulture fr-FR
.NET                 fr-FR

Actual behavior

  • PowerShell Core: Windows, macOS, Ubuntu
Test                 Name 
----                 ---- 
initial              en-US
$PSCulture           en-US
GetCulture           fr-FR
$host.CurrentCulture fr-FR
.NET                 fr-FR
  • Windows PowerShell 5.1
Test                 Name 
----                 ---- 
initial              en-US
$PSCulture           en-US
GetCulture           en-US
$host.CurrentCulture en-US
.NET                 fr-FR

Environment data

PowerShell Core v6.0.0-beta (v6.0.0-beta.1) on macOS 10.12.5
PowerShell Core v6.0.0-beta (v6.0.0-beta.1) on Ubuntu 16.04.1 LTS
PowerShell Core v6.0.0-beta (v6.0.0-beta.1) on Microsoft Windows 10 Pro (64-bit; v10.0.14393)
Windows PowerShell v5.1.14393.1198 on Microsoft Windows 10 Pro (64-bit; v10.0.14393)
Breaking-Change Committee-Reviewed Issue-Bug Resolution-Fixed WG-Engine

Most helpful comment

And yes there is a bug right in the initialization code. In SessionStateScope.cs:SetVariable(), the code takes the carefully constructed custom-type object, extracts the basic information, creates a new vanilla PSVariable and inserts that into session state instead of using the custom variable. I would guess this has been broken for forever. Fixing it to use the original variable is technically a breaking change but it's probably worth it.

All 19 comments

PowerShell does not follow the changes in .Net. This is true for changing current directory too.

Well, as you can see, in Core it _does_ reflect .NET-level changes in Get-Culture, but not in $PSCulture.

Also, with respect to the current directory: there's justification for PowerShell "doing its own thing", because it has its own system of drives, the majority of which have no .NET counterpart.

The same justification doesn't apply to culture settings, which are shared by both worlds, especially given that cmdlets _do_ already respect in-session switching; e.g.:

> [cultureinfo]::currentculture = 'fr-FR'; Get-Date

samedi 20 mai 2017 13:39:08

That means we have to make sure PowerShell "not doing its own things" for Culture.

@iSazonov - yes, and that condition appears to be met (I haven't dug into the source). Do you know something to the contrary? Or are you just saying that an investigation / official feedback is needed?

@mklement0 Please see Get-Culture code.

Looking at the source code, it looks like Get-Culture simply delegates to $host.CurrentCulture, which is consistent with the (updated) findings above.

In the case of a console host, this seems to lead to ConsoleHost.cs, which has the following:

        public override System.Globalization.CultureInfo CurrentCulture
        {
            get
            {
                lock (hostGlobalLock)
                {
#if !CORECLR
                    return NativeCultureResolver.Culture;
#else
                    return CultureInfo.CurrentCulture;
#endif
                }
            }
        }
  • That explains why _Core's_ Get-Culture / $host.CurrentCulture is always in sync with [cultureinfo]::CurrentCulture.

  • For _Windows_ PowerShell, the value is _cached_ in NativeCultureResolver.cs, which explains why it can get out of sync with the thread's changed-after-session-startup culture.

So we can explain the disparity, but the question is _what the design intent was_ and, therefore, _how the inconsistency should be resolved_ (my vote is to always reflect the thread's then-current culture).

I agree that a consistency should be. After quickly view codes, it seems PowerShell use Cultureinfo.CurrentCulture without caching.

@mklement0 Could you please update a status of the issue? Should we consider a fix for $PSCulture?

@iSazonov:

The status hasn't changed. Yes, I think we should fix $PSCulture to reflect the culture in effect for the thread (session) at hand, to be consistent with Get-Culture and $host.CurrentCulture.

It is a departure from Windows PowerShell, however, so it needs to be documented.
(Note that backward compatibility is _already_ broken, due to Get-Culture and $host.CurrentCulture acting differently than in Windows PowerShell).

@SteveL-MSFT We need PowerShell Committee conclusion.

See also: the related discussion in https://github.com/PowerShell/PowerShell/issues/3833#issuecomment-431837446

@iSazonov it would help @PowerShell/powershell-committee if you or @mklement0 could summarize the question you want @PowerShell/powershell-committee to answer

@SteveL-MSFT:

In Windows PowerShell:

  • Get-Culture reflects _the current user's persistently defined culture_, irrespective of (transient) in-session changes.
  • $PSCulture and $host.CurrentCulture reflect the same value.

In PowerShell Core:

  • Get-Culture always reflects the _current thread's_ culture, which may or may not have been changed in-session.

  • $host.CurrentCulture reflects the same value, whereas $PSCulture reflects the culture that was in effect at session-startup time.

The suggestion is to correct this inconsistency and have $PSCulture reflect the same value as Get-Culture and $host.CurrentCulture.

As an aside: The above shows that we've already broken backward compatibility, though it won't typically surface, as changing the culture in-session is not common.
When it comes to introducing Set-Culture to PS Core, the difference becomes more problematic - see https://github.com/PowerShell/PowerShell/issues/3833#issuecomment-431837446.

$PSCulture and $PSUICulture are implemented as specific subclasses of PSVariable . For example $PSCulture is defined in CultureVariable.cs and looks like:

    /// <summary>
    /// A variable that represents $PSCulture
    /// </summary>
    internal class PSCultureVariable : PSVariable
    {
        /// <summary>
        /// Constructs an instance of the variable.
        /// </summary>
        internal PSCultureVariable()
            : base(SpecialVariables.PSCulture, true, ScopedItemOptions.ReadOnly | ScopedItemOptions.AllScope,
                   RunspaceInit.DollarPSCultureDescription)
        {
        }

        /// <summary>
        /// Gets or sets the value of the variable.
        /// </summary>
        ///
        public override object Value
        {
            get
            {
                DebuggerCheckVariableRead();
                return System.Globalization.CultureInfo.CurrentCulture.Name;
            }
        }
    }

As you can see, it's not a cached value and should reflect the current culture.

@BrucePay: That can't be the whole story, as the following test demonstrates:

# With en-US in effect *at session startup*:
PS> [cultureinfo]::CurrentCulture = 'de-dE'; [cultureinfo]::CurrentCulture.Name; $PSCulture
de-DE
en-US  # !! session-startup value is still in effect

I suspect there's a bug where the special binding for $PSCulture is being replaced by a regular PSVariable. I'm still investigating to what's going on.

And yes there is a bug right in the initialization code. In SessionStateScope.cs:SetVariable(), the code takes the carefully constructed custom-type object, extracts the basic information, creates a new vanilla PSVariable and inserts that into session state instead of using the custom variable. I would guess this has been broken for forever. Fixing it to use the original variable is technically a breaking change but it's probably worth it.

@PowerShell/powershell-committee reviewed this. We agree the intent is that Get-Culture and $PSCulture have the same behavior and reflects the current thread culture. This looks like a long standing bug.

:tada:This issue was addressed in #10138, which has now been successfully released as v7.0.0-preview.5.:tada:

Handy links:

Was this page helpful?
0 / 5 - 0 ratings