diff --git a/NTFSSecurity/ItemCmdlets/GetChildItem2.cs b/NTFSSecurity/ItemCmdlets/GetChildItem2.cs index 8a31073..82dcdf1 100644 --- a/NTFSSecurity/ItemCmdlets/GetChildItem2.cs +++ b/NTFSSecurity/ItemCmdlets/GetChildItem2.cs @@ -150,8 +150,8 @@ namespace NTFSSecurity // for itself. Before 5.0.0, [1] was read as a character class, so a file with brackets in its name was not // returned for its name. The enumeration returns every item for *.* as Windows does, so the comparison does // too; with the dot as an ordinary character, it would drop the items without a dot, most folders. - // CultureInvariant: the enumeration ignores case without the culture of the thread, and with tr-TR the - // comparison would otherwise treat I and i as different letters and drop items that the enumeration matched. + // CultureInvariant: like the enumeration (see MoveNextInvariant), the comparison would otherwise treat I and i as + // different letters with tr-TR and drop items that the enumeration returned. var pattern = filter == "*.*" ? "*" : filter; wildcard = new WildcardPattern(pattern.Replace("`", "``").Replace("[", "`[").Replace("]", "`]"), WildcardOptions.Compiled | WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); @@ -208,18 +208,33 @@ namespace NTFSSecurity { if (directory) { - var files = di.EnumerateDirectories(filter, global::System.IO.SearchOption.TopDirectoryOnly); - WriteFileSystemInfoCollection(files.GetEnumerator()); + IEnumerator files; + using (new InvariantCultureScope()) + { + files = di.EnumerateDirectories(filter, global::System.IO.SearchOption.TopDirectoryOnly).GetEnumerator(); + } + + WriteFileSystemInfoCollection(files); } else if (file) { - var files = di.EnumerateFiles(filter, global::System.IO.SearchOption.TopDirectoryOnly); - WriteFileSystemInfoCollection(files.GetEnumerator()); + IEnumerator files; + using (new InvariantCultureScope()) + { + files = di.EnumerateFiles(filter, global::System.IO.SearchOption.TopDirectoryOnly).GetEnumerator(); + } + + WriteFileSystemInfoCollection(files); } else { - var files = di.EnumerateFileSystemInfos(filter, global::System.IO.SearchOption.TopDirectoryOnly); - WriteFileSystemInfoCollection(files.GetEnumerator()); + IEnumerator files; + using (new InvariantCultureScope()) + { + files = di.EnumerateFileSystemInfos(filter, global::System.IO.SearchOption.TopDirectoryOnly).GetEnumerator(); + } + + WriteFileSystemInfoCollection(files); } } @@ -286,7 +301,7 @@ namespace NTFSSecurity protected void WriteFileSystemInfoCollection(IEnumerator fileSystemInfos) { - while (fileSystemInfos.MoveNext()) + while (MoveNextInvariant(fileSystemInfos)) { FileSystemInfo current = (FileSystemInfo)fileSystemInfos.Current; if (!wildcard.IsMatch(current.Name)) @@ -359,5 +374,32 @@ namespace NTFSSecurity { base.EndProcessing(); } + + // AlphaFS 2.2 matches the filter with a regular expression that takes the culture of the thread, and with tr-TR the + // letters I and i differ: index* did not match Index.txt. The enumeration therefore runs with the invariant culture, + // which is back before an item reaches the pipeline, so that no later command sees it. + private static bool MoveNextInvariant(IEnumerator enumerator) + { + using (new InvariantCultureScope()) + { + return enumerator.MoveNext(); + } + } + + private sealed class InvariantCultureScope : IDisposable + { + private readonly System.Globalization.CultureInfo previous; + + public InvariantCultureScope() + { + previous = System.Threading.Thread.CurrentThread.CurrentCulture; + System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.InvariantCulture; + } + + public void Dispose() + { + System.Threading.Thread.CurrentThread.CurrentCulture = previous; + } + } } } diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 0d907db..9adbfe5 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -159,10 +159,10 @@ Describe 'Get-ChildItem2' { ($result.Name | Sort-Object) -join ',' | Should -Be (($Expected | Sort-Object) -join ',') } - # With the culture of the thread tr-TR, a case-insensitive comparison that is not culture invariant treats I and i as - # different letters: the cmdlet dropped the items whose names differ in the case of an I from the pattern, which - # the enumeration had matched without regard to the culture. INDEX* is the control: both sides turn I into the - # dotless letter. + # With the culture of the thread tr-TR, a case-insensitive regular expression that is not culture invariant treats I and i + # as different letters. AlphaFS matches the filter that way, and so did the comparison of the cmdlet, so the cmdlet + # dropped the items whose names differ from the filter in the case of an I. INDEX* is the control: both sides turn I + # into the dotless letter. It 'Should match -Filter ignoring case in Turkish, where I and i are different letters' -ForEach @( @{ Filter = 'index*'; Expected = 'Index.txt' } @{ Filter = 'INDEX*'; Expected = 'Index.txt' }