Reputation: 119
I need to run a piece of code (c# or powershell) that will set the USER culture, at the system level, not thread level. ie, said code will run, and when I open Control Panel again, I will see the Format settings changed to that setting. I've seen lots of conversations about CultureInfo, but they are all for that thread, I need USER level. Using .NET 4.6.1, and for windows 7. The cmdlet from powershell works on Windows8 and 10 only.
I've tried many methods in CultureInfo, as in .net4.6 and up, it is read/write. However, it only seems to work for the current thread or threads. But after execution of this(see below), I go to control panel and do not see my changes. The powershell cmdlet does this for you.
CultureInfo.CurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");
CultureInfo.CurrentUICulture = new CultureInfo("en-US");
None seem to do what a simple powershell cmdlet can do.
# works for win8 and win10 only
set-culture en-US
I would like to have a way of changing the current culture for the USER, and then any applications opened by following automation tasks will execute with that setting. Thanks in advance!
Upvotes: 3
Views: 993
Reputation: 1673
This is an oldie but in case someone stumbles upon that: I disassembled the DLL and ended up with the following code to set a locale. Please also look at the comment I literally put in my code base too.
[DllImport ("kernelbase.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int NlsUpdateLocale (string LocaleName, int Flags);
public static void SetLocale (string locale)
{
// NOTE: this code was derived by disassembling the Set-Culture PowerShell command located at C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.InternationalSettings.Commands\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.InternationalSettings.Commands.dll
// However the PowerShell command has a branch that executes when the result of NlsUpdateLocale is not 0.
// This branch then sets all values that make up a locale separately. This is probably for cultures that are customized.
// Thus this branch is not implemented here and 0 simply leads to an error stating that Set-Culture should be called for that user manually in order to have the correct date, time, number and currency format for Excel rendering.
var NLS_LOCALE_SELECT = 1;
var NLS_LOCALE_CLEAR_USER_DATA = 2;
var result = NlsUpdateLocale (locale, NLS_LOCALE_SELECT | NLS_LOCALE_CLEAR_USER_DATA);
if (result != 0)
throw new Exception ($"There ws an error setting the user locale to '{locale}'. Please set the locale for the service user of this service manually by executing the PowerShell command 'Set-Culture {locale}'. Result of NlsUpdateLocale was {result} instead of 0. ");
}
This method should be fine when using locales (or cultures however you call them) that are known to Windows. For example: en-US, de-AT, de-DE and so on.
Consider that some older operating systems might not support all cultures that newer operating systems do. For example Windows Server 2012 does not have en-CH available in .NET code whereas Windows 10 does. I did not test however if this also holds true for that DLL-method but I would suppose so.
You can get a list of the supported .NET cultures by issuing the following PowerShell command: [System.Globalization.CultureInfo]::GetCultures([System.Globalization.CultureTypes]::AllCultures)
Upvotes: 2
Reputation: 33738
That's because, as the documentation clearly states, setting the current culture via CultureInfo
is thread-specific.
Powershell's set-culture
command does something entirely different by calling into unmanaged Win32 functions, as you can see from the disassembly of the cmdlet:
[SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults")]
protected override void ProcessRecord()
{
if (LPAPIWrapper.NlsUpdateLocale(cultureinfo.Name, NLS_LOCALE_SELECT | NLS_LOCALE_CLEAR_USER_DATA) == 0)
{
NumberFormatInfo numberFormat = cultureinfo.NumberFormat;
DateTimeFormatInfo dateTimeFormat = cultureinfo.DateTimeFormat;
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICURRDIGITS, numberFormat.CurrencyDecimalDigits.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONDECIMALSEP, numberFormat.CurrencyDecimalSeparator.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SMONTHOUSANDSEP, numberFormat.CurrencyGroupSeparator.ToString());
SetGroupSizes(LOCALE_SMONGROUPING, numberFormat.CurrencyGroupSizes);
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_INEGCURR, numberFormat.CurrencyNegativePattern.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ICURRENCY, numberFormat.CurrencyPositivePattern.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY, numberFormat.CurrencySymbol.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDIGITSUBSTITUTION, numberFormat.DigitSubstitution.ToString());
string text = "";
string[] nativeDigits = numberFormat.NativeDigits;
foreach (string str in nativeDigits)
{
text += str;
}
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNATIVEDIGITS, text);
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SNEGATIVESIGN, numberFormat.NegativeSign.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDIGITS, numberFormat.NumberDecimalDigits.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, numberFormat.NumberDecimalSeparator.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, numberFormat.NumberGroupSeparator.ToString());
SetGroupSizes(LOCALE_SGROUPING, numberFormat.NumberGroupSizes);
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_INEGNUMBER, numberFormat.NumberNegativePattern.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SPOSITIVESIGN, numberFormat.PositiveSign.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, dateTimeFormat.DateSeparator.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIME, dateTimeFormat.TimeSeparator.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, dateTimeFormat.ShortDatePattern.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLONGDATE, dateTimeFormat.LongDatePattern.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIMEFORMAT, dateTimeFormat.LongTimePattern.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S1159, dateTimeFormat.AMDesignator.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_S2359, dateTimeFormat.PMDesignator.ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTDAYOFWEEK, ((int)(dateTimeFormat.FirstDayOfWeek + 6) % 7).ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IFIRSTWEEKOFYEAR, ((int)dateTimeFormat.CalendarWeekRule).ToString());
LPAPIWrapper.SetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SYEARMONTH, dateTimeFormat.YearMonthPattern.ToString());
LPAPIWrapper.SendNotifyMessage((IntPtr)65535, 26u, IntPtr.Zero, "intl");
}
}
If you want to analyse what's going on further you can disassemble the dll located at C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.InternationalSettings.Commands\v4.0_3.0.0.0__31bf3856ad364e35\Microsoft.InternationalSettings.Commands.dll
Upvotes: 4