/********************************************************************++ Copyright (c) Microsoft Corporation. All rights reserved. Descripton: Windows Vista and later support non-traditional UI fallback ie., a user on an Arabic machine can choose either French or English(US) as UI fallback language. CLR does not support this (non-traditional) fallback mechanism. So the static methods in this class calculate appropriate UI Culture natively. ConsoleHot uses this API to set correct Thread UICulture. Dependent on: GetThreadPreferredUILanguages SetThreadPreferredUILanguages These methods are available on Windows Vista and later. --********************************************************************/ using System; using System.Globalization; using System.Runtime.InteropServices; using System.Text; using Dbg = System.Management.Automation.Diagnostics; using WORD = System.UInt16; namespace Microsoft.PowerShell { /// /// Custom culture /// internal class VistaCultureInfo : CultureInfo { private string[] _fallbacks; // Cache the immediate parent and immediate fallback private VistaCultureInfo _parentCI = null; private object _syncObject = new object(); /// /// Constructs a CultureInfo that keeps track of fallbacks /// /// Name of the culture to construct. /// /// ordered,null-delimited list of fallbacks /// public VistaCultureInfo(string name, string[] fallbacks) : base(name) { _fallbacks = fallbacks; } /// /// Returns Parent culture for the current CultureInfo. /// If Parent.Name is null or empty, then chooses the immediate fallback /// If it is not empty, otherwise just returns Parent. /// public override CultureInfo Parent { get { // First traverse the parent heirarchy as established by CLR. // This is required because there is difference in the parent heirarchy // between CLR and Windows for Chinese. Ex: Native windows has // zh-CN->zh-Hans->neutral whereas CLR has zh-CN->zh-CHS->zh-Hans->neutral if ((null != base.Parent) && (!string.IsNullOrEmpty(base.Parent.Name))) { return ImmediateParent; } // Check whether we have any fallback specified // MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK // returns fallback cultures (specified by the user) // and also adds neutral culture where appropriate. // Ex: ja-jp ja en-us en while ((null != _fallbacks) && (_fallbacks.Length > 0)) { string fallback = _fallbacks[0]; string[] fallbacksForParent = null; if (_fallbacks.Length > 1) { fallbacksForParent = new string[_fallbacks.Length - 1]; Array.Copy(_fallbacks, 1, fallbacksForParent, 0, _fallbacks.Length - 1); } try { return new VistaCultureInfo(fallback, fallbacksForParent); } // if there is any exception constructing the culture..catch..and go to // the next culture in the list. catch (ArgumentException) { _fallbacks = fallbacksForParent; } } //no fallbacks..just return base parent return base.Parent; } } /// /// This is called to create the parent culture (as defined by CLR) /// of the current culture. /// private VistaCultureInfo ImmediateParent { get { if (null == _parentCI) { lock (_syncObject) { if (null == _parentCI) { string parentCulture = base.Parent.Name; // remove the parentCulture from the m_fallbacks list. // ie., remove duplicates from the parent heirarchy. string[] fallbacksForTheParent = null; if (null != _fallbacks) { fallbacksForTheParent = new string[_fallbacks.Length]; int currentIndex = 0; foreach (string culture in _fallbacks) { if (!parentCulture.Equals(culture, StringComparison.OrdinalIgnoreCase)) { fallbacksForTheParent[currentIndex] = culture; currentIndex++; } } // There is atlease 1 duplicate in m_fallbacks which was not added to // fallbacksForTheParent array. Resize the array to take care of this. if (_fallbacks.Length != currentIndex) { Array.Resize(ref fallbacksForTheParent, currentIndex); } } _parentCI = new VistaCultureInfo(parentCulture, fallbacksForTheParent); } } } return _parentCI; } } /// /// Clones the custom CultureInfo retaining the fallbacks. /// /// Cloned custom CultureInfo public override object Clone() { return new VistaCultureInfo(base.Name, _fallbacks); } } /// /// Static wrappers to get User chosen UICulture (for Vista and later) /// internal static class NativeCultureResolver { private static CultureInfo s_uiCulture = null; private static CultureInfo s_culture = null; private static object s_syncObject = new object(); /// /// Gets the UICulture to be used by console host /// internal static CultureInfo UICulture { get { if (null == s_uiCulture) { lock (s_syncObject) { if (null == s_uiCulture) { s_uiCulture = GetUICulture(); } } } return (CultureInfo)s_uiCulture.Clone(); } } internal static CultureInfo Culture { get { if (null == s_culture) { lock (s_syncObject) { if (null == s_culture) { s_culture = GetCulture(); } } } return s_culture; } } internal static CultureInfo GetUICulture() { return GetUICulture(true); } internal static CultureInfo GetCulture() { return GetCulture(true); } internal static CultureInfo GetUICulture(bool filterOutNonConsoleCultures) { if (!IsVistaAndLater()) { s_uiCulture = EmulateDownLevel(); return s_uiCulture; } // We are running on Vista string langBuffer = GetUserPreferredUILangs(filterOutNonConsoleCultures); if (!string.IsNullOrEmpty(langBuffer)) { try { string[] fallbacks = langBuffer.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries); string fallback = fallbacks[0]; string[] fallbacksForParent = null; if (fallbacks.Length > 1) { fallbacksForParent = new string[fallbacks.Length - 1]; Array.Copy(fallbacks, 1, fallbacksForParent, 0, fallbacks.Length - 1); } s_uiCulture = new VistaCultureInfo(fallback, fallbacksForParent); return s_uiCulture; } catch (ArgumentException) { } } s_uiCulture = EmulateDownLevel(); return s_uiCulture; } [System.Diagnostics.CodeAnalysis.SuppressMessage("GoldMan", "#pw17903:UseOfLCID", Justification = "In XP and below GetUserDefaultLocaleName is not available")] internal static CultureInfo GetCulture(bool filterOutNonConsoleCultures) { CultureInfo returnValue; try { if (!IsVistaAndLater()) { int lcid = GetUserDefaultLCID(); returnValue = new CultureInfo(lcid); } else { // Vista and above StringBuilder name = new StringBuilder(16); if (0 == GetUserDefaultLocaleName(name, 16)) { // ther is an error retrieving the culture, // just use the current thread's culture returnValue = CultureInfo.CurrentCulture; } else { returnValue = new CultureInfo(name.ToString().Trim()); } } if (filterOutNonConsoleCultures) { // filter out languages that console cannot display.. // Sometimes GetConsoleFallbackUICulture returns neutral cultures // like "en" on "ar-SA". However neutral culture cannot be // asssigned as CurrentCulture. CreateSpecificCulture fixes // this problem. returnValue = CultureInfo.CreateSpecificCulture( returnValue.GetConsoleFallbackUICulture().Name); } } catch (ArgumentException) { // if there is any exception retrieving the // culture, just use the current thread's culture. returnValue = CultureInfo.CurrentCulture; } return returnValue; } [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] internal static extern WORD GetUserDefaultUILanguage(); /// /// Constructs CultureInfo object without considering any Vista and later /// custom culture fallback logic. /// /// A CultureInfo object [System.Diagnostics.CodeAnalysis.SuppressMessage("GoldMan", "#pw17903:UseOfLCID", Justification = "This is only called In XP and below where GetUserDefaultLocaleName is not available, or as a fallback when GetThreadPreferredUILanguages fails")] private static CultureInfo EmulateDownLevel() { // GetConsoleFallbackUICulture is not required. // This is retained in order not to break existing code. ushort langId = NativeCultureResolver.GetUserDefaultUILanguage(); CultureInfo ci = new CultureInfo((int)langId); return ci.GetConsoleFallbackUICulture(); } /// /// Checks if the current operating system is Vista or later /// /// /// true, if vista and above /// false, otherwise. /// private static bool IsVistaAndLater() { // The version number is obtained from MSDN // 4 - Windows NT 4.0, Windows Me, Windows 98, or Windows 95. // 5 - Windows Server 2003 R2, Windows Server 2003, Windows XP, or Windows 2000. // 6 - Windows Vista or Windows Server "Longhorn". if (Environment.OSVersion.Version.Major >= 6) { return true; } return false; } /// /// This method is called on vista and above. /// Using GetThreadPreferredUILanguages this method gets /// the UI languages a user has chosen. /// /// /// List of ThredPreferredUILanguages. /// /// /// This method will work only on Vista and later. /// private static string GetUserPreferredUILangs(bool filterOutNonConsoleCultures) { long numberLangs = 0; int bufferSize = 0; string returnval = ""; if (filterOutNonConsoleCultures) { // Filter out languages that do not support console. // The third paramter should be null otherwise this API will not // set Console CodePage filter. // The MSDN documentation does not call this out explicitly. Opened // Bug 950 (Windows Developer Content) to track this. if (!SetThreadPreferredUILanguages(s_MUI_CONSOLE_FILTER, null, IntPtr.Zero)) { return returnval; } } // calculate buffer size required // MUI_MERGE_SYSTEM_FALLBACK | MUI_MERGE_USER_FALLBACK // returns fallback cultures (specified by the user) // and also adds neutral culture where appropriate. // Ex: ja-jp ja en-us en if (!GetThreadPreferredUILanguages( s_MUI_LANGUAGE_NAME | s_MUI_MERGE_SYSTEM_FALLBACK | s_MUI_MERGE_USER_FALLBACK, out numberLangs, null, out bufferSize)) { return returnval; } // calculate space required to store output. // StringBuilder will not work for this case as CLR // does not copy the entire string if there are delimiter ('\0') // in the middle of a string. byte[] langBufferPtr = new byte[bufferSize * 2]; // Now get the actual value if (!GetThreadPreferredUILanguages( s_MUI_LANGUAGE_NAME | s_MUI_MERGE_SYSTEM_FALLBACK | s_MUI_MERGE_USER_FALLBACK, out numberLangs, langBufferPtr, // Pointer to a buffer in which this function retrieves an ordered, null-delimited list. out bufferSize)) { return returnval; } try { string langBuffer = Encoding.Unicode.GetString(langBufferPtr); returnval = langBuffer.Trim().ToLowerInvariant(); return returnval; } catch (ArgumentNullException) { } catch (System.Text.DecoderFallbackException) { } return returnval; } #region Dll Import data /// /// Returns the locale identifier for the user default locale. /// /// /// /// This function can return data from custom locales. Locales are not /// guaranteed to be the same from computer to computer or between runs /// of an application. If your application must persist or transmit data, /// see Using Persistent Locale Data. /// Applications that are intended to run only on Windows Vista and later /// should use GetUserDefaultLocaleName in preference to this function. /// GetUserDefaultLocaleName provides good support for supplemental locales. /// However, GetUserDefaultLocaleName is not supported for versions of Windows /// prior to Windows Vista. /// [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] private static extern int GetUserDefaultLCID(); /// /// Retrieves the user default locale name. /// /// /// /// /// Returns the size of the buffer containing the locale name, including /// the terminating null character, if successful. The function returns 0 /// if it does not succeed. To get extended error information, the applciation /// can call GetLastError. Possible returns from GetLastError /// include ERR_INSUFFICIENT_BUFFER. /// /// /// /// [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] private static extern int GetUserDefaultLocaleName( [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpLocaleName, int cchLocaleName); [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] private static extern bool SetThreadPreferredUILanguages(int dwFlags, StringBuilder pwszLanguagesBuffer, IntPtr pulNumLanguages); [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] private static extern bool GetThreadPreferredUILanguages(int dwFlags, out long pulNumLanguages, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pwszLanguagesBuffer, out int pcchLanguagesBuffer); [DllImport("kernel32.dll", SetLastError = false, CharSet = CharSet.Unicode)] internal static extern Int16 SetThreadUILanguage(Int16 langId); //private static int MUI_LANGUAGE_ID = 0x4; private static int s_MUI_LANGUAGE_NAME = 0x8; private static int s_MUI_CONSOLE_FILTER = 0x100; private static int s_MUI_MERGE_USER_FALLBACK = 0x20; private static int s_MUI_MERGE_SYSTEM_FALLBACK = 0x10; #endregion } }