From c29bd7d6841e827807b5671511f84ee385b2aad1 Mon Sep 17 00:00:00 2001 From: jeffbi Date: Thu, 25 May 2017 17:24:47 -0700 Subject: [PATCH] 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. --- .../engine/Utils.cs | 5 + .../namespaces/FileSystemProvider.cs | 110 ++++++++++++------ .../Get-ChildItem.Tests.ps1 | 68 ++++++++--- 3 files changed, 132 insertions(+), 51 deletions(-) diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 8987ab432c..6c1b3bd945 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -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; + /// This member is used for internal test purposes. public static void SetTestHook(string property, bool value) { diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index d5dde6ff71..b8e0418212 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -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 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 index 8ccf7b31ec..d3964dc8dc 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-ChildItem.Tests.ps1 @@ -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' {