diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs index c851c2bc81..65e55a86ab 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs @@ -126,49 +126,10 @@ namespace Microsoft.PowerShell.Commands foreach (string path in pathsToProcess) { - byte[] bytehash = null; - string hash = null; - Stream openfilestream = null; - - try + if (ComputeFileHash(path, out string hash)) { - openfilestream = File.OpenRead(path); - bytehash = hasher.ComputeHash(openfilestream); - - hash = BitConverter.ToString(bytehash).Replace("-", string.Empty); WriteHashResult(Algorithm, hash, path); } - catch (FileNotFoundException ex) - { - var errorRecord = new ErrorRecord( - ex, - "FileNotFound", - ErrorCategory.ObjectNotFound, - path); - WriteError(errorRecord); - } - catch (UnauthorizedAccessException ex) - { - var errorRecord = new ErrorRecord( - ex, - "UnauthorizedAccessError", - ErrorCategory.InvalidData, - path); - WriteError(errorRecord); - } - catch (IOException ioException) - { - var errorRecord = new ErrorRecord( - ioException, - "FileReadError", - ErrorCategory.ReadError, - path); - WriteError(errorRecord); - } - finally - { - openfilestream?.Dispose(); - } } } @@ -190,6 +151,61 @@ namespace Microsoft.PowerShell.Commands } } + /// + /// Read the file and calculate the hash. + /// + /// Path to file which will be hashed. + /// Will contain the hash of the file content. + /// Boolean value indicating whether the hash calculation succeeded or failed. + private bool ComputeFileHash(string path, out string hash) + { + byte[] bytehash = null; + Stream openfilestream = null; + + hash = null; + + try + { + openfilestream = File.OpenRead(path); + + bytehash = hasher.ComputeHash(openfilestream); + hash = BitConverter.ToString(bytehash).Replace("-", string.Empty); + } + catch (FileNotFoundException ex) + { + var errorRecord = new ErrorRecord( + ex, + "FileNotFound", + ErrorCategory.ObjectNotFound, + path); + WriteError(errorRecord); + } + catch (UnauthorizedAccessException ex) + { + var errorRecord = new ErrorRecord( + ex, + "UnauthorizedAccessError", + ErrorCategory.InvalidData, + path); + WriteError(errorRecord); + } + catch (IOException ioException) + { + var errorRecord = new ErrorRecord( + ioException, + "FileReadError", + ErrorCategory.ReadError, + path); + WriteError(errorRecord); + } + finally + { + openfilestream?.Dispose(); + } + + return hash != null; + } + /// /// Create FileHashInfo object and output it. /// diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FileHash.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FileHash.Tests.ps1 index eac9bd9c90..6cb3118fec 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FileHash.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-FileHash.Tests.ps1 @@ -77,4 +77,19 @@ Describe "Get-FileHash" -Tags "CI" { $result.Path | Should -Be $testDocument } } + + Context "File should be closed before Get-FileHash writes pipeline output" { + It "Should be able to edit the file without 'file is in use' exceptions" { + # This test runs against a copy of the document + # because it involves renaming it, + # and that might break tests added later on. + $testDocumentCopy = "${testDocument}-copy" + Copy-Item -Path $testdocument -Destination $testDocumentCopy + + $newPath = Get-FileHash -Path $testDocumentCopy | Rename-Item -NewName {$_.Hash} -PassThru + $newPath.FullName | Should -Exist + + Remove-Item -Path $testDocumentCopy -Force + } + } }