From b06ad6aa6164986c9cf3a8248aec0eb91b9dca0d Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Wed, 20 Feb 2019 12:23:27 -0800 Subject: [PATCH] Enable discovering modules that have names same as a culture (e.g. Az) (#8777) Here are the major changes: - Add logic to skip checking for possible resource directories for the first set of sub directories from the top level. - There was an additional skip if the folder is hidden, rather than doing an explicit attribute check, change the `EnumerationOption` to skip hidden folders. - Since the `IsPossibleModuleDirectory()` helper now only checks to see if the name matches a culture, renamed to `IsPossibleResourceDirectory()` - When getting the default modules, we don't search recursively into individual module folders, so removed additional check for possible resource directory. --- .../engine/Modules/ModuleUtils.cs | 54 +++++++++---------- .../help/HelpFileHelpProvider.cs | 2 +- .../Get-Module.Tests.ps1 | 51 ++++++++++-------- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index adc3d98c4..0c24fa002 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs @@ -16,35 +16,34 @@ namespace System.Management.Automation.Internal // - Ignore files/directories when access is denied; // - Search top directory only. private static readonly System.IO.EnumerationOptions s_defaultEnumerationOptions = - new System.IO.EnumerationOptions() { AttributesToSkip = 0 }; + new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden }; // Default option for UNC path enumeration. Same as above plus a large buffer size. // For network shares, a large buffer may result in better performance as more results can be batched over the wire. // The buffer size 16K is recommended in the comment of the 'BufferSize' property: // "A "large" buffer, for example, would be 16K. Typical is 4K." private static readonly System.IO.EnumerationOptions s_uncPathEnumerationOptions = - new System.IO.EnumerationOptions() { AttributesToSkip = 0, BufferSize = 16384 }; + new System.IO.EnumerationOptions() { AttributesToSkip = FileAttributes.Hidden, BufferSize = 16384 }; + + private static readonly string EnCulturePath = Path.DirectorySeparatorChar + "en"; + private static readonly string EnUsCulturePath = Path.DirectorySeparatorChar + "en-us"; /// - /// Check if a directory could be a module folder. + /// Check if a directory is likely a localized resources folder. /// - internal static bool IsPossibleModuleDirectory(string dir) + /// Directory to check if it is a possible resource folder. + /// True if the directory name matches a culture. + internal static bool IsPossibleResourceDirectory(string dir) { - // We shouldn't be searching in hidden directories. - FileAttributes attributes = File.GetAttributes(dir); - if (0 != (attributes & FileAttributes.Hidden)) - { - return false; - } - // Assume locale directories do not contain modules. - if (dir.EndsWith(@"\en", StringComparison.OrdinalIgnoreCase) || - dir.EndsWith(@"\en-us", StringComparison.OrdinalIgnoreCase)) + if (dir.EndsWith(EnCulturePath, StringComparison.OrdinalIgnoreCase) || + dir.EndsWith(EnUsCulturePath, StringComparison.OrdinalIgnoreCase)) { - return false; + return true; } dir = Path.GetFileName(dir); + // Use some simple pattern matching to avoid the call into GetCultureInfo when we know it will fail (and throw). if ((dir.Length == 2 && char.IsLetter(dir[0]) && char.IsLetter(dir[1])) || @@ -55,12 +54,12 @@ namespace System.Management.Automation.Internal // This might not throw on invalid culture still // 4096 is considered the unknown locale - so assume that could be a module var cultureInfo = new CultureInfo(dir); - return cultureInfo.LCID == 4096; + return cultureInfo.LCID != 4096; } catch { } } - return true; + return false; } /// @@ -75,6 +74,7 @@ namespace System.Management.Automation.Internal Queue directoriesToCheck = new Queue(); directoriesToCheck.Enqueue(topDirectoryToCheck); + bool firstSubDirs = true; while (directoriesToCheck.Count > 0) { string directoryToCheck = directoriesToCheck.Dequeue(); @@ -83,7 +83,7 @@ namespace System.Management.Automation.Internal string[] subDirectories = Directory.GetDirectories(directoryToCheck, "*", options); foreach (string toAdd in subDirectories) { - if (IsPossibleModuleDirectory(toAdd)) + if (firstSubDirs || !IsPossibleResourceDirectory(toAdd)) { directoriesToCheck.Enqueue(toAdd); } @@ -92,6 +92,7 @@ namespace System.Management.Automation.Internal catch (IOException) { } catch (UnauthorizedAccessException) { } + firstSubDirs = false; string[] files = Directory.GetFiles(directoryToCheck, "*", options); foreach (string moduleFile in files) { @@ -304,17 +305,14 @@ namespace System.Management.Automation.Internal { foreach (var subdirectory in subdirectories) { - if (IsPossibleModuleDirectory(subdirectory)) + if (subdirectory.EndsWith("Microsoft.PowerShell.Management", StringComparison.OrdinalIgnoreCase) || + subdirectory.EndsWith("Microsoft.PowerShell.Utility", StringComparison.OrdinalIgnoreCase)) { - if (subdirectory.EndsWith("Microsoft.PowerShell.Management", StringComparison.OrdinalIgnoreCase) || - subdirectory.EndsWith("Microsoft.PowerShell.Utility", StringComparison.OrdinalIgnoreCase)) - { - directoriesToCheck.AddFirst(subdirectory); - } - else - { - directoriesToCheck.AddLast(subdirectory); - } + directoriesToCheck.AddFirst(subdirectory); + } + else + { + directoriesToCheck.AddLast(subdirectory); } } } @@ -487,7 +485,7 @@ namespace System.Management.Automation.Internal } } - string moduleShortName = System.IO.Path.GetFileNameWithoutExtension(modulePath); + string moduleShortName = Path.GetFileNameWithoutExtension(modulePath); IDictionary exportedCommands = AnalysisCache.GetExportedCommands(modulePath, testOnly: false, context); diff --git a/src/System.Management.Automation/help/HelpFileHelpProvider.cs b/src/System.Management.Automation/help/HelpFileHelpProvider.cs index 60769b6de..32066611a 100644 --- a/src/System.Management.Automation/help/HelpFileHelpProvider.cs +++ b/src/System.Management.Automation/help/HelpFileHelpProvider.cs @@ -370,7 +370,7 @@ namespace System.Management.Automation // * and SearchOption.AllDirectories gets all the version directories. string[] directories = Directory.GetDirectories(psModulePath, "*", SearchOption.AllDirectories); - var possibleModuleDirectories = directories.Where(directory => ModuleUtils.IsPossibleModuleDirectory(directory)); + var possibleModuleDirectories = directories.Where(directory => !ModuleUtils.IsPossibleResourceDirectory(directory)); foreach (string directory in possibleModuleDirectories) { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1 index b6ea7d8dc..bbff5b8d9 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Module.Tests.ps1 @@ -10,11 +10,13 @@ Describe "Get-Module -ListAvailable" -Tags "CI" { New-Item -ItemType Directory -Path "$testdrive\Modules\Foo\2.0" -Force > $null New-Item -ItemType Directory -Path "$testdrive\Modules\Bar\Download" -Force > $null New-Item -ItemType Directory -Path "$testdrive\Modules\Zoo\Too" -Force > $null + New-Item -ItemType Directory -Path "$testdrive\Modules\Az" -Force > $null New-ModuleManifest -Path "$testdrive\Modules\Foo\1.1\Foo.psd1" -ModuleVersion 1.1 New-ModuleManifest -Path "$testdrive\Modules\Foo\2.0\Foo.psd1" -ModuleVersion 2.0 New-ModuleManifest -Path "$testdrive\Modules\Bar\Bar.psd1" New-ModuleManifest -Path "$testdrive\Modules\Zoo\Zoo.psd1" + New-ModuleManifest -Path "$testdrive\Modules\Az\Az.psd1" -ModuleVersion 1.1 New-Item -ItemType File -Path "$testdrive\Modules\Foo\1.1\Foo.psm1" > $null New-Item -ItemType File -Path "$testdrive\Modules\Foo\2.0\Foo.psm1" > $null @@ -22,6 +24,7 @@ Describe "Get-Module -ListAvailable" -Tags "CI" { New-Item -ItemType File -Path "$testdrive\Modules\Bar\Download\Download.psm1" > $null New-Item -ItemType File -Path "$testdrive\Modules\Zoo\Zoo.psm1" > $null New-Item -ItemType File -Path "$testdrive\Modules\Zoo\Too\Zoo.psm1" > $null + New-Item -ItemType File -Path "$testdrive\Modules\Az\Az.psm1" > $null $fullyQualifiedPathTestCases = @( # The current behaviour in PowerShell is that version gets ignored when using Get-Module -FullyQualifiedName with a path @@ -40,11 +43,13 @@ Describe "Get-Module -ListAvailable" -Tags "CI" { It "Get-Module -ListAvailable" { $modules = Get-Module -ListAvailable - $modules.Count | Should -Be 4 + $modules.Count | Should -Be 5 $modules = $modules | Sort-Object -Property Name, Version - $modules.Name -join "," | Should -BeExactly "Bar,Foo,Foo,Zoo" - $modules[1].Version | Should -Be "1.1" - $modules[2].Version | Should -Be '2.0' + $modules.Name -join "," | Should -BeExactly "Az,Bar,Foo,Foo,Zoo" + $modules[0].Version | Should -Be "1.1" + $modules[1].Version | Should -Be "0.0.1" + $modules[2].Version | Should -Be '1.1' + $modules[3].Version | Should -Be '2.0' } It "Get-Module -ListAvailable" { @@ -58,25 +63,27 @@ Describe "Get-Module -ListAvailable" -Tags "CI" { It "Get-Module -ListAvailable -All" { $modules = Get-Module -ListAvailable -All - $modules.Count | Should -Be 10 + $modules.Count | Should -Be 12 $modules = $modules | Sort-Object -Property Name, Path - $modules.Name -join "," | Should -BeExactly "Bar,Bar,Download,Foo,Foo,Foo,Foo,Zoo,Zoo,Zoo" + $modules.Name -join "," | Should -BeExactly "Az,Az,Bar,Bar,Download,Foo,Foo,Foo,Foo,Zoo,Zoo,Zoo" $modules[0].ModuleType | Should -BeExactly "Manifest" $modules[1].ModuleType | Should -BeExactly "Script" - $modules[2].ModuleType | Should -BeExactly "Script" - $modules[3].ModuleType | Should -BeExactly "Manifest" - $modules[3].Version | Should -Be "1.1" + $modules[2].ModuleType | Should -BeExactly "Manifest" + $modules[3].ModuleType | Should -BeExactly "Script" $modules[4].ModuleType | Should -BeExactly "Script" $modules[5].ModuleType | Should -BeExactly "Manifest" - $modules[5].Version | Should -Be "2.0" + $modules[5].Version | Should -Be "1.1" $modules[6].ModuleType | Should -BeExactly "Script" - $modules[7].ModuleType | Should -BeExactly "Script" - $modules[7].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path - $modules[8].ModuleType | Should -BeExactly "Manifest" - $modules[8].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Zoo.psd1").Path + $modules[7].ModuleType | Should -BeExactly "Manifest" + $modules[7].Version | Should -Be "2.0" + $modules[8].ModuleType | Should -BeExactly "Script" $modules[9].ModuleType | Should -BeExactly "Script" - $modules[9].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Zoo.psm1").Path + $modules[9].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path + $modules[10].ModuleType | Should -BeExactly "Manifest" + $modules[10].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Zoo.psd1").Path + $modules[11].ModuleType | Should -BeExactly "Script" + $modules[11].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Zoo.psm1").Path } It "Get-Module -ListAvailable -All" { @@ -93,19 +100,19 @@ Describe "Get-Module -ListAvailable" -Tags "CI" { It "Get-Module -ListAvailable" { $modules = Get-Module "$testdrive\Modules\*" -ListAvailable - $modules.Count | Should -Be 4 + $modules.Count | Should -Be 5 $modules = $modules | Sort-Object -Property Name, Version - $modules.Name -join "," | Should -BeExactly "Bar,Foo,Foo,Zoo" - $modules[1].Version | Should -Be "1.1" - $modules[2].Version | Should -Be '2.0' + $modules.Name -join "," | Should -BeExactly "Az,Bar,Foo,Foo,Zoo" + $modules[2].Version | Should -Be "1.1" + $modules[3].Version | Should -Be '2.0' } It "Get-Module -ListAvailable -All" { $modules = Get-Module "$testdrive\Modules\*" -ListAvailable -All - $modules.Count | Should -Be 5 + $modules.Count | Should -Be 6 $modules = $modules | Sort-Object -Property Name, Path - $modules.Name -join "," | Should -BeExactly "Bar,Foo,Foo,Zoo,Zoo" - $modules[3].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path + $modules.Name -join "," | Should -BeExactly "Az,Bar,Foo,Foo,Zoo,Zoo" + $modules[4].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path } It "Get-Module -FullyQualifiedName -ListAvailable" {