Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/System.Management.Automation/engine/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1561,11 +1561,14 @@ public static class InternalTestHooks
internal static bool UseDebugAmsiImplementation;
internal static bool BypassAppLockerPolicyCaching;
internal static bool BypassOnlineHelpRetrieval;
internal static bool ThrowHelpCultureNotSupported;
internal static bool ForcePromptForChoiceDefaultOption;
internal static bool NoPromptForPassword;
internal static bool ForceFormatListFixedLabelWidth;

// Update-Help tests
internal static bool ThrowHelpCultureNotSupported;
internal static CultureInfo CurrentUICulture;

// Stop/Restart/Rename Computer tests
internal static bool TestStopComputer;
internal static bool TestWaitStopComputer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,8 @@ private void ProcessModule(UpdatableHelpModuleInfo module)
#endif
catch (UpdatableHelpSystemException e)
{
if (e.FullyQualifiedErrorId == "HelpCultureNotSupported")
if (e.FullyQualifiedErrorId == "HelpCultureNotSupported"
|| e.FullyQualifiedErrorId == "UnableToRetrieveHelpInfoXml")
{
installed = false;

Expand Down Expand Up @@ -665,7 +666,7 @@ internal bool IsUpdateNecessary(UpdatableHelpModuleInfo module, UpdatableHelpInf
}

// Culture check
if (!newHelpInfo.IsCultureSupported(culture))
if (!newHelpInfo.IsCultureSupported(culture.Name))
{
throw new UpdatableHelpSystemException("HelpCultureNotSupported",
StringUtil.Format(HelpDisplayStrings.HelpCultureNotSupported,
Expand Down
58 changes: 44 additions & 14 deletions src/System.Management.Automation/help/UpdatableHelpInfo.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Management.Automation.Internal;
using System.Text;

Expand Down Expand Up @@ -37,6 +39,44 @@ internal CultureSpecificUpdatableHelp(CultureInfo culture, Version version)
/// Supported culture.
/// </summary>
internal CultureInfo Culture { get; set; }

/// <summary>
/// Enumerates fallback chain (parents) of the culture, including itself.
/// </summary>
/// <param name="culture">Culture to enumerate</param>
/// <example>
/// Examples:
/// en-GB => { en-GB, en }
/// zh-Hans-CN => { zh-Hans-CN, zh-Hans, zh }.
/// </example>
/// <returns>An enumerable list of culture names.</returns>
internal static IEnumerable<string> GetCultureFallbackChain(CultureInfo culture)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the issues is that on Ubuntu 20+, the default culture is set as C.UTF-8.
It doesn't have 'en' in its parent chain. Can we accommodate for cases like that? If a matching culture is not found fallback to en-US?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adityapatwardhan Interesting case, but out of scope. From my skimming of the POSIX standard, C.UTF-8 is more of a default null culture and in no way equivalent to en-US. In fact, presuming everyone uses en-US is the very source of the issues I am attempting to fix.

Source issue shows the error that will be shown, I think it is well good enough:

Update-Help: Failed to update Help for the module(s) 'Microsoft.PowerShell.Core' with UI culture(s) {C}:
Unable to retrieve the HelpInfo XML file for UI culture C.
Make sure the HelpInfoUri property in the module manifest is valid or check your network connection and then try the command again..
English-US help content is available and can be installed using: Update-Help -UICulture en-US.

I could specialise the error message to also show something like "Your culture is C.UTF-8, which is a default not associated with any language, consider changing your system culture", but please create another issue for it so that it can be discussed.

Copy link
Member

@adityapatwardhan adityapatwardhan May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the updated error message is much better than the current one. It would be great if you could do that. I will open a issue for that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here: #19633

{
// We use just names instead because comparing two CultureInfo objects
// can fail if they are created using different means
while (culture != null)
{
if (string.IsNullOrEmpty(culture.Name))
{
yield break;
}

yield return culture.Name;

culture = culture.Parent;
}
}

/// <summary>
/// Checks if a culture is supported.
/// </summary>
/// <param name="cultureName">Name of the culture to check.</param>
/// <returns>True if supported, false if not.</returns>
internal bool IsCultureSupported(string cultureName)
{
Debug.Assert(cultureName != null, $"{nameof(cultureName)} may not be null");
return GetCultureFallbackChain(Culture).Any(fallback => fallback == cultureName);
}
}

/// <summary>
Expand Down Expand Up @@ -99,22 +139,12 @@ internal bool IsNewerVersion(UpdatableHelpInfo helpInfo, CultureInfo culture)
/// <summary>
/// Checks if a culture is supported.
/// </summary>
/// <param name="culture">Culture to check.</param>
/// <param name="cultureName">Name of the culture to check.</param>
/// <returns>True if supported, false if not.</returns>
internal bool IsCultureSupported(CultureInfo culture)
internal bool IsCultureSupported(string cultureName)
{
Debug.Assert(culture != null);

foreach (CultureSpecificUpdatableHelp updatableHelpItem in UpdatableHelpItems)
{
if (string.Equals(updatableHelpItem.Culture.Name, culture.Name,
StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
Debug.Assert(cultureName != null, $"{nameof(cultureName)} may not be null");
return UpdatableHelpItems.Any(item => item.IsCultureSupported(cultureName));
}

/// <summary>
Expand Down
20 changes: 5 additions & 15 deletions src/System.Management.Automation/help/UpdatableHelpSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,19 +294,13 @@ internal IEnumerable<string> GetCurrentUICulture()
{
CultureInfo culture = CultureInfo.CurrentUICulture;

while (culture != null)
// Allow tests to override system culture
if (InternalTestHooks.CurrentUICulture != null)
{
if (string.IsNullOrEmpty(culture.Name))
{
yield break;
}

yield return culture.Name;

culture = culture.Parent;
culture = InternalTestHooks.CurrentUICulture;
}

yield break;
return CultureSpecificUpdatableHelp.GetCultureFallbackChain(culture);
}

#region Help Metadata Retrieval
Expand Down Expand Up @@ -591,13 +585,9 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo

if (!string.IsNullOrEmpty(currentCulture))
{
IEnumerable<WildcardPattern> patternList = SessionStateUtilities.CreateWildcardsFromStrings(
globPatterns: new[] { currentCulture },
options: WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant);

for (int i = 0; i < updatableHelpItem.Length; i++)
{
if (SessionStateUtilities.MatchesAnyWildcardPattern(updatableHelpItem[i].Culture.Name, patternList, true))
if (updatableHelpItem[i].IsCultureSupported(currentCulture))
{
helpInfo.HelpContentUriCollection.Add(new UpdatableHelpUri(moduleName, moduleGuid, updatableHelpItem[i].Culture, uri));
}
Expand Down
53 changes: 52 additions & 1 deletion test/powershell/engine/Help/HelpSystem.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function UpdateHelpFromLocalContentPath {
throw "Unable to find help content at '$helpContentPath'"
}

# Test files are 'en-US', set explicit culture so test does not help on non-US systems
# Test files are 'en-US', set explicit culture so test does not fail on non-US systems
Update-Help -Module $ModuleName -SourcePath $helpContentPath -UICulture 'en-US' -Force -ErrorAction Stop -Scope $Scope
}

Expand Down Expand Up @@ -613,3 +613,54 @@ Describe 'help renders when using a PAGER with a space in the path' -Tags 'CI' {
help Get-Command | Should -Be "R2V0LUNvbW1hbmQ="
}
}

Describe 'Update-Help allows partial culture matches' -Tags 'CI' {
BeforeAll {
function Test-UpdateHelpAux($UICulture, $Pass)
{
# If null (in system culture tests), omit entirely
$CultureArg = $UICulture ? @{ UICulture = $UICulture } : @{}
$Args = @{
Module = 'Microsoft.PowerShell.Core'
SourcePath = Join-Path $PSScriptRoot 'assets'
Force = $true
ErrorAction = $Pass ? 'Stop' : 'SilentlyContinue'
ErrorVariable = 'ErrorVariable'
}

Update-Help @Args @CultureArg

if (-not $Pass) {
$ErrorVariable | Should -Match 'Failed to update Help for the module.*'
}
}
}

AfterEach {
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('CurrentUICulture', $null)
}

It 'Checks culture match against en-US: <UICulture>' -TestCases @(
@{ UICulture = 'en-US' }
@{ UICulture = 'en' }
@{ UICulture = 'en-GB'; Pass = $false }
@{ UICulture = 'de-DE'; Pass = $false }
) {
param($UICulture, $Pass = $true)

Test-UpdateHelpAux $UICulture $Pass
}

# When using system culture, "en-GB" will use "en" as fallback, so passes
It 'Checks system culture match against en-US: <UICulture>' -TestCases @(
@{ UICulture = 'en-US' }
@{ UICulture = 'en' }
@{ UICulture = 'en-GB' }
@{ UICulture = 'de-DE'; Pass = $false }
) {
param($UICulture, $Pass = $true)

[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('CurrentUICulture', [System.Globalization.CultureInfo]::new($UICulture))
Test-UpdateHelpAux $null $Pass
}
}