Make Get-ChildItem continue enumeration when encountering error on contained item (#3806)

Added try/catch within the enumeration loop to allow the enumeration to continue after encountering an error such as an item within the directory being deleted or renamed.

To assist in testing, two new internal test hooks have been added which cause Get-ChildItem to either delete or rename a specific file (file name hard-coded) when encountered during enumeration.
This commit is contained in:
jeffbi 2017-05-25 17:24:47 -07:00 committed by Dongbo Wang
parent 7aa7f3858c
commit c29bd7d684
3 changed files with 132 additions and 51 deletions

View File

@ -1580,6 +1580,11 @@ namespace System.Management.Automation.Internal
// Simulate 'System.Diagnostics.Stopwatch.IsHighResolution is false' to test Get-Uptime throw
internal static bool StopwatchIsNotHighResolution;
// Used in the FileSystemProvider to simulate deleting a file during enumeration in Get-ChildItem
internal static bool GciEnumerationActionDelete = false;
// Used in the FileSystemProvider to simulate renaming a file during enumeration in Get-ChildItem
internal static bool GciEnumerationActionRename = false;
/// <summary>This member is used for internal test purposes.</summary>
public static void SetTestHook(string property, bool value)
{

View File

@ -1677,51 +1677,85 @@ namespace Microsoft.PowerShell.Commands
return;
}
bool attributeFilter = true;
bool switchAttributeFilter = true;
bool filterHidden = false; // "Hidden" is specified somewhere in the expression
bool switchFilterHidden = false; // "Hidden" is specified somewhere in the parameters
if (null != evaluator)
// Internal test code, run only if one of the
// 'GciEnumerationAction' test hooks are set.
if (InternalTestHooks.GciEnumerationActionDelete)
{
attributeFilter = evaluator.Evaluate(filesystemInfo.Attributes); // expressions
filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
}
if (null != switchEvaluator)
{
switchAttributeFilter = switchEvaluator.Evaluate(filesystemInfo.Attributes); // switch parameters
switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
}
bool hidden = false;
if (!Force) hidden = (filesystemInfo.Attributes & FileAttributes.Hidden) != 0;
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
// default hidden attribute filter.
// if specification is to return all containers, then do not do attribute filter on
// the containers.
bool attributeSatisfy =
((attributeFilter && switchAttributeFilter) ||
((returnContainers == ReturnContainers.ReturnAllContainers) &&
((filesystemInfo.Attributes & FileAttributes.Directory) != 0)));
if (attributeSatisfy && (filterHidden || switchFilterHidden || Force || !hidden))
{
if (nameOnly)
if (string.Equals(filesystemInfo.Name, "c283d143-2116-4809-bf11-4f7d61613f92", StringComparison.InvariantCulture))
{
WriteItemObject(
filesystemInfo.Name,
filesystemInfo.FullName,
false);
File.Delete(filesystemInfo.FullName);
}
else
}
else if (InternalTestHooks.GciEnumerationActionRename)
{
if (string.Equals(filesystemInfo.Name, "B1B691A9-B7B1-4584-AED7-5259511BEEC4", StringComparison.InvariantCulture))
{
if (filesystemInfo is FileInfo)
WriteItemObject(filesystemInfo, filesystemInfo.FullName, false);
var newFullName = Path.Combine(directory.FullName, "77efd2bb-92aa-4ad3-979a-18936a4bd565");
File.Move(filesystemInfo.FullName, newFullName);
}
}
try
{
bool attributeFilter = true;
bool switchAttributeFilter = true;
// 'Hidden' is specified somewhere in the expression
bool filterHidden = false;
// 'Hidden' is specified somewhere in the parameters
bool switchFilterHidden = false;
if (null != evaluator)
{
attributeFilter = evaluator.Evaluate(filesystemInfo.Attributes);
filterHidden = evaluator.ExistsInExpression(FileAttributes.Hidden);
}
if (null != switchEvaluator)
{
switchAttributeFilter = switchEvaluator.Evaluate(filesystemInfo.Attributes);
switchFilterHidden = switchEvaluator.ExistsInExpression(FileAttributes.Hidden);
}
bool hidden = false;
if (!Force)
{
hidden = (filesystemInfo.Attributes & FileAttributes.Hidden) != 0;
}
// If 'Hidden' is explicitly specified anywhere in the attribute filter, then override
// default hidden attribute filter.
// If specification is to return all containers, then do not do attribute filter on
// the containers.
bool attributeSatisfy =
((attributeFilter && switchAttributeFilter) ||
((returnContainers == ReturnContainers.ReturnAllContainers) &&
((filesystemInfo.Attributes & FileAttributes.Directory) != 0)));
if (attributeSatisfy && (filterHidden || switchFilterHidden || Force || !hidden))
{
if (nameOnly)
{
WriteItemObject(
filesystemInfo.Name,
filesystemInfo.FullName,
false);
}
else
WriteItemObject(filesystemInfo, filesystemInfo.FullName, true);
{
if (filesystemInfo is FileInfo)
WriteItemObject(filesystemInfo, filesystemInfo.FullName, false);
else
WriteItemObject(filesystemInfo, filesystemInfo.FullName, true);
}
}
}
catch (System.IO.FileNotFoundException ex)
{
WriteError(new ErrorRecord(ex, "DirIOError", ErrorCategory.ReadError, directory.FullName));
}
catch (UnauthorizedAccessException ex)
{
WriteError(new ErrorRecord(ex, "DirUnauthorizedAccessError", ErrorCategory.PermissionDenied, directory.FullName));
}
}// foreach
}// foreach

View File

@ -4,12 +4,18 @@ Describe "Get-ChildItem" -Tags "CI" {
BeforeAll {
# Create Test data
$null = New-Item -Path $TestDrive -Name "a" -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name "B" -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name "c" -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name "D" -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name "E" -ItemType "Directory" -Force
$null = New-Item -Path $TestDrive -Name ".F" -ItemType "File" -Force | %{$_.Attributes = "hidden"}
$item_a = "a3fe710a-31af-4834-bc29-d0b584589838"
$item_B = "B1B691A9-B7B1-4584-AED7-5259511BEEC4"
$item_c = "c283d143-2116-4809-bf11-4f7d61613f92"
$item_D = "D39B4FD9-3E1D-4DD5-8718-22FE2C934CE3"
$item_E = "EE150FEB-0F21-4AFF-8066-AF59E925810C"
$item_F = ".F81D8514-8862-4227-B041-0529B1656A43"
$null = New-Item -Path $TestDrive -Name $item_a -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name $item_B -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name $item_c -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name $item_D -ItemType "File" -Force
$null = New-Item -Path $TestDrive -Name $item_E -ItemType "Directory" -Force
$null = New-Item -Path $TestDrive -Name $item_F -ItemType "File" -Force | %{$_.Attributes = "hidden"}
}
It "Should list the contents of the current folder" {
@ -34,25 +40,25 @@ Describe "Get-ChildItem" -Tags "CI" {
It "Should list files in sorted order" {
$files = Get-ChildItem -Path $TestDrive
$files[0].Name | Should Be "E"
$files[1].Name | Should Be "a"
$files[2].Name | Should Be "B"
$files[3].Name | Should Be "c"
$files[4].Name | Should Be "D"
$files[0].Name | Should Be $item_E
$files[1].Name | Should Be $item_a
$files[2].Name | Should Be $item_B
$files[3].Name | Should Be $item_c
$files[4].Name | Should Be $item_D
}
It "Should list hidden files as well when 'Force' parameter is used" {
$files = Get-ChildItem -path $TestDrive -Force
$files | Should not be $null
$files.Count | Should be 6
$files.Name.Contains(".F")
$files.Name.Contains($item_F) | Should Be $true
}
It "Should list only hidden files when 'Hidden' parameter is used" {
$files = Get-ChildItem -path $TestDrive -Hidden
$files | Should not be $null
$files.Count | Should be 1
$files[0].Name | Should Be ".F"
$files[0].Name | Should Be $item_F
}
It "Should give .sys file if the fullpath is specified with hidden and force parameter" -Skip:(!$IsWindows){
$file = Get-ChildItem -path "$env:SystemDrive\\pagefile.sys" -Hidden
@ -60,6 +66,42 @@ Describe "Get-ChildItem" -Tags "CI" {
$file.Count | Should be 1
$file.Name | Should be "pagefile.sys"
}
It "Should continue enumerating a directory when a contained item is deleted" {
$Error.Clear()
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("GciEnumerationActionDelete", $true)
$result = Get-ChildItem -Path $TestDrive -ErrorAction SilentlyContinue
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("GciEnumerationActionDelete", $false)
if ($IsWindows)
{
$Error.Count | Should BeExactly 0
$result.Count | Should BeExactly 5
}
else
{
$Error.Count | Should BeExactly 1
$Error[0].FullyQualifiedErrorId | Should BeExactly "DirIOError,Microsoft.PowerShell.Commands.GetChildItemCommand"
$Error[0].Exception | Should BeOfType System.Io.FileNotFoundException
$result.Count | Should BeExactly 4
}
}
It "Should continue enumerating a directory when a contained item is renamed" {
$Error.Clear()
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("GciEnumerationActionRename", $true)
$result = Get-ChildItem -Path $TestDrive -ErrorAction SilentlyContinue
[System.Management.Automation.Internal.InternalTestHooks]::SetTestHook("GciEnumerationActionRename", $false)
if ($IsWindows)
{
$Error.Count | Should BeExactly 0
$result.Count | Should BeExactly 4
}
else
{
$Error.Count | Should BeExactly 1
$Error[0].FullyQualifiedErrorId | Should BeExactly "DirIOError,Microsoft.PowerShell.Commands.GetChildItemCommand"
$Error[0].Exception | Should BeOfType System.Io.FileNotFoundException
$result.Count | Should BeExactly 3
}
}
}
Context 'Env: Provider' {