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.
This commit is contained in:
Steve Lee 2019-02-20 12:23:27 -08:00 committed by Dongbo Wang
parent 4de3a72142
commit b06ad6aa61
3 changed files with 56 additions and 51 deletions

View file

@ -16,35 +16,34 @@ namespace System.Management.Automation.Internal
// - Ignore files/directories when access is denied; // - Ignore files/directories when access is denied;
// - Search top directory only. // - Search top directory only.
private static readonly System.IO.EnumerationOptions s_defaultEnumerationOptions = 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. // 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. // 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: // The buffer size 16K is recommended in the comment of the 'BufferSize' property:
// "A "large" buffer, for example, would be 16K. Typical is 4K." // "A "large" buffer, for example, would be 16K. Typical is 4K."
private static readonly System.IO.EnumerationOptions s_uncPathEnumerationOptions = 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";
/// <summary> /// <summary>
/// Check if a directory could be a module folder. /// Check if a directory is likely a localized resources folder.
/// </summary> /// </summary>
internal static bool IsPossibleModuleDirectory(string dir) /// <param name="dir">Directory to check if it is a possible resource folder.</param>
/// <returns>True if the directory name matches a culture.</returns>
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. // Assume locale directories do not contain modules.
if (dir.EndsWith(@"\en", StringComparison.OrdinalIgnoreCase) || if (dir.EndsWith(EnCulturePath, StringComparison.OrdinalIgnoreCase) ||
dir.EndsWith(@"\en-us", StringComparison.OrdinalIgnoreCase)) dir.EndsWith(EnUsCulturePath, StringComparison.OrdinalIgnoreCase))
{ {
return false; return true;
} }
dir = Path.GetFileName(dir); dir = Path.GetFileName(dir);
// Use some simple pattern matching to avoid the call into GetCultureInfo when we know it will fail (and throw). // 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])) 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 // This might not throw on invalid culture still
// 4096 is considered the unknown locale - so assume that could be a module // 4096 is considered the unknown locale - so assume that could be a module
var cultureInfo = new CultureInfo(dir); var cultureInfo = new CultureInfo(dir);
return cultureInfo.LCID == 4096; return cultureInfo.LCID != 4096;
} }
catch { } catch { }
} }
return true; return false;
} }
/// <summary> /// <summary>
@ -75,6 +74,7 @@ namespace System.Management.Automation.Internal
Queue<string> directoriesToCheck = new Queue<string>(); Queue<string> directoriesToCheck = new Queue<string>();
directoriesToCheck.Enqueue(topDirectoryToCheck); directoriesToCheck.Enqueue(topDirectoryToCheck);
bool firstSubDirs = true;
while (directoriesToCheck.Count > 0) while (directoriesToCheck.Count > 0)
{ {
string directoryToCheck = directoriesToCheck.Dequeue(); string directoryToCheck = directoriesToCheck.Dequeue();
@ -83,7 +83,7 @@ namespace System.Management.Automation.Internal
string[] subDirectories = Directory.GetDirectories(directoryToCheck, "*", options); string[] subDirectories = Directory.GetDirectories(directoryToCheck, "*", options);
foreach (string toAdd in subDirectories) foreach (string toAdd in subDirectories)
{ {
if (IsPossibleModuleDirectory(toAdd)) if (firstSubDirs || !IsPossibleResourceDirectory(toAdd))
{ {
directoriesToCheck.Enqueue(toAdd); directoriesToCheck.Enqueue(toAdd);
} }
@ -92,6 +92,7 @@ namespace System.Management.Automation.Internal
catch (IOException) { } catch (IOException) { }
catch (UnauthorizedAccessException) { } catch (UnauthorizedAccessException) { }
firstSubDirs = false;
string[] files = Directory.GetFiles(directoryToCheck, "*", options); string[] files = Directory.GetFiles(directoryToCheck, "*", options);
foreach (string moduleFile in files) foreach (string moduleFile in files)
{ {
@ -304,17 +305,14 @@ namespace System.Management.Automation.Internal
{ {
foreach (var subdirectory in subdirectories) 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) || directoriesToCheck.AddFirst(subdirectory);
subdirectory.EndsWith("Microsoft.PowerShell.Utility", StringComparison.OrdinalIgnoreCase)) }
{ else
directoriesToCheck.AddFirst(subdirectory); {
} directoriesToCheck.AddLast(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<string, CommandTypes> exportedCommands = AnalysisCache.GetExportedCommands(modulePath, testOnly: false, context); IDictionary<string, CommandTypes> exportedCommands = AnalysisCache.GetExportedCommands(modulePath, testOnly: false, context);

View file

@ -370,7 +370,7 @@ namespace System.Management.Automation
// * and SearchOption.AllDirectories gets all the version directories. // * and SearchOption.AllDirectories gets all the version directories.
string[] directories = Directory.GetDirectories(psModulePath, "*", SearchOption.AllDirectories); 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) foreach (string directory in possibleModuleDirectories)
{ {

View file

@ -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\Foo\2.0" -Force > $null
New-Item -ItemType Directory -Path "$testdrive\Modules\Bar\Download" -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\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\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\Foo\2.0\Foo.psd1" -ModuleVersion 2.0
New-ModuleManifest -Path "$testdrive\Modules\Bar\Bar.psd1" New-ModuleManifest -Path "$testdrive\Modules\Bar\Bar.psd1"
New-ModuleManifest -Path "$testdrive\Modules\Zoo\Zoo.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\1.1\Foo.psm1" > $null
New-Item -ItemType File -Path "$testdrive\Modules\Foo\2.0\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\Bar\Download\Download.psm1" > $null
New-Item -ItemType File -Path "$testdrive\Modules\Zoo\Zoo.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\Zoo\Too\Zoo.psm1" > $null
New-Item -ItemType File -Path "$testdrive\Modules\Az\Az.psm1" > $null
$fullyQualifiedPathTestCases = @( $fullyQualifiedPathTestCases = @(
# The current behaviour in PowerShell is that version gets ignored when using Get-Module -FullyQualifiedName with a path # 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" { It "Get-Module -ListAvailable" {
$modules = 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 = $modules | Sort-Object -Property Name, Version
$modules.Name -join "," | Should -BeExactly "Bar,Foo,Foo,Zoo" $modules.Name -join "," | Should -BeExactly "Az,Bar,Foo,Foo,Zoo"
$modules[1].Version | Should -Be "1.1" $modules[0].Version | Should -Be "1.1"
$modules[2].Version | Should -Be '2.0' $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 <Name> -ListAvailable" { It "Get-Module <Name> -ListAvailable" {
@ -58,25 +63,27 @@ Describe "Get-Module -ListAvailable" -Tags "CI" {
It "Get-Module -ListAvailable -All" { It "Get-Module -ListAvailable -All" {
$modules = 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 = $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[0].ModuleType | Should -BeExactly "Manifest"
$modules[1].ModuleType | Should -BeExactly "Script" $modules[1].ModuleType | Should -BeExactly "Script"
$modules[2].ModuleType | Should -BeExactly "Script" $modules[2].ModuleType | Should -BeExactly "Manifest"
$modules[3].ModuleType | Should -BeExactly "Manifest" $modules[3].ModuleType | Should -BeExactly "Script"
$modules[3].Version | Should -Be "1.1"
$modules[4].ModuleType | Should -BeExactly "Script" $modules[4].ModuleType | Should -BeExactly "Script"
$modules[5].ModuleType | Should -BeExactly "Manifest" $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[6].ModuleType | Should -BeExactly "Script"
$modules[7].ModuleType | Should -BeExactly "Script" $modules[7].ModuleType | Should -BeExactly "Manifest"
$modules[7].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path $modules[7].Version | Should -Be "2.0"
$modules[8].ModuleType | Should -BeExactly "Manifest" $modules[8].ModuleType | Should -BeExactly "Script"
$modules[8].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Zoo.psd1").Path
$modules[9].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 <Name> -ListAvailable -All" { It "Get-Module <Name> -ListAvailable -All" {
@ -93,19 +100,19 @@ Describe "Get-Module -ListAvailable" -Tags "CI" {
It "Get-Module <Path> -ListAvailable" { It "Get-Module <Path> -ListAvailable" {
$modules = Get-Module "$testdrive\Modules\*" -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 = $modules | Sort-Object -Property Name, Version
$modules.Name -join "," | Should -BeExactly "Bar,Foo,Foo,Zoo" $modules.Name -join "," | Should -BeExactly "Az,Bar,Foo,Foo,Zoo"
$modules[1].Version | Should -Be "1.1" $modules[2].Version | Should -Be "1.1"
$modules[2].Version | Should -Be '2.0' $modules[3].Version | Should -Be '2.0'
} }
It "Get-Module <Path> -ListAvailable -All" { It "Get-Module <Path> -ListAvailable -All" {
$modules = Get-Module "$testdrive\Modules\*" -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 = $modules | Sort-Object -Property Name, Path
$modules.Name -join "," | Should -BeExactly "Bar,Foo,Foo,Zoo,Zoo" $modules.Name -join "," | Should -BeExactly "Az,Bar,Foo,Foo,Zoo,Zoo"
$modules[3].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path $modules[4].Path | Should -BeExactly (Resolve-Path "$testdrive\Modules\Zoo\Too\Zoo.psm1").Path
} }
It "Get-Module -FullyQualifiedName <FullyQualifiedName> -ListAvailable" { It "Get-Module -FullyQualifiedName <FullyQualifiedName> -ListAvailable" {