From ca2d328da7dd34cca7ce083bf656344fa72d2929 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 26 Jul 2022 17:44:56 -0700
Subject: [PATCH 01/34] added and updated tests for basic functionality
---
Tests/Compress-Archive.Tests.ps1 | 112 +++++++++++++++++++++++++++++--
1 file changed, 105 insertions(+), 7 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index cfa3ad9..c0c2cc5 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -276,26 +276,94 @@ BeforeDiscovery {
$content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-2/Sample-3.txt
}
- It "Validate that a single file can be compressed" {
- $sourcePath = "TestDrive:/SourceDir/ChildDir-1/Sample-2.txt"
- $destinationPath = "TestDrive:/archive1.zip"
+ It "Compresses a single file" {
+ $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt"
+ $destinationPath = "$TestDrive$($DS)archive1.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -BeZipArchiveOnlyContaining @('Sample-2.txt')
}
- It "Validate that an empty folder can be compressed" {
- $sourcePath = "TestDrive:/EmptyDir"
- $destinationPath = "TestDrive:/archive2.zip"
+ It "Compresses a non-empty directory" {
+ $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1"
+ $destinationPath = "$TestDrive$($DS)archive4.zip"
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('ChildDir-1/', 'ChildDir-1/Sample-2.txt')
+ }
+
+ It "Compresses an empty directory" {
+ $sourcePath = "$TestDrive$($DS)EmptyDir"
+ $destinationPath = "$TestDrive$($DS)archive2.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -BeZipArchiveOnlyContaining @('EmptyDir/')
}
+ It "Compresses multiple files" {
+ $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt")
+ $destinationPath = "$TestDrive$($DS)archive2.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt')
+ }
+
+ It "Compress multiple files and a single empty-directory" {
+ $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
+ "$TestDrive$($DS)SourceDir$($DS)ChildEmptyDir")
+
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'EmptyDir/')
+ }
+
+ It "Compresses multiple files and a single non-empty directory" {
+ $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
+ "$TestDrive$($DS)SourceDir$($DS)ChildDir-1")
+
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-1/', 'ChildDir-1/Sample-2.txt')
+ }
+
+ It "Compresses multiple files and non-empty directories" {
+ $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
+ "$TestDrive$($DS)SourceDir$($DS)ChildDir-1", "$TestDrive$($DS)SourceDir$($DS)ChildDir-2")
+
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt')
+ }
+
+ It "Compresses multiple files, non-empty directories, and an empty directory" {
+ $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
+ "$TestDrive$($DS)SourceDir$($DS)ChildDir-1", "$TestDrive$($DS)SourceDir$($DS)ChildDir-2", "$TestDrive$($DS)SourceDir$($DS)ChildEmptyDir")
+
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "EmptyDir/")
+ }
+
It "Validate a folder containing files, non-empty folders, and empty folders can be compressed" {
$sourcePath = "TestDrive:/SourceDir"
$destinationPath = "TestDrive:/archive3.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt')
}
+
+ It "Compresses a zero-byte file" {
+ $sourcePath = "$TestDrive$($DS)EmptyFile"
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ $contents = @('EmptyFile')
+ Test-ZipArchive $destinationPath $contents
+ }
}
Context "Update tests" -Skip {
@@ -525,9 +593,10 @@ BeforeDiscovery {
$content = "Some Data"
$content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ New-Item -LiteralPath "$TestDrive$($DS)Source[]Dir" -Type Directory | Out-Null
+ $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)file1[].txt
}
-
It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
$sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
$destinationPath = "TestDrive:/Sample[]SingleFile.zip"
@@ -544,6 +613,35 @@ BeforeDiscovery {
$destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
Remove-Item -LiteralPath $destinationPath
}
+
+ It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
+ $sourcePath = "$TestDrive$($DS)Source[]Dir"
+ "Some Random Content" | Out-File -LiteralPath "$sourcePath$($DS)Sample[]File.txt"
+ $destinationPath = "$TestDrive$($DS)archive1.zip"
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ }
+ finally
+ {
+ Remove-Item -LiteralPath $sourcePath -Force -Recurse
+ }
+ }
+
+ It "Accepts LiteralPath parameter for a file with wildcards in the filename" {
+ $sourcePath = "$TestDrive$($DS)SourceDir($DS)file1[].txt"
+ $destinationPath = "$TestDrive$($DS)archive1.zip"
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ }
+ finally
+ {
+ Remove-Item -LiteralPath $sourcePath -Force -Recurse
+ }
+ }
}
Context "test" -Tag lol {
From 39158b59781ce21b28a6564728b1b1d609649026 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 27 Jul 2022 13:02:44 -0700
Subject: [PATCH 02/34] added and worked on Expand-Archive cmdlet but it is
incomplete
---
Tests/Compress-Archive.Tests.ps1 | 6 +++---
src/CompressArchiveCommand.cs | 7 +------
src/ErrorMessages.cs | 15 +++++++++------
src/Localized/Messages.resx | 14 ++++++++------
src/WriteMode.cs | 6 ++++++
5 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index c0c2cc5..4942ca6 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -405,7 +405,7 @@ BeforeDiscovery {
}
catch
{
- $_.FullyQualifiedErrorId | Should -Be "ArchiveExists,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExists,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
@@ -435,7 +435,7 @@ BeforeDiscovery {
}
catch
{
- $_.FullyQualifiedErrorId | Should -Be "ArchiveExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
@@ -450,7 +450,7 @@ BeforeDiscovery {
}
catch
{
- $_.FullyQualifiedErrorId | Should -Be "ArchiveExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
diff --git a/src/CompressArchiveCommand.cs b/src/CompressArchiveCommand.cs
index de521d9..cadecd0 100644
--- a/src/CompressArchiveCommand.cs
+++ b/src/CompressArchiveCommand.cs
@@ -16,12 +16,7 @@ namespace Microsoft.PowerShell.Archive
{
[Cmdlet("Compress", "Archive", SupportsShouldProcess = true)]
[OutputType(typeof(FileInfo))]
- public sealed class CompressArchiveCommand : PSCmdlet
- {
-
- // TODO: Add filter parameter
- // TODO: Add flatten parameter
- // TODO: Add comments to methods
+ public sealed class CompressArchiveCommand : PSCmdlet {
// TODO: Add tar support
private enum ParameterSet
diff --git a/src/ErrorMessages.cs b/src/ErrorMessages.cs
index 80ec0c5..0b68a9b 100644
--- a/src/ErrorMessages.cs
+++ b/src/ErrorMessages.cs
@@ -30,8 +30,8 @@ internal static string GetErrorMessage(ErrorCode errorCode)
ErrorCode.PathNotFound => Messages.PathNotFoundMessage,
ErrorCode.InvalidPath => Messages.InvalidPathMessage,
ErrorCode.DuplicatePaths => Messages.DuplicatePathsMessage,
- ErrorCode.ArchiveExists => Messages.ArchiveExistsMessage,
- ErrorCode.ArchiveExistsAsDirectory => Messages.ArchiveExistsAsDirectoryMessage,
+ ErrorCode.DestinationExists => Messages.DestinationExistsMessage,
+ ErrorCode.DestinationExistsAsDirectory => Messages.DestinationExistsAsDirectoryMessage,
ErrorCode.ArchiveReadOnly => Messages.ArchiveIsReadOnlyMessage,
ErrorCode.ArchiveDoesNotExist => Messages.ArchiveDoesNotExistMessage,
ErrorCode.ArchiveIsNonEmptyDirectory => Messages.ArchiveIsNonEmptyDirectory,
@@ -53,10 +53,10 @@ internal enum ErrorCode
InvalidPath,
// Used when when a path has been supplied to the cmdlet at least twice
DuplicatePaths,
- // Used when DestinationPath is an existing file
- ArchiveExists,
+ // Used when DestinationPath is an existing file (used in Compress-Archive & Expand-Archive)
+ DestinationExists,
// Used when DestinationPath is an existing directory
- ArchiveExistsAsDirectory,
+ DestinationExistsAsDirectory,
// Used when DestinationPath is a non-empty directory and Action Overwrite is specified
ArchiveIsNonEmptyDirectory,
// Used when Compress-Archive cmdlet is in Update mode but the archive is read-only
@@ -72,6 +72,9 @@ internal enum ErrorCode
// Used when the cmdlet could not overwrite DestinationPath
OverwriteDestinationPathFailed,
// Used when the user enters the working directory as DestinationPath and it is an existing folder and -WriteMode Overwrite is specified
- CannotOverwriteWorkingDirectory
+ // Used in Compress-Archive, Expand-Archive
+ CannotOverwriteWorkingDirectory,
+ // Expand-Archive: used when a path resolved to multiple paths when only one was needed
+ PathResolvedToMultiplePaths,
}
}
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index e230be9..7f9ebfc 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -126,12 +126,6 @@
The archive {0} does not exist.
-
- The destination path {0} is a directory.
-
-
- The destination path {0} already exists.
-
The archive {0} does not have an extension or an extension that matches the chosen archive format.
@@ -152,6 +146,11 @@
Create
+
+ The destination path {0} is a directory.
+
+
+ The destination path {0} already exists.
The path(s) {0} have been specified more than once.
@@ -174,6 +173,9 @@
{0}% complete
+
+ The path {0} refers to multiple paths, but only one was expected. Perhaps this occured because multiple paths matched the wildcard (if applicable).
+
A path supplied to -LiteralPath is the same as the path supplied to -DestinationPath.
diff --git a/src/WriteMode.cs b/src/WriteMode.cs
index 00efad3..5a1ae75 100644
--- a/src/WriteMode.cs
+++ b/src/WriteMode.cs
@@ -13,4 +13,10 @@ public enum WriteMode
Update,
Overwrite
}
+
+ public enum ExpandArchiveWriteMode
+ {
+ Expand,
+ Overwrite
+ }
}
From 2f3da2dabf574fbbc5b4f8fc552c09d1dbe604aa Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 27 Jul 2022 17:31:32 -0700
Subject: [PATCH 03/34] worked on Expand-Archive, added IEntry class, added
support for ShouldProcess in Expand-Archive
---
Tests/Compress-Archive.Tests.ps1 | 104 ++++++++++---
src/ArchiveCommandBase.cs | 52 +++++++
src/ErrorMessages.cs | 4 +-
src/ExpandArchiveCommand.cs | 251 +++++++++++++++++++++++++++++++
src/IArchive.cs | 2 +
src/IEntry.cs | 15 ++
src/Localized/Messages.resx | 6 +-
src/TarArchive.cs | 5 +
src/ZipArchive.cs | 42 ++++++
9 files changed, 453 insertions(+), 28 deletions(-)
create mode 100644 src/ArchiveCommandBase.cs
create mode 100644 src/ExpandArchiveCommand.cs
create mode 100644 src/IEntry.cs
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 4942ca6..4acaf64 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -274,6 +274,15 @@ BeforeDiscovery {
$content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
$content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
$content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-2/Sample-3.txt
+
+ "Hello, World!" | Out-File -FilePath $TestDrive$($DS)HelloWorld.txt
+
+ # Create a zero-byte file
+ New-Item $TestDrive$($DS)EmptyFile -Type File | Out-Null
+
+ # Create a file whose last write time is before 1980
+ $content | Out-File -FilePath $TestDrive$($DS)OldFile.txt
+ Set-ItemProperty -Path $TestDrive$($DS)OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
}
It "Compresses a single file" {
@@ -285,7 +294,7 @@ BeforeDiscovery {
It "Compresses a non-empty directory" {
$sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1"
- $destinationPath = "$TestDrive$($DS)archive4.zip"
+ $destinationPath = "$TestDrive$($DS)archive2.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
@@ -294,76 +303,109 @@ BeforeDiscovery {
It "Compresses an empty directory" {
$sourcePath = "$TestDrive$($DS)EmptyDir"
- $destinationPath = "$TestDrive$($DS)archive2.zip"
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -BeZipArchiveOnlyContaining @('EmptyDir/')
}
It "Compresses multiple files" {
$sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt")
- $destinationPath = "$TestDrive$($DS)archive2.zip"
+ $destinationPath = "$TestDrive$($DS)archive4.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt')
}
- It "Compress multiple files and a single empty-directory" {
+ It "Compresses multiple files and a single empty directory" {
$sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
"$TestDrive$($DS)SourceDir$($DS)ChildEmptyDir")
- $destinationPath = "$TestDrive$($DS)archive3.zip"
+ $destinationPath = "$TestDrive$($DS)archive5.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'EmptyDir/')
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildEmptyDir/')
}
It "Compresses multiple files and a single non-empty directory" {
$sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
- "$TestDrive$($DS)SourceDir$($DS)ChildDir-1")
+ "$TestDrive$($DS)SourceDir$($DS)ChildDir-2")
- $destinationPath = "$TestDrive$($DS)archive3.zip"
+ $destinationPath = "$TestDrive$($DS)archive6.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-1/', 'ChildDir-1/Sample-2.txt')
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-2/', 'ChildDir-2/Sample-3.txt')
}
It "Compresses multiple files and non-empty directories" {
- $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
+ $sourcePath = @("$TestDrive$($DS)HelloWorld.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
"$TestDrive$($DS)SourceDir$($DS)ChildDir-1", "$TestDrive$($DS)SourceDir$($DS)ChildDir-2")
- $destinationPath = "$TestDrive$($DS)archive3.zip"
+ $destinationPath = "$TestDrive$($DS)archive7.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-1/', 'ChildDir-2/',
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt')
}
It "Compresses multiple files, non-empty directories, and an empty directory" {
- $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
+ $sourcePath = @("$TestDrive$($DS)HelloWorld.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
"$TestDrive$($DS)SourceDir$($DS)ChildDir-1", "$TestDrive$($DS)SourceDir$($DS)ChildDir-2", "$TestDrive$($DS)SourceDir$($DS)ChildEmptyDir")
- $destinationPath = "$TestDrive$($DS)archive3.zip"
+ $destinationPath = "$TestDrive$($DS)archive8.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-1/', 'ChildDir-2/',
- 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "EmptyDir/")
+ Test-ZipArchive $destinationPath @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "ChildEmptyDir/")
}
- It "Validate a folder containing files, non-empty folders, and empty folders can be compressed" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive3.zip"
+ It "Compresses a directory containing files, non-empty directories, and an empty directory can be compressed" -Tag td4 {
+ $sourcePath = "$TestDrive$($DS)SourceDir"
+ $destinationPath = "$TestDrive$($DS)archive9.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt')
}
It "Compresses a zero-byte file" {
$sourcePath = "$TestDrive$($DS)EmptyFile"
- $destinationPath = "$TestDrive$($DS)archive3.zip"
+ $destinationPath = "$TestDrive$($DS)archive10.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
$contents = @('EmptyFile')
Test-ZipArchive $destinationPath $contents
}
+
+ It "Compresses a file whose last write time is before 1980" {
+ $sourcePath = "$TestDrive$($DS)OldFile.txt"
+ $destinationPath = "$TestDrive$($DS)archive11.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemProperty -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('OldFile.txt')
+
+ # Get the archive
+ $fileMode = [System.IO.FileMode]::Open
+ $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
+ $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
+ $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
+ $entry = $archive.GetEntry("OldFile.txt")
+ $entry | Should -Not -BeNullOrEmpty
+
+ $entry.LastWriteTime.Year | Should -BeExactly 1980
+ $entry.LastWriteTime.Month| Should -BeExactly 1
+ $entry.LastWriteTime.Day | Should -BeExactly 1
+ $entry.LastWriteTime.Hour | Should -BeExactly 0
+ $entry.LastWriteTime.Minute | Should -BeExactly 0
+ $entry.LastWriteTime.Second | Should -BeExactly 0
+ $entry.LastWriteTime.Millisecond | Should -BeExactly 0
+
+
+ $archive.Dispose()
+ $archiveStream.Dispose()
+ }
}
Context "Update tests" -Skip {
@@ -589,12 +631,22 @@ BeforeDiscovery {
Context "Special and Wildcard Characters Tests" {
BeforeAll {
+<<<<<<< HEAD
New-Item TestDrive:/SourceDir -Type Directory | Out-Null
$content = "Some Data"
$content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
New-Item -LiteralPath "$TestDrive$($DS)Source[]Dir" -Type Directory | Out-Null
$content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)file1[].txt
+=======
+ New-Item $TestDrive$($DS)SourceDir -Type Directory | Out-Null
+
+ New-Item -Path "$TestDrive$($DS)Source`[`]Dir" -Type Directory | Out-Null
+
+ $content = "Some Data"
+ $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)Sample-1.txt
+ $content | Out-File -LiteralPath $TestDrive$($DS)file1[].txt
+>>>>>>> 8b3dcd5 (worked on Expand-Archive, added IEntry class, added support for ShouldProcess in Expand-Archive)
}
It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
@@ -610,14 +662,20 @@ BeforeDiscovery {
$destinationPath = "TestDrive:/archive[2.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+<<<<<<< HEAD
$destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
Remove-Item -LiteralPath $destinationPath
+=======
+ Test-Path -LiteralPath $destinationPath | Should -Be $true
+ Test-ZipArchive $destinationPath @("SourceDir/", "SourceDir/Sample-1.txt")
+ Remove-Item -LiteralPath $destinationPath -Force
+>>>>>>> 8b3dcd5 (worked on Expand-Archive, added IEntry class, added support for ShouldProcess in Expand-Archive)
}
It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
$sourcePath = "$TestDrive$($DS)Source[]Dir"
"Some Random Content" | Out-File -LiteralPath "$sourcePath$($DS)Sample[]File.txt"
- $destinationPath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)archive3.zip"
try
{
Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
@@ -630,8 +688,8 @@ BeforeDiscovery {
}
It "Accepts LiteralPath parameter for a file with wildcards in the filename" {
- $sourcePath = "$TestDrive$($DS)SourceDir($DS)file1[].txt"
- $destinationPath = "$TestDrive$($DS)archive1.zip"
+ $sourcePath = "$TestDrive$($DS)file1[].txt"
+ $destinationPath = "$TestDrive$($DS)archive4.zip"
try
{
Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
diff --git a/src/ArchiveCommandBase.cs b/src/ArchiveCommandBase.cs
new file mode 100644
index 0000000..4dab683
--- /dev/null
+++ b/src/ArchiveCommandBase.cs
@@ -0,0 +1,52 @@
+using Microsoft.PowerShell.Archive.Localized;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.Archive
+{
+ ///
+ /// This class is meant to be a base class for all cmdlets in the archive module
+ ///
+ public class ArchiveCommandBase : PSCmdlet
+ {
+ protected ArchiveFormat DetermineArchiveFormat(string destinationPath, ArchiveFormat? archiveFormat)
+ {
+ // Check if cmdlet is able to determine the format of the archive based on the extension of DestinationPath
+ bool ableToDetermineArchiveFormat = ArchiveFactory.TryGetArchiveFormatForPath(path: destinationPath, archiveFormat: out var archiveFormatBasedOnExt);
+ // If the user did not specify which archive format to use, try to determine it automatically
+ if (archiveFormat is null)
+ {
+ if (ableToDetermineArchiveFormat)
+ {
+ archiveFormat = archiveFormatBasedOnExt;
+ }
+ else
+ {
+ // If the archive format could not be determined, use zip by default and emit a warning
+ var warningMsg = String.Format(Messages.ArchiveFormatCouldNotBeDeterminedWarning, destinationPath);
+ WriteWarning(warningMsg);
+ archiveFormat = ArchiveFormat.zip;
+ }
+ // Write a verbose message saying that Format is not specified and a format was determined automatically
+ string verboseMessage = String.Format(Messages.ArchiveFormatDeterminedVerboseMessage, archiveFormat);
+ WriteVerbose(verboseMessage);
+ }
+ // If the user did specify which archive format to use, emit a warning if DestinationPath does not match the chosen archive format
+ else
+ {
+ if (archiveFormat is null || archiveFormat.Value != archiveFormat.Value)
+ {
+ var warningMsg = String.Format(Messages.ArchiveExtensionDoesNotMatchArchiveFormatWarning, destinationPath);
+ WriteWarning(warningMsg);
+ }
+ }
+
+ // archiveFormat is never null at this point
+ return archiveFormat.Value;
+ }
+ }
+}
diff --git a/src/ErrorMessages.cs b/src/ErrorMessages.cs
index 0b68a9b..805b74d 100644
--- a/src/ErrorMessages.cs
+++ b/src/ErrorMessages.cs
@@ -34,7 +34,7 @@ internal static string GetErrorMessage(ErrorCode errorCode)
ErrorCode.DestinationExistsAsDirectory => Messages.DestinationExistsAsDirectoryMessage,
ErrorCode.ArchiveReadOnly => Messages.ArchiveIsReadOnlyMessage,
ErrorCode.ArchiveDoesNotExist => Messages.ArchiveDoesNotExistMessage,
- ErrorCode.ArchiveIsNonEmptyDirectory => Messages.ArchiveIsNonEmptyDirectory,
+ ErrorCode.DestinationIsNonEmptyDirectory => Messages.DestinationIsNonEmptyDirectory,
ErrorCode.SamePathAndDestinationPath => Messages.SamePathAndDestinationPathMessage,
ErrorCode.SameLiteralPathAndDestinationPath => Messages.SameLiteralPathAndDestinationPathMessage,
ErrorCode.InsufficientPermissionsToAccessPath => Messages.InsufficientPermssionsToAccessPathMessage,
@@ -58,7 +58,7 @@ internal enum ErrorCode
// Used when DestinationPath is an existing directory
DestinationExistsAsDirectory,
// Used when DestinationPath is a non-empty directory and Action Overwrite is specified
- ArchiveIsNonEmptyDirectory,
+ DestinationIsNonEmptyDirectory,
// Used when Compress-Archive cmdlet is in Update mode but the archive is read-only
ArchiveReadOnly,
// Used when DestinationPath does not exist and the Compress-Archive cmdlet is in Update mode
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
new file mode 100644
index 0000000..21a6f0b
--- /dev/null
+++ b/src/ExpandArchiveCommand.cs
@@ -0,0 +1,251 @@
+using Microsoft.PowerShell.Archive.Localized;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.Archive
+{
+ [Cmdlet("Expand", "Archive", SupportsShouldProcess = true)]
+ [OutputType(typeof(System.IO.FileSystemInfo))]
+ public class ExpandArchiveCommand: ArchiveCommandBase
+ {
+ [Parameter(Position=0, Mandatory = true, ParameterSetName = "Path", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ public string Path { get; set; } = String.Empty;
+
+ [Parameter(Mandatory = true, ParameterSetName = "LiteralPath", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ public string LiteralPath { get; set; } = String.Empty;
+
+ [Parameter(Position = 2, Mandatory = true)]
+ public string DestinationPath { get; set; } = String.Empty;
+
+ [Parameter]
+ public ExpandArchiveWriteMode WriteMode { get; set; } = ExpandArchiveWriteMode.Expand;
+
+ [Parameter()]
+ public ArchiveFormat? Format { get; set; } = null;
+
+ [Parameter]
+ public SwitchParameter PassThru { get; set; }
+
+ #region PrivateMembers
+
+ private PathHelper _pathHelper;
+
+ private System.IO.FileSystemInfo? _destinationPathInfo;
+
+ private bool _didCreateOutput;
+
+ #endregion
+
+ public ExpandArchiveCommand()
+ {
+ _didCreateOutput = false;
+ _pathHelper = new PathHelper(cmdlet: this);
+ _destinationPathInfo = null;
+ }
+
+ protected override void BeginProcessing()
+ {
+ // Resolve DestinationPath
+ _destinationPathInfo = _pathHelper.ResolveToSingleFullyQualifiedPath(path: DestinationPath, hasWildcards: false);
+ DestinationPath = _destinationPathInfo.FullName;
+
+ ValidateDestinationPath();
+ }
+
+ protected override void ProcessRecord()
+ {
+
+ }
+
+ protected override void EndProcessing()
+ {
+ // Resolve Path or LiteralPath
+ bool checkForWildcards = ParameterSetName.StartsWith("Path");
+ string path = ParameterSetName.StartsWith("Path") ? Path : LiteralPath;
+ System.IO.FileSystemInfo sourcePath = _pathHelper.ResolveToSingleFullyQualifiedPath(path: path, hasWildcards: checkForWildcards);
+
+ ValidateSourcePath(sourcePath);
+
+ // Determine archive format based on sourcePath
+ Format = DetermineArchiveFormat(destinationPath: sourcePath.FullName, archiveFormat: Format);
+
+ // Get an archive from source path -- this is where we will switch between different types of archives
+ using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.zip, archivePath: sourcePath.FullName, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
+ try
+ {
+ // If the destination path is a file that needs to be overwriten, delete it
+ if (_destinationPathInfo.Exists && !_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory) && WriteMode == ExpandArchiveWriteMode.Overwrite)
+ {
+ if (ShouldProcess(target: _destinationPathInfo.FullName, action: "Overwrite"))
+ {
+ _destinationPathInfo.Delete();
+ System.IO.Directory.CreateDirectory(_destinationPathInfo.FullName);
+ _destinationPathInfo = new System.IO.DirectoryInfo(_destinationPathInfo.FullName);
+ }
+ }
+
+ // If the destination path does not exist, create it
+ if (!_destinationPathInfo.Exists && ShouldProcess(target: _destinationPathInfo.FullName, action: "Create"))
+ {
+ System.IO.Directory.CreateDirectory(_destinationPathInfo.FullName);
+ _destinationPathInfo = new System.IO.DirectoryInfo(_destinationPathInfo.FullName);
+ }
+
+ // Get the next entry in the archive
+ var nextEntry = archive.GetNextEntry();
+ while (nextEntry != null)
+ {
+ // TODO: Refactor this part
+
+ // The location of the entry post-expanding of the archive
+ string postExpandPath = GetPostExpansionPath(entryName: nextEntry.Name, destinationPath: _destinationPathInfo.FullName);
+
+ // If the entry name is invalid, write a non-terminating error
+ if (IsPathInvalid(postExpandPath))
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.InvalidPath, postExpandPath);
+ WriteError(errorRecord);
+ continue;
+ }
+
+ System.IO.FileSystemInfo postExpandPathInfo = new System.IO.FileInfo(postExpandPath);
+
+ if (!postExpandPathInfo.Exists && System.IO.Directory.Exists(postExpandPath))
+ {
+ var directoryInfo = new System.IO.DirectoryInfo(postExpandPath);
+ // If postExpandPath is an existing directory containing files and/or directories, then write an error
+ if (directoryInfo.GetFileSystemInfos().Length > 0)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DestinationIsNonEmptyDirectory, postExpandPath);
+ WriteError(errorRecord);
+ continue;
+ }
+ postExpandPathInfo = directoryInfo;
+ }
+
+ // Throw an error if the cmdlet is not in Overwrite mode but the postExpandPath exists
+ if (postExpandPathInfo.Exists && WriteMode != ExpandArchiveWriteMode.Overwrite)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DestinationExists, postExpandPath);
+ WriteError(errorRecord);
+ continue;
+ }
+
+ if (postExpandPathInfo.Exists && ShouldProcess(target: _destinationPathInfo.FullName, action: "Expand and Overwrite"))
+ {
+ postExpandPathInfo.Delete();
+ nextEntry.ExpandTo(_destinationPathInfo.FullName);
+ } else if (ShouldProcess(target: _destinationPathInfo.FullName, action: "Expand"))
+ {
+ nextEntry.ExpandTo(_destinationPathInfo.FullName);
+ }
+
+
+ nextEntry = archive.GetNextEntry();
+ }
+
+
+ } catch
+ {
+
+ }
+ }
+
+ protected override void StopProcessing()
+ {
+ // Do clean up if the user abruptly stops execution
+ }
+
+ #region PrivateMethods
+
+ private void ValidateDestinationPath()
+ {
+ ErrorCode? errorCode = null;
+
+ // In this case, DestinationPath does not exist
+ if (!_destinationPathInfo.Exists)
+ {
+ // Do nothing
+ }
+ // Check if DestinationPath is an existing directory
+ else if (_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory))
+ {
+ // Throw an error if the DestinationPath is the current working directory and the cmdlet is in Overwrite mode
+ if (WriteMode == ExpandArchiveWriteMode.Overwrite && _destinationPathInfo.FullName == SessionState.Path.CurrentFileSystemLocation.ProviderPath)
+ {
+ errorCode = ErrorCode.CannotOverwriteWorkingDirectory;
+ }
+ }
+ // If DestinationPath is an existing file
+ else
+ {
+ // Throw an error if DestinationPath exists and the cmdlet is not in Overwrite mode
+ if (WriteMode == ExpandArchiveWriteMode.Expand)
+ {
+ errorCode = ErrorCode.DestinationExists;
+ }
+ }
+
+ if (errorCode != null)
+ {
+ // Throw an error -- since we are validating DestinationPath, the problem is with DestinationPath
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode.Value, errorItem: _destinationPathInfo.FullName);
+ ThrowTerminatingError(errorRecord);
+ }
+ }
+
+ private void ValidateSourcePath(System.IO.FileSystemInfo sourcePath)
+ {
+ // Throw a terminating error if sourcePath does not exist
+ if (!sourcePath.Exists)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.PathNotFound, errorItem: sourcePath.FullName);
+ ThrowTerminatingError(errorRecord);
+ }
+
+ // Throw a terminating error if sourcePath is a directory
+ if (sourcePath.Attributes.HasFlag(FileAttributes.Directory))
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.DestinationExistsAsDirectory, errorItem: sourcePath.FullName);
+ ThrowTerminatingError(errorRecord);
+ }
+
+ // Ensure sourcePath is not the same as the destination path when the cmdlet is in overwrite mode
+ // When the cmdlet is not in overwrite mode, other errors will be thrown when validating DestinationPath before it even gets to this line
+ if (PathHelper.ArePathsSame(sourcePath, _destinationPathInfo) && WriteMode == ExpandArchiveWriteMode.Overwrite)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.SamePathAndDestinationPath, errorItem: sourcePath.FullName);
+ ThrowTerminatingError(errorRecord);
+ }
+ }
+
+ private string GetPostExpansionPath(string entryName, string destinationPath)
+ {
+ // Normalize entry name - on Windows, replace forwardslash with backslash
+ string normalizedEntryName = entryName.Replace(System.IO.Path.AltDirectorySeparatorChar, System.IO.Path.DirectorySeparatorChar);
+ return System.IO.Path.Combine(destinationPath, normalizedEntryName);
+ }
+
+ private bool IsPathInvalid(string path)
+ {
+ foreach (var invalidCharacter in System.IO.Path.GetInvalidPathChars())
+ {
+ if (path.Contains(invalidCharacter))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/IArchive.cs b/src/IArchive.cs
index a785a5f..52d9aef 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -24,6 +24,8 @@ internal interface IArchive: IDisposable
// Throws an exception if the archive is in create mode.
internal string[] GetEntries();
+ internal IEntry? GetNextEntry();
+
// Expands an archive to a destination folder.
// Throws an exception if the archive is not in read mode.
internal void Expand(string destinationPath);
diff --git a/src/IEntry.cs b/src/IEntry.cs
new file mode 100644
index 0000000..c3dfde8
--- /dev/null
+++ b/src/IEntry.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.PowerShell.Archive
+{
+ internal interface IEntry
+ {
+ public string Name { get; }
+
+ public void ExpandTo(string destinationPath);
+ }
+}
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index 7f9ebfc..b2fe38f 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -135,9 +135,6 @@
The -Format was not specified, so the archive format was determined to be {0} based on its extension.
-
- The archive {0} cannot be overwritten because it is a non-empty directory.
-
The archive at {0} is read-only.
@@ -152,6 +149,9 @@
The destination path {0} already exists.
+
+ The destination {0} cannot be overwritten because it is a non-empty directory.
+
The path(s) {0} have been specified more than once.
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index da25815..5ae2e4d 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -43,6 +43,11 @@ string[] IArchive.GetEntries()
throw new NotImplementedException();
}
+ IEntry? IArchive.GetNextEntry()
+ {
+ return null;
+ }
+
void IArchive.Expand(string destinationPath)
{
throw new NotImplementedException();
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index 4c74147..8c58119 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -24,6 +24,8 @@ internal class ZipArchive : IArchive
private const char ZipArchiveDirectoryPathTerminator = '/';
+ private int _entryIndex;
+
ArchiveMode IArchive.Mode => _mode;
string IArchive.Path => _archivePath;
@@ -36,6 +38,7 @@ public ZipArchive(string archivePath, ArchiveMode mode, System.IO.FileStream arc
_archiveStream = archiveStream;
_zipArchive = new System.IO.Compression.ZipArchive(stream: archiveStream, mode: ConvertToZipArchiveMode(_mode), leaveOpen: true);
_compressionLevel = compressionLevel;
+ _entryIndex = -1;
}
// If a file is added to the archive when it already contains a folder with the same name,
@@ -89,6 +92,26 @@ string[] IArchive.GetEntries()
throw new NotImplementedException();
}
+ IEntry? IArchive.GetNextEntry()
+ {
+ if (_entryIndex < 0 || _entryIndex >= _zipArchive.Entries.Count)
+ {
+ _entryIndex = 0;
+ }
+
+ // If there are no entries, return null
+ if (_zipArchive.Entries.Count == 0)
+ {
+ return null;
+ }
+
+ // Create an ZipArchive.ZipArchiveEntry object
+ var nextEntry = _zipArchive.Entries[_entryIndex];
+ _entryIndex++;
+
+ return new ZipArchiveEntry(nextEntry);
+ }
+
void IArchive.Expand(string destinationPath)
{
throw new NotImplementedException();
@@ -125,5 +148,24 @@ public void Dispose()
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
+
+ internal class ZipArchiveEntry : IEntry
+ {
+ // Underlying object is System.IO.Compression.ZipArchiveEntry
+
+ private System.IO.Compression.ZipArchiveEntry _entry;
+
+ string IEntry.Name => _entry.FullName;
+
+ void IEntry.ExpandTo(string destinationPath)
+ {
+ _entry.ExtractToFile(destinationPath);
+ }
+
+ internal ZipArchiveEntry(System.IO.Compression.ZipArchiveEntry entry)
+ {
+ _entry = entry;
+ }
+ }
}
}
From 9839283fa7ad5ac02270ba996a8b3439dafcb190 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Thu, 28 Jul 2022 12:07:42 -0700
Subject: [PATCH 04/34] added Expand-Archive.Tests.ps1 and worked on parameter
set validation tests
---
Tests/Expand-Archive.Tests.ps1 | 348 ++++++++++++++++++++++++++
src/ExpandArchiveCommand.cs | 11 +-
src/Microsoft.PowerShell.Archive.psd1 | 3 +-
3 files changed, 359 insertions(+), 3 deletions(-)
create mode 100644 Tests/Expand-Archive.Tests.ps1
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
new file mode 100644
index 0000000..2a5be16
--- /dev/null
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -0,0 +1,348 @@
+# Tests for Expand-Archive
+
+Describe("Expand-Archive Tests") {
+ BeforeAll {
+ function Add-CompressionAssemblies {
+ Add-Type -AssemblyName System.IO.Compression
+ if ($psedition -eq "Core")
+ {
+ Add-Type -AssemblyName System.IO.Compression.ZipFile
+ }
+ else
+ {
+ Add-Type -AssemblyName System.IO.Compression.FileSystem
+ }
+ }
+ $CmdletClassName = "Microsoft.PowerShell.Archive.ExpandArchiveCommand"
+ $DS = [System.IO.Path]::DirectorySeparatorChar
+ Add-CompressionAssemblies
+
+ # Progress perference
+ $originalProgressPref = $ProgressPreference
+ $ProgressPreference = "SilentlyContinue"
+ }
+
+ AfterAll {
+ $global:ProgressPreference = $originalProgressPref
+ }
+
+ Context "Parameter set validation tests" {
+ BeforeAll {
+ function ExpandArchivePathParameterSetValidator {
+ param
+ (
+ [string] $path,
+ [string] $destinationPath
+ )
+
+ try
+ {
+ Expand-Archive -Path $path -DestinationPath $destinationPath
+ throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to Path parameterset."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,$CmdletClassName"
+ }
+ }
+
+ function ExpandArchiveLiteralPathParameterSetValidator {
+ param
+ (
+ [string] $literalPath,
+ [string] $destinationPath
+ )
+
+ try
+ {
+ Expand-Archive -LiteralPath $literalPath -DestinationPath $destinationPath
+ throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,$CmdletClassName"
+ }
+ }
+
+ # Set up files for tests
+ New-Item $TestDrive$($DS)SourceDir -Type Directory | Out-Null
+ $content = "Some Data"
+ $content | Out-File -FilePath $TestDrive$($DS)Sample-1.txt
+
+ # Create archives called archive1.zip and archive2.zip
+ Compress-Archive -Path $TestDrive$($DS)Sample-1.txt -DestinationPath $TestDrive$($DS)archive1.zip
+ Compress-Archive -Path $TestDrive$($DS)Sample-1.txt -DestinationPath $TestDrive$($DS)archive2.zip
+ }
+
+
+ It "Validate errors with NULL & EMPTY values for Path, LiteralPath, and DestinationPath" {
+ $sourcePath = "$TestDrive$($DS)SourceDir"
+ $destinationPath = "$TestDrive$($DS)SampleSingleFile.zip"
+
+ ExpandArchivePathParameterSetValidator $null $destinationPath
+ ExpandArchivePathParameterSetValidator $sourcePath $null
+ ExpandArchivePathParameterSetValidator $null $null
+
+ ExpandArchivePathParameterSetValidator "" $destinationPath
+ ExpandArchivePathParameterSetValidator $sourcePath ""
+ ExpandArchivePathParameterSetValidator "" ""
+
+ ExpandArchiveLiteralPathParameterSetValidator $null $destinationPath
+ ExpandArchiveLiteralPathParameterSetValidator $sourcePath $null
+ ExpandArchiveLiteralPathParameterSetValidator $null $null
+
+ ExpandArchiveLiteralPathParameterSetValidator "" $destinationPath
+ ExpandArchiveLiteralPathParameterSetValidator $sourcePath ""
+ ExpandArchiveLiteralPathParameterSetValidator "" ""
+ }
+
+ It "Throws when invalid path non-existing path is supplied for Path or LiteralPath parameters" {
+ $path = "$TestDrive$($DS)non-existant.zip"
+ $destinationPath = "$TestDrive($DS)DestinationFolder"
+ try
+ {
+ Expand-Archive -Path $path -DestinationPath $destinationPath
+ throw "Failed to validate that an invalid Path $invalidPath was supplied as input to Expand-Archive cmdlet."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "PathNotFound,$CmdletClassName"
+ }
+
+ try
+ {
+ Expand-Archive -LiteralPath $path -DestinationPath $destinationPath
+ throw "Failed to validate that an invalid LiteralPath $invalidPath was supplied as input to Expand-Archive cmdlet."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "PathNotFound,$CmdletClassName"
+ }
+ }
+
+ It "Throws when invalid path non-filesystem path is supplied for Path or LiteralPath parameters" {
+ $path = "Variable:DS"
+ $destinationPath = "$TestDrive($DS)DestinationFolder"
+ try
+ {
+ Expand-Archive -Path $path -DestinationPath $destinationPath
+ throw "Failed to validate that an invalid Path $invalidPath was supplied as input to Expand-Archive cmdlet."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "InvalidPath,$CmdletClassName"
+ }
+
+ try
+ {
+ Expand-Archive -LiteralPath $path -DestinationPath $destinationPath
+ throw "Failed to validate that an invalid LiteralPath $invalidPath was supplied as input to Expand-Archive cmdlet."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "InvalidPath,$CmdletClassName"
+ }
+ }
+
+ It "Throws an error when multiple paths are supplied as input to Path parameter" {
+ $sourcePath = @(
+ "$TestDrive$($DS)SourceDir$($DS)archive1.zip",
+ "$TestDrive$($DS)SourceDir$($DS)archive2.zip")
+ $destinationPath = "$TestDrive$($DS)DestinationFolder"
+
+ try
+ {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect that duplicate Path $sourcePath is supplied as input to Path parameter."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgument,$CmdletClassName"
+ }
+ }
+
+ It "Throws an error when multiple paths are supplied as input to LiteralPath parameter" {
+ $sourcePath = @(
+ "$TestDrive$($DS)SourceDir$($DS)archive1.zip",
+ "$TestDrive$($DS)SourceDir$($DS)archive2.zip")
+ $destinationPath = "$TestDrive$($DS)DestinationFolder"
+
+ try
+ {
+ Expand-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect that duplicate Path $sourcePath is supplied as input to LiteralPath parameter."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgument,$CmdletClassName"
+ }
+ }
+
+ ## From 504
+ It "Validate that Source Path can be at SystemDrive location" -Skip {
+ $sourcePath = "$env:SystemDrive$($DS)SourceDir"
+ $destinationPath = "$TestDrive$($DS)SampleFromSystemDrive.zip"
+ New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux
+ "Some Data" | Out-File -FilePath $sourcePath$($DS)SampleSourceFileForArchive.txt
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Remove-Item "$sourcePath" -Force -Recurse -ErrorAction SilentlyContinue
+ }
+ }
+
+ It "Throws an error when Path and DestinationPath are the same and -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = $sourcePath
+
+ try {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+ throw "Failed to detect an error when Path and DestinationPath are the same and -Overwrite is specified"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,$CmdletClassName"
+ }
+ }
+
+ It "Throws an error when LiteralPath and DestinationPath are the same and WriteMode -Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = $sourcePath
+
+ try {
+ Expand-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+ throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Overwrite is specified"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,$CmdletClassName"
+ }
+ }
+ }
+
+ Context "DestinationPath and Overwrite Tests" {
+ # error when destination path is a file and overwrite is not specified
+ # error when output has same name as existant file and overwrite is not specified
+
+ # no error when destination path is existing folder
+ # no error when output is folder
+
+ # output is directory w/ at least 1 item
+ # output has same name as current working directory
+
+ # overwrite file works
+ # overwrite output file works done
+ # overwrite output file w/directory
+ # overwrite non-existant path works
+
+ # last write times
+
+ BeforeAll {
+ "Hello, World!" | Out-File -FilePath "$TestDrive$($DS)file1.txt"
+ Compress-Archive -Path "$TestDrive$($DS)file1.txt" -DestinationPath "$TestDrive$($DS)archive1.zip"
+
+ New-Item -Path "$TestDrive$($DS)directory1" -ItemType Directory
+
+ # Create archive2.zip containing directory1
+ Compress-Archive -Path "$TestDrive$($DS)directory1" -DestinationPath "$TestDrive$($DS)archive2.zip"
+
+ New-Item -Path "$TestDrive$($DS)ParentDir" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ParentDir/file1.txt" -ItemType Directory
+
+ # Create a dir that is a container for items to be overwritten
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2" -ItemType File
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" -ItemType File
+ }
+
+ It "Throws an error when DestinationPath is an existing file" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)file1.txt"
+
+ try {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExists,$CmdletClassName"
+ }
+ }
+
+ It "Does not throw an error when DestinationPath is an existing directory" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)directory1"
+
+ try {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
+ } catch {
+ throw "An error was thrown but an error was not expected"
+ }
+ }
+
+ It "Does not throw an error when a directory in the archive has the same destination path as an existing directory" {
+ $sourcePath = "$TestDrive$($DS)archive2.zip"
+ $destinationPath = "$TestDrive"
+
+ try {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
+ } catch {
+ throw "An error was thrown but an error was not expected"
+ }
+ }
+
+ It "Writes a non-terminating error when a file in the archive has a destination path that already exists" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorVariable error
+ $error.Count | Should -Be 1
+ $error[0].FullyQualifiedErrorId | Should -Be "DestinationExists,$CmdletClassName"
+ }
+
+ It "Writes a non-terminating error when a file in the archive has a destination path that is an existing directory containing at least 1 item and -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 1
+ $error[0].FullyQualifiedErrorId | Should -Be "DestinationExists,$CmdletClassName"
+ }
+
+ It "Writes a non-terminating error when a file in the archive has a destination path that is the working directory and -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)ParentDir/file1.txt"
+
+ Push-Location $destinationPath
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 1
+ $error[0].FullyQualifiedErrorId | Should -Be "CannotOverwriteWorkingDirectory,$CmdletClassName"
+
+ Pop-Location
+ }
+
+ It "Overwrites a file when it is DestinationPath and -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/file2"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 0
+
+ # Ensure the file in archive1.zip was expanded
+ Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2/file1.txt"
+ }
+
+ It "Overwrites a file whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 0
+
+ # Ensure the file in archive1.zip was expanded
+ Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt"
+
+ # Ensure the contents of file1.txt is "Hello, World!"
+ Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 21a6f0b..f354746 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -23,6 +23,7 @@ public class ExpandArchiveCommand: ArchiveCommandBase
public string LiteralPath { get; set; } = String.Empty;
[Parameter(Position = 2, Mandatory = true)]
+ [ValidateNotNullOrEmpty]
public string DestinationPath { get; set; } = String.Empty;
[Parameter]
@@ -222,7 +223,15 @@ private void ValidateSourcePath(System.IO.FileSystemInfo sourcePath)
// When the cmdlet is not in overwrite mode, other errors will be thrown when validating DestinationPath before it even gets to this line
if (PathHelper.ArePathsSame(sourcePath, _destinationPathInfo) && WriteMode == ExpandArchiveWriteMode.Overwrite)
{
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.SamePathAndDestinationPath, errorItem: sourcePath.FullName);
+ ErrorCode errorCode;
+ if (ParameterSetName == "Path")
+ {
+ errorCode = ErrorCode.SamePathAndDestinationPath;
+ } else
+ {
+ errorCode = ErrorCode.SameLiteralPathAndDestinationPath;
+ }
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode, errorItem: sourcePath.FullName);
ThrowTerminatingError(errorRecord);
}
}
diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1
index a047d5a..46df1a8 100644
--- a/src/Microsoft.PowerShell.Archive.psd1
+++ b/src/Microsoft.PowerShell.Archive.psd1
@@ -18,5 +18,4 @@ PrivateData = @{
'@
Prerelease = 'preview1'
}
-}
-}
+}
\ No newline at end of file
From 9e6404c6962017648d3a772ec0dd17f5f2ad12bf Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Thu, 28 Jul 2022 16:33:17 -0700
Subject: [PATCH 05/34] added and updated tests for Expand-Archive
---
Tests/Expand-Archive.Tests.ps1 | 57 ++++++++++++++---
src/ExpandArchiveCommand.cs | 112 +++++++++++++++++++--------------
src/IEntry.cs | 2 +
src/ZipArchive.cs | 8 ++-
4 files changed, 120 insertions(+), 59 deletions(-)
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index 2a5be16..e684ccc 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -232,7 +232,9 @@ Describe("Expand-Archive Tests") {
# overwrite file works
# overwrite output file works done
+ # overwrite file w/file done
# overwrite output file w/directory
+ # overwrite directory w/file
# overwrite non-existant path works
# last write times
@@ -254,6 +256,15 @@ Describe("Expand-Archive Tests") {
New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2" -ItemType File
New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1" -ItemType Directory
New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" -ItemType File
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4/file1.txt" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4/file1.txt/somefile" -ItemType File
+
+ # Create directory to override
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3" -ItemType Directory
+ New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3/directory1" -ItemType File
}
It "Throws an error when DestinationPath is an existing file" {
@@ -300,11 +311,11 @@ Describe("Expand-Archive Tests") {
It "Writes a non-terminating error when a file in the archive has a destination path that is an existing directory containing at least 1 item and -WriteMode Overwrite is specified" {
$sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive"
+ $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 1
- $error[0].FullyQualifiedErrorId | Should -Be "DestinationExists,$CmdletClassName"
+ $error[0].FullyQualifiedErrorId | Should -Be "DestinationIsNonEmptyDirectory,$CmdletClassName"
}
It "Writes a non-terminating error when a file in the archive has a destination path that is the working directory and -WriteMode Overwrite is specified" {
@@ -313,11 +324,13 @@ Describe("Expand-Archive Tests") {
Push-Location $destinationPath
- Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
- $error.Count | Should -Be 1
- $error[0].FullyQualifiedErrorId | Should -Be "CannotOverwriteWorkingDirectory,$CmdletClassName"
-
- Pop-Location
+ try {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 1
+ $error[0].FullyQualifiedErrorId | Should -Be "CannotOverwriteWorkingDirectory,$CmdletClassName"
+ } finally {
+ Pop-Location
+ }
}
It "Overwrites a file when it is DestinationPath and -WriteMode Overwrite is specified" {
@@ -328,10 +341,10 @@ Describe("Expand-Archive Tests") {
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2/file1.txt"
+ Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2/file1.txt" -PathType Leaf
}
- It "Overwrites a file whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" {
+ It "Overwrites a file whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" -Tag td {
$sourcePath = "$TestDrive$($DS)archive1.zip"
$destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1"
@@ -339,10 +352,34 @@ Describe("Expand-Archive Tests") {
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt"
+ Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" -PathType Leaf
# Ensure the contents of file1.txt is "Hello, World!"
Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
}
+
+ It "Overwrites a directory whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 0
+
+ # Ensure the file in archive1.zip was expanded
+ Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" -PathType Leaf
+
+ # Ensure the contents of file1.txt is "Hello, World!"
+ Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
+ }
+
+ It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" {
+ $sourcePath = "$TestDrive$($DS)archive2.zip"
+ $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3"
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
+ $error.Count | Should -Be 0
+
+ # Ensure the file in archive1.zip was expanded
+ Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3/directory1" -PathType Container
+ }
}
}
\ No newline at end of file
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index f354746..0f1f64a 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -83,6 +83,7 @@ protected override void EndProcessing()
try
{
// If the destination path is a file that needs to be overwriten, delete it
+
if (_destinationPathInfo.Exists && !_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory) && WriteMode == ExpandArchiveWriteMode.Overwrite)
{
if (ShouldProcess(target: _destinationPathInfo.FullName, action: "Overwrite"))
@@ -100,56 +101,11 @@ protected override void EndProcessing()
_destinationPathInfo = new System.IO.DirectoryInfo(_destinationPathInfo.FullName);
}
- // Get the next entry in the archive
+ // Get the next entry in the archive and process it
var nextEntry = archive.GetNextEntry();
while (nextEntry != null)
{
- // TODO: Refactor this part
-
- // The location of the entry post-expanding of the archive
- string postExpandPath = GetPostExpansionPath(entryName: nextEntry.Name, destinationPath: _destinationPathInfo.FullName);
-
- // If the entry name is invalid, write a non-terminating error
- if (IsPathInvalid(postExpandPath))
- {
- var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.InvalidPath, postExpandPath);
- WriteError(errorRecord);
- continue;
- }
-
- System.IO.FileSystemInfo postExpandPathInfo = new System.IO.FileInfo(postExpandPath);
-
- if (!postExpandPathInfo.Exists && System.IO.Directory.Exists(postExpandPath))
- {
- var directoryInfo = new System.IO.DirectoryInfo(postExpandPath);
- // If postExpandPath is an existing directory containing files and/or directories, then write an error
- if (directoryInfo.GetFileSystemInfos().Length > 0)
- {
- var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DestinationIsNonEmptyDirectory, postExpandPath);
- WriteError(errorRecord);
- continue;
- }
- postExpandPathInfo = directoryInfo;
- }
-
- // Throw an error if the cmdlet is not in Overwrite mode but the postExpandPath exists
- if (postExpandPathInfo.Exists && WriteMode != ExpandArchiveWriteMode.Overwrite)
- {
- var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DestinationExists, postExpandPath);
- WriteError(errorRecord);
- continue;
- }
-
- if (postExpandPathInfo.Exists && ShouldProcess(target: _destinationPathInfo.FullName, action: "Expand and Overwrite"))
- {
- postExpandPathInfo.Delete();
- nextEntry.ExpandTo(_destinationPathInfo.FullName);
- } else if (ShouldProcess(target: _destinationPathInfo.FullName, action: "Expand"))
- {
- nextEntry.ExpandTo(_destinationPathInfo.FullName);
- }
-
-
+ ProcessArchiveEntry(nextEntry);
nextEntry = archive.GetNextEntry();
}
@@ -167,6 +123,68 @@ protected override void StopProcessing()
#region PrivateMethods
+ private void ProcessArchiveEntry(IEntry entry)
+ {
+ // The location of the entry post-expanding of the archive
+ string postExpandPath = GetPostExpansionPath(entryName: entry.Name, destinationPath: _destinationPathInfo.FullName);
+
+ // If the entry name is invalid, write a non-terminating error and stop processing the entry
+ if (IsPathInvalid(postExpandPath))
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.InvalidPath, postExpandPath);
+ WriteError(errorRecord);
+ return;
+ }
+
+
+ System.IO.FileSystemInfo postExpandPathInfo = new System.IO.FileInfo(postExpandPath);
+
+ // Use this variable to keep track if there is a collision
+ // If the postExpandPath is a file, then no matter if the entry is a file or directory, it is a collision
+ bool hasCollision = postExpandPathInfo.Exists;
+
+ if (System.IO.Directory.Exists(postExpandPath))
+ {
+ var directoryInfo = new System.IO.DirectoryInfo(postExpandPath);
+
+ // If the entry is a directory and postExpandPath is a directory, no collision occurs (because there is no need to overwrite directories)
+ hasCollision = !entry.IsDirectory;
+
+ // If postExpandPath is an existing directory containing files and/or directories, then write an error
+ if (hasCollision && directoryInfo.GetFileSystemInfos().Length > 0)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DestinationIsNonEmptyDirectory, postExpandPath);
+ WriteError(errorRecord);
+ return;
+ }
+ postExpandPathInfo = directoryInfo;
+ }
+
+ // Throw an error if the cmdlet is not in Overwrite mode but the postExpandPath exists
+ if (hasCollision && WriteMode != ExpandArchiveWriteMode.Overwrite)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.DestinationExists, postExpandPath);
+ WriteError(errorRecord);
+ return;
+ }
+
+ string expandAction = hasCollision ? "Overwrite and Expand" : "Expand";
+ if (ShouldProcess(target: postExpandPath, action: expandAction))
+ {
+ if (hasCollision)
+ {
+ postExpandPathInfo.Delete();
+ System.Threading.Thread.Sleep(1000);
+ }
+ // Only expand the entry if there is a need to expand
+ // There is a need to expand unless the entry is a directory and the postExpandPath is also a directory
+ if (!(entry.IsDirectory && postExpandPathInfo.Attributes.HasFlag(FileAttributes.Directory)))
+ {
+ entry.ExpandTo(_destinationPathInfo.FullName);
+ }
+ }
+ }
+
private void ValidateDestinationPath()
{
ErrorCode? errorCode = null;
diff --git a/src/IEntry.cs b/src/IEntry.cs
index c3dfde8..503e52b 100644
--- a/src/IEntry.cs
+++ b/src/IEntry.cs
@@ -10,6 +10,8 @@ internal interface IEntry
{
public string Name { get; }
+ public bool IsDirectory { get; }
+
public void ExpandTo(string destinationPath);
}
}
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index 8c58119..1089be0 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -94,13 +94,13 @@ string[] IArchive.GetEntries()
IEntry? IArchive.GetNextEntry()
{
- if (_entryIndex < 0 || _entryIndex >= _zipArchive.Entries.Count)
+ if (_entryIndex < 0)
{
_entryIndex = 0;
}
// If there are no entries, return null
- if (_zipArchive.Entries.Count == 0)
+ if (_zipArchive.Entries.Count == 0 || _entryIndex >= _zipArchive.Entries.Count)
{
return null;
}
@@ -157,8 +157,12 @@ internal class ZipArchiveEntry : IEntry
string IEntry.Name => _entry.FullName;
+ bool IEntry.IsDirectory => _entry.FullName.EndsWith(System.IO.Path.AltDirectorySeparatorChar);
+
void IEntry.ExpandTo(string destinationPath)
{
+ string postExpandPath = System.IO.Path.Combine(destinationPath, _entry.FullName);
+ System.Diagnostics.Debug.Assert(!System.IO.File.Exists(postExpandPath));
_entry.ExtractToFile(destinationPath);
}
From 6c3b2965ec7ca2a3ed9ab2aa579c942988c48dff Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Fri, 29 Jul 2022 17:31:26 -0700
Subject: [PATCH 06/34] fixed bug where wrong destination path was determined
for an item in the archive, added tests for Expand-Archive
---
Tests/Compress-Archive.Tests.ps1 | 2 +-
Tests/Expand-Archive.Tests.ps1 | 80 ++++++++++++++++++++++++++++++--
src/ArchiveCommandBase.cs | 4 +-
src/ExpandArchiveCommand.cs | 32 ++++++++-----
src/ZipArchive.cs | 12 +++--
5 files changed, 109 insertions(+), 21 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 4acaf64..8271248 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -507,7 +507,7 @@ BeforeDiscovery {
}
catch
{
- $_.FullyQualifiedErrorId | Should -Be "ArchiveIsNonEmptyDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ $_.FullyQualifiedErrorId | Should -Be "DestinationIsNonEmptyDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index e684ccc..2e4c41c 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -240,6 +240,7 @@ Describe("Expand-Archive Tests") {
# last write times
BeforeAll {
+ New-Item -Path "$TestDrive$($DS)file1.txt" -ItemType File
"Hello, World!" | Out-File -FilePath "$TestDrive$($DS)file1.txt"
Compress-Archive -Path "$TestDrive$($DS)file1.txt" -DestinationPath "$TestDrive$($DS)archive1.zip"
@@ -265,6 +266,14 @@ Describe("Expand-Archive Tests") {
# Create directory to override
New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3" -ItemType Directory
New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3/directory1" -ItemType File
+
+ # Set the error action preference so non-terminating errors aren't displayed
+ $ErrorActionPreference = 'SilentlyContinue'
+ }
+
+ AfterAll {
+ # Reset to default value
+ $ErrorActionPreference = 'Continue'
}
It "Throws an error when DestinationPath is an existing file" {
@@ -320,9 +329,9 @@ Describe("Expand-Archive Tests") {
It "Writes a non-terminating error when a file in the archive has a destination path that is the working directory and -WriteMode Overwrite is specified" {
$sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)ParentDir/file1.txt"
+ $destinationPath = "$TestDrive$($DS)ParentDir"
- Push-Location $destinationPath
+ Push-Location "$destinationPath/file1.txt"
try {
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
@@ -369,10 +378,10 @@ Describe("Expand-Archive Tests") {
Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" -PathType Leaf
# Ensure the contents of file1.txt is "Hello, World!"
- Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
+ Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" | Should -Be "Hello, World!"
}
- It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" {
+ It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" -Tag this1 {
$sourcePath = "$TestDrive$($DS)archive2.zip"
$destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
@@ -382,4 +391,67 @@ Describe("Expand-Archive Tests") {
Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3/directory1" -PathType Container
}
}
+
+ Context "Basic functionality tests" {
+ # extract to a directory works
+ # extract to working directory works when DestinationPath is specified
+ # expand archive works when -DestinationPath is not specified (and a single top level item which is a directory)
+ # expand archive works when -DestinationPath is not specified (and there are mutiple top level items)
+
+ BeforeAll {
+ New-Item -Path "$TestDrive/file1.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "$TestDrive/file1.txt"
+ Compress-Archive -Path "$TestDrive/file1.txt" -DestinationPath "$TestDrive/archive1.zip"
+
+ New-Item -Path "$TestDrive/directory2" -ItemType Directory
+
+ New-Item -Path "$TestDrive/DirectoryToArchive" -ItemType Directory
+ Compress-Archive -Path "$TestDrive/DirectoryToArchive" -DestinationPath "$TestDrive/archive2.zip"
+ }
+
+ It "Expands an archive when a non-existant directory is specified as -DestinationPath" {
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/directory1"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+
+ $itemsInDestinationPath = Get-ChildItem $destinationPath -Name
+ $itemsInDestinationPath.Count | Should -Be 1
+ $itemsInDestinationPath[0] | Should -Be "file1.txt"
+ }
+
+ It "Expands an archive to the working directory when it is specified as -DestinationPath" {
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/directory2"
+
+ Push-Location $destinationPath
+
+ Expand-Archive -Path $sourcePath -DestinationPath $PWD
+
+ $itemsInDestinationPath = Get-ChildItem $PWD -Name
+ $itemsInDestinationPath.Count | Should -Be 1
+ $itemsInDestinationPath[0] | Should -Be "file1.txt"
+
+ Pop-Location
+ }
+
+ It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" {
+ $sourcePath = "$TestDrive/archive2.zip"
+ $destinationPath = "$TestDrive/directory2"
+
+ Push-Location $destinationPath
+
+ Expand-Archive -Path $sourcePath
+
+ $itemsInDestinationPath = Get-ChildItem "$TestDrive/directory2" -Name -Recurse
+ $itemsInDestinationPath.Count | Should -Be 1
+ $itemsInDestinationPath[0] | Should -Be "DirectoryToArchive"
+
+ Test-Path -Path "$TestDrive/directory2/DirectoryToArchive" -PathType Container
+
+ Pop-Location
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/src/ArchiveCommandBase.cs b/src/ArchiveCommandBase.cs
index 4dab683..1259d39 100644
--- a/src/ArchiveCommandBase.cs
+++ b/src/ArchiveCommandBase.cs
@@ -16,7 +16,7 @@ public class ArchiveCommandBase : PSCmdlet
protected ArchiveFormat DetermineArchiveFormat(string destinationPath, ArchiveFormat? archiveFormat)
{
// Check if cmdlet is able to determine the format of the archive based on the extension of DestinationPath
- bool ableToDetermineArchiveFormat = ArchiveFactory.TryGetArchiveFormatForPath(path: destinationPath, archiveFormat: out var archiveFormatBasedOnExt);
+ bool ableToDetermineArchiveFormat = ArchiveFactory.TryGetArchiveFormatFromExtension(path: destinationPath, archiveFormat: out var archiveFormatBasedOnExt);
// If the user did not specify which archive format to use, try to determine it automatically
if (archiveFormat is null)
{
@@ -29,7 +29,7 @@ protected ArchiveFormat DetermineArchiveFormat(string destinationPath, ArchiveFo
// If the archive format could not be determined, use zip by default and emit a warning
var warningMsg = String.Format(Messages.ArchiveFormatCouldNotBeDeterminedWarning, destinationPath);
WriteWarning(warningMsg);
- archiveFormat = ArchiveFormat.zip;
+ archiveFormat = ArchiveFormat.Zip;
}
// Write a verbose message saying that Format is not specified and a format was determined automatically
string verboseMessage = String.Format(Messages.ArchiveFormatDeterminedVerboseMessage, archiveFormat);
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 0f1f64a..47c3dee 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -69,8 +69,8 @@ protected override void ProcessRecord()
protected override void EndProcessing()
{
// Resolve Path or LiteralPath
- bool checkForWildcards = ParameterSetName.StartsWith("Path");
- string path = ParameterSetName.StartsWith("Path") ? Path : LiteralPath;
+ bool checkForWildcards = ParameterSetName == "Path";
+ string path = checkForWildcards ? Path : LiteralPath;
System.IO.FileSystemInfo sourcePath = _pathHelper.ResolveToSingleFullyQualifiedPath(path: path, hasWildcards: checkForWildcards);
ValidateSourcePath(sourcePath);
@@ -79,7 +79,7 @@ protected override void EndProcessing()
Format = DetermineArchiveFormat(destinationPath: sourcePath.FullName, archiveFormat: Format);
// Get an archive from source path -- this is where we will switch between different types of archives
- using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.zip, archivePath: sourcePath.FullName, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
+ using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: sourcePath.FullName, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
try
{
// If the destination path is a file that needs to be overwriten, delete it
@@ -110,9 +110,10 @@ protected override void EndProcessing()
}
- } catch
+ } catch (System.UnauthorizedAccessException unauthorizedAccessException)
{
-
+ // TODO: Change this later to write an error
+ throw unauthorizedAccessException;
}
}
@@ -128,6 +129,12 @@ private void ProcessArchiveEntry(IEntry entry)
// The location of the entry post-expanding of the archive
string postExpandPath = GetPostExpansionPath(entryName: entry.Name, destinationPath: _destinationPathInfo.FullName);
+ // If postExpandPath has a terminating `/`, remove it (there is case where overwriting a file may fail because of this)
+ if (postExpandPath.EndsWith(System.IO.Path.DirectorySeparatorChar))
+ {
+ postExpandPath = postExpandPath.Remove(postExpandPath.Length - 1);
+ }
+
// If the entry name is invalid, write a non-terminating error and stop processing the entry
if (IsPathInvalid(postExpandPath))
{
@@ -157,6 +164,13 @@ private void ProcessArchiveEntry(IEntry entry)
WriteError(errorRecord);
return;
}
+ // If postExpandPath is the same as the working directory, then write an error
+ if (hasCollision && postExpandPath == SessionState.Path.CurrentFileSystemLocation.ProviderPath)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.CannotOverwriteWorkingDirectory, postExpandPath);
+ WriteError(errorRecord);
+ return;
+ }
postExpandPathInfo = directoryInfo;
}
@@ -180,7 +194,7 @@ private void ProcessArchiveEntry(IEntry entry)
// There is a need to expand unless the entry is a directory and the postExpandPath is also a directory
if (!(entry.IsDirectory && postExpandPathInfo.Attributes.HasFlag(FileAttributes.Directory)))
{
- entry.ExpandTo(_destinationPathInfo.FullName);
+ entry.ExpandTo(postExpandPath);
}
}
}
@@ -197,11 +211,7 @@ private void ValidateDestinationPath()
// Check if DestinationPath is an existing directory
else if (_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory))
{
- // Throw an error if the DestinationPath is the current working directory and the cmdlet is in Overwrite mode
- if (WriteMode == ExpandArchiveWriteMode.Overwrite && _destinationPathInfo.FullName == SessionState.Path.CurrentFileSystemLocation.ProviderPath)
- {
- errorCode = ErrorCode.CannotOverwriteWorkingDirectory;
- }
+ // Do nothing
}
// If DestinationPath is an existing file
else
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index 1089be0..73d3f51 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -161,9 +161,15 @@ internal class ZipArchiveEntry : IEntry
void IEntry.ExpandTo(string destinationPath)
{
- string postExpandPath = System.IO.Path.Combine(destinationPath, _entry.FullName);
- System.Diagnostics.Debug.Assert(!System.IO.File.Exists(postExpandPath));
- _entry.ExtractToFile(destinationPath);
+ // .NET APIs differentiate a file and directory by a terminating `/`
+ // If the entry name ends with `/`, it is a directory
+ if (_entry.FullName.EndsWith(System.IO.Path.AltDirectorySeparatorChar))
+ {
+ System.IO.Directory.CreateDirectory(destinationPath);
+ } else
+ {
+ _entry.ExtractToFile(destinationPath);
+ }
}
internal ZipArchiveEntry(System.IO.Compression.ZipArchiveEntry entry)
From 128c518ad55a95cc91accbd3f40e02418d07c594 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 2 Aug 2022 14:13:13 -0700
Subject: [PATCH 07/34] added tests for basic functionality for Expand-Archive
---
Tests/Compress-Archive.Tests.ps1 | 34 +++++++++-
Tests/Expand-Archive.Tests.ps1 | 113 ++++++++++++++++++++++++++++---
src/ExpandArchiveCommand.cs | 23 ++++++-
src/IArchive.cs | 6 ++
src/ZipArchive.cs | 29 +++++++-
5 files changed, 194 insertions(+), 11 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 8271248..0ec9a46 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -379,7 +379,7 @@ BeforeDiscovery {
$destinationPath = "$TestDrive$($DS)archive11.zip"
# Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemProperty -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
$dateProperty.Year | Should -BeLessThan 1980
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
@@ -403,6 +403,38 @@ BeforeDiscovery {
$entry.LastWriteTime.Millisecond | Should -BeExactly 0
+ $archive.Dispose()
+ $archiveStream.Dispose()
+ }
+
+ It "Compresses a directory whose last write time is before 1980" {
+ New-Item -Path "$TestDrive/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "$TestDrive/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
+
+ $sourcePath = "$TestDrive$($DS)olddirectory"
+ $destinationPath = "$TestDrive$($DS)archive12.zip"
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ Test-ZipArchive $destinationPath @('olddirectory/')
+
+ # Get the archive
+ $fileMode = [System.IO.FileMode]::Open
+ $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
+ $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
+ $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
+ $entry = $archive.GetEntry("olddirectory/")
+ $entry | Should -Not -BeNullOrEmpty
+
+ $entry.LastWriteTime.Year | Should -BeExactly 1980
+ $entry.LastWriteTime.Month| Should -BeExactly 1
+ $entry.LastWriteTime.Day | Should -BeExactly 1
+ $entry.LastWriteTime.Hour | Should -BeExactly 0
+ $entry.LastWriteTime.Minute | Should -BeExactly 0
+ $entry.LastWriteTime.Second | Should -BeExactly 0
+ $entry.LastWriteTime.Millisecond | Should -BeExactly 0
+
+
$archive.Dispose()
$archiveStream.Dispose()
}
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index 2e4c41c..fecdbb0 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -404,20 +404,26 @@ Describe("Expand-Archive Tests") {
Compress-Archive -Path "$TestDrive/file1.txt" -DestinationPath "$TestDrive/archive1.zip"
New-Item -Path "$TestDrive/directory2" -ItemType Directory
+ New-Item -Path "$TestDrive/directory3" -ItemType Directory
+ New-Item -Path "$TestDrive/directory4" -ItemType Directory
+ New-Item -Path "$TestDrive/directory5" -ItemType Directory
New-Item -Path "$TestDrive/DirectoryToArchive" -ItemType Directory
Compress-Archive -Path "$TestDrive/DirectoryToArchive" -DestinationPath "$TestDrive/archive2.zip"
+
+ # Create an archive containing a file and an empty folder
+ Compress-Archive -Path "$TestDrive/file1.txt","$TestDrive/DirectoryToArchive" -DestinationPath "$TestDrive/archive3.zip"
}
- It "Expands an archive when a non-existant directory is specified as -DestinationPath" {
+ It "Expands an archive when a non-existent directory is specified as -DestinationPath" {
$sourcePath = "$TestDrive/archive1.zip"
$destinationPath = "$TestDrive/directory1"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
- $itemsInDestinationPath = Get-ChildItem $destinationPath -Name
+ $itemsInDestinationPath = Get-ChildItem $destinationPath -Recurse
$itemsInDestinationPath.Count | Should -Be 1
- $itemsInDestinationPath[0] | Should -Be "file1.txt"
+ $itemsInDestinationPath[0].Name | Should -Be "file1.txt"
}
It "Expands an archive to the working directory when it is specified as -DestinationPath" {
@@ -428,29 +434,120 @@ Describe("Expand-Archive Tests") {
Expand-Archive -Path $sourcePath -DestinationPath $PWD
- $itemsInDestinationPath = Get-ChildItem $PWD -Name
+ $itemsInDestinationPath = Get-ChildItem $PWD -Recurse
$itemsInDestinationPath.Count | Should -Be 1
- $itemsInDestinationPath[0] | Should -Be "file1.txt"
+ $itemsInDestinationPath[0].Name | Should -Be "file1.txt"
Pop-Location
}
It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" {
$sourcePath = "$TestDrive/archive2.zip"
- $destinationPath = "$TestDrive/directory2"
+ $destinationPath = "$TestDrive/directory3"
Push-Location $destinationPath
Expand-Archive -Path $sourcePath
- $itemsInDestinationPath = Get-ChildItem "$TestDrive/directory2" -Name -Recurse
+ $itemsInDestinationPath = Get-ChildItem "$TestDrive/directory3" -Name -Recurse
$itemsInDestinationPath.Count | Should -Be 1
$itemsInDestinationPath[0] | Should -Be "DirectoryToArchive"
- Test-Path -Path "$TestDrive/directory2/DirectoryToArchive" -PathType Container
+ Test-Path -Path "$TestDrive/directory3/DirectoryToArchive" -PathType Container
Pop-Location
}
+
+ It "Expands an archive containing multiple top-level items to a directory with that archive's name when -DestinationPath is not specified" {
+ $sourcePath = "$TestDrive/archive3.zip"
+ $destinationPath = "$TestDrive/directory4"
+
+ Push-Location $destinationPath
+
+ Expand-Archive -Path $sourcePath
+
+ $itemsInDestinationPath = Get-ChildItem $destinationPath -Name -Recurse
+ $itemsInDestinationPath.Count | Should -Be 2
+ $itemsInDestinationPath.Contains("DirectoryToArchive") | Should -Be $true
+ $itemsInDestinationPath.Contains("file1.txt") | Should -Be $true
+
+ Pop-Location
+ }
+
+ It "Expands an archive containing multiple files, non-empty directories, and empty directories" {
+
+ # Create an archive containing multiple files, non-empty directories, and empty directories
+ New-Item -Path "$TestDrive/file2.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "$TestDrive/file2.txt"
+ New-Item -Path "$TestDrive/file3.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "$TestDrive/file3.txt"
+
+ New-Item -Path "$TestDrive/emptydirectory1" -ItemType Directory
+ New-Item -Path "$TestDrive/emptydirectory2" -ItemType Directory
+
+ New-Item -Path "$TestDrive/nonemptydirectory1" -ItemType Directory
+ New-Item -Path "$TestDrive/nonemptydirectory2" -ItemType Directory
+
+ New-Item -Path "$TestDrive/nonemptydirectory1/subfile1.txt" -ItemType File
+ New-Item -Path "$TestDrive/nonemptydirectory2/subemptydirectory1" -ItemType Directory
+
+ $archive4Paths = @("$TestDrive/file2.txt", "$TestDrive/file3.txt", "$TestDrive/emptydirectory1", "$TestDrive/emptydirectory2", "$TestDrive/nonemptydirectory1", "$TestDrive/nonemptydirectory2")
+
+ Compress-Archive -Path $archive4Paths -DestinationPath "$TestDrive/archive4.zip"
+
+ $sourcePath = "$TestDrive/archive4.zip"
+ $destinationPath = "$TestDrive/directory5"
+
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+
+ $expandedItems = Get-ChildItem $destinationPath -Recurse -Name
+
+ $itemsInArchive = @("file2.txt", "file3.txt", "emptydirectory1", "emptydirectory2", "nonemptydirectory1", "nonemptydirectory1/subfile1.txt", "nonemptydirectory2/subemptydirectory1")
+
+ foreach ($item in $itemsInArchive) {
+ $item | Should -BeIn $expandedItems
+ }
+ }
+
+ It "Expands an archive containing a file whose LastWriteTime is in the past" {
+ New-Item -Path "$TestDrive/oldfile.txt" -ItemType File
+ Set-ItemProperty -Path "$TestDrive/oldfile.txt" -Name "LastWriteTime" -Value '2003-01-16 14:44'
+ Compress-Archive -Path "$TestDrive/oldfile.txt" -DestinationPath "$TestDrive/archive_oldfile.zip"
+
+ $sourcePath = "$TestDrive/archive_oldfile.zip"
+ $destinationPath = "$TestDrive/destination6"
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+
+ $lastWriteTime = Get-ItemPropertyValue -Path "$TestDrive/oldfile.txt" -Name "LastWriteTime"
+
+ $lastWriteTime.Year | Should -Be 2003
+ $lastWriteTime.Month | Should -Be 1
+ $lastWriteTime.Day | Should -Be 16
+ $lastWriteTime.Hour | Should -Be 14
+ $lastWriteTime.Minute | Should -Be 44
+ $lastWriteTime.Second | Should -Be 0
+ $lastWriteTime.Millisecond | Should -Be 0
+ }
+
+ It "Expands an archive containing a directory whose LastWriteTime is in the past" {
+ New-Item -Path "$TestDrive/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "$TestDrive/olddirectory" -Name "LastWriteTime" -Value '2003-01-16 14:44'
+ Compress-Archive -Path "$TestDrive/olddirectory" -DestinationPath "$TestDrive/archive_olddirectory.zip"
+
+ $sourcePath = "$TestDrive/archive_olddirectory.zip"
+ $destinationPath = "$TestDrive/destination_olddirectory"
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+
+ $lastWriteTime = Get-ItemPropertyValue -Path "$TestDrive/destination_olddirectory/olddirectory" -Name "LastWriteTime"
+
+ $lastWriteTime.Year | Should -Be 2003
+ $lastWriteTime.Month | Should -Be 1
+ $lastWriteTime.Day | Should -Be 16
+ $lastWriteTime.Hour | Should -Be 14
+ $lastWriteTime.Minute | Should -Be 44
+ $lastWriteTime.Second | Should -Be 0
+ $lastWriteTime.Millisecond | Should -Be 0
+ }
}
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 47c3dee..15371db 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -192,7 +192,7 @@ private void ProcessArchiveEntry(IEntry entry)
}
// Only expand the entry if there is a need to expand
// There is a need to expand unless the entry is a directory and the postExpandPath is also a directory
- if (!(entry.IsDirectory && postExpandPathInfo.Attributes.HasFlag(FileAttributes.Directory)))
+ if (!(entry.IsDirectory && postExpandPathInfo.Attributes.HasFlag(FileAttributes.Directory) && postExpandPathInfo.Exists))
{
entry.ExpandTo(postExpandPath);
}
@@ -282,6 +282,27 @@ private bool IsPathInvalid(string path)
}
return false;
}
+
+ // Used to determine what the DestinationPath should be when it is not specified
+ private string DetermineDestinationPath(IArchive archive)
+ {
+ var workingDirectory = SessionState.Path.CurrentFileSystemLocation.ProviderPath;
+ if (archive.HasTopLevelDirectoryOnly())
+ {
+
+ } else
+ {
+ // destination path will be "working directory/archive file name"
+ var filename = System.IO.Path.GetFileName(archive.Path);
+ // If filename does not have an extension, throw a terminating error because the cmdlet
+ // cannot determine what destination path should be
+ if (System.IO.Path.GetExtension(filename) == String.Empty)
+ {
+ var errorRecord =
+ }
+ return System.IO.Path.Combine(workingDirectory, filename);
+ }
+ }
#endregion
}
diff --git a/src/IArchive.cs b/src/IArchive.cs
index 52d9aef..afba831 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -15,6 +15,9 @@ internal interface IArchive: IDisposable
// Get the fully qualified path of the archive
internal string Path { get; }
+ // Number of entries
+ internal int NumberOfEntries { get; }
+
// Add a file or folder to the archive. The entry name of the added item in the
// will be ArchiveEntry.Name.
// Throws an exception if the archive is in read mode.
@@ -29,5 +32,8 @@ internal interface IArchive: IDisposable
// Expands an archive to a destination folder.
// Throws an exception if the archive is not in read mode.
internal void Expand(string destinationPath);
+
+ // Does the archive have only a top-level directory?
+ internal bool HasTopLevelDirectoryOnly();
}
}
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index 73d3f51..9b96595 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -30,6 +30,8 @@ internal class ZipArchive : IArchive
string IArchive.Path => _archivePath;
+ int IArchive.NumberOfEntries => _zipArchive.Entries.Count;
+
public ZipArchive(string archivePath, ArchiveMode mode, System.IO.FileStream archiveStream, CompressionLevel compressionLevel)
{
_disposedValue = false;
@@ -71,7 +73,18 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition)
entryName += ZipArchiveDirectoryPathTerminator;
}
- _zipArchive.CreateEntry(entryName);
+ entryInArchive = _zipArchive.CreateEntry(entryName);
+
+ // Set the last write time
+ if (entryInArchive != null)
+ {
+ var lastWriteTime = addition.FileSystemInfo.LastWriteTime;
+ if (lastWriteTime.Year < 1980 || lastWriteTime.Year > 2107)
+ {
+ lastWriteTime = new DateTime(1980, 1, 1, 0, 0, 0);
+ }
+ entryInArchive.LastWriteTime = lastWriteTime;
+ }
}
}
else
@@ -149,6 +162,18 @@ public void Dispose()
GC.SuppressFinalize(this);
}
+ bool IArchive.HasTopLevelDirectoryOnly()
+ {
+ if (_zipArchive.Entries.Count == 0 || _zipArchive.Entries.Count > 1)
+ {
+ return false;
+ }
+
+ // At this point, we know the archive has one entry only
+ // We can determine if the entry is a directory by checking if the entry name ends with '/'
+ return _zipArchive.Entries[0].FullName.EndsWith(ZipArchiveDirectoryPathTerminator);
+ }
+
internal class ZipArchiveEntry : IEntry
{
// Underlying object is System.IO.Compression.ZipArchiveEntry
@@ -166,6 +191,8 @@ void IEntry.ExpandTo(string destinationPath)
if (_entry.FullName.EndsWith(System.IO.Path.AltDirectorySeparatorChar))
{
System.IO.Directory.CreateDirectory(destinationPath);
+ var lastWriteTime = _entry.LastWriteTime;
+ System.IO.Directory.SetLastWriteTime(destinationPath, lastWriteTime.DateTime);
} else
{
_entry.ExtractToFile(destinationPath);
From fda726353356f9d9fa8e273a156b7640b793d6a3 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 2 Aug 2022 17:52:48 -0700
Subject: [PATCH 08/34] added support for automatically determining
DestinationPath and worked on tests for Expand-Archive
---
Tests/Expand-Archive.Tests.ps1 | 55 ++++++++++-----
src/ErrorMessages.cs | 4 ++
src/ExpandArchiveCommand.cs | 94 +++++++++++++++----------
src/IArchive.cs | 2 +-
src/Localized/Messages.resx | 9 +++
src/Microsoft.PowerShell.Archive.csproj | 8 ---
src/TarArchive.cs | 7 ++
src/ZipArchive.cs | 36 +++++++---
8 files changed, 140 insertions(+), 75 deletions(-)
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index fecdbb0..ccb5b32 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -287,17 +287,6 @@ Describe("Expand-Archive Tests") {
}
}
- It "Does not throw an error when DestinationPath is an existing directory" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)directory1"
-
- try {
- Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
- } catch {
- throw "An error was thrown but an error was not expected"
- }
- }
-
It "Does not throw an error when a directory in the archive has the same destination path as an existing directory" {
$sourcePath = "$TestDrive$($DS)archive2.zip"
$destinationPath = "$TestDrive"
@@ -381,7 +370,7 @@ Describe("Expand-Archive Tests") {
Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" | Should -Be "Hello, World!"
}
- It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" -Tag this1 {
+ It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" {
$sourcePath = "$TestDrive$($DS)archive2.zip"
$destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
@@ -426,6 +415,17 @@ Describe("Expand-Archive Tests") {
$itemsInDestinationPath[0].Name | Should -Be "file1.txt"
}
+ It "Expands an archive when DestinationPath is an existing directory" {
+ $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $destinationPath = "$TestDrive$($DS)directory1"
+
+ try {
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
+ } catch {
+ throw "An error was thrown but an error was not expected"
+ }
+ }
+
It "Expands an archive to the working directory when it is specified as -DestinationPath" {
$sourcePath = "$TestDrive/archive1.zip"
$destinationPath = "$TestDrive/directory2"
@@ -441,7 +441,7 @@ Describe("Expand-Archive Tests") {
Pop-Location
}
- It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" {
+ It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" -Tag this1{
$sourcePath = "$TestDrive/archive2.zip"
$destinationPath = "$TestDrive/directory3"
@@ -449,9 +449,9 @@ Describe("Expand-Archive Tests") {
Expand-Archive -Path $sourcePath
- $itemsInDestinationPath = Get-ChildItem "$TestDrive/directory3" -Name -Recurse
+ $itemsInDestinationPath = Get-ChildItem "$TestDrive/directory3" -Recurse
$itemsInDestinationPath.Count | Should -Be 1
- $itemsInDestinationPath[0] | Should -Be "DirectoryToArchive"
+ $itemsInDestinationPath[0].Name | Should -Be "DirectoryToArchive"
Test-Path -Path "$TestDrive/directory3/DirectoryToArchive" -PathType Container
@@ -467,9 +467,11 @@ Describe("Expand-Archive Tests") {
Expand-Archive -Path $sourcePath
$itemsInDestinationPath = Get-ChildItem $destinationPath -Name -Recurse
- $itemsInDestinationPath.Count | Should -Be 2
- $itemsInDestinationPath.Contains("DirectoryToArchive") | Should -Be $true
- $itemsInDestinationPath.Contains("file1.txt") | Should -Be $true
+ $itemsInDestinationPath.Count | Should -Be 3
+ "archive3" | Should -BeIn $itemsInDestinationPath
+ "archive3${DS}DirectoryToArchive" | Should -BeIn $itemsInDestinationPath
+ "archive3${DS}file1.txt" | Should -BeIn $itemsInDestinationPath
+
Pop-Location
}
@@ -502,8 +504,9 @@ Describe("Expand-Archive Tests") {
$expandedItems = Get-ChildItem $destinationPath -Recurse -Name
- $itemsInArchive = @("file2.txt", "file3.txt", "emptydirectory1", "emptydirectory2", "nonemptydirectory1", "nonemptydirectory1/subfile1.txt", "nonemptydirectory2/subemptydirectory1")
+ $itemsInArchive = @("file2.txt", "file3.txt", "emptydirectory1", "emptydirectory2", "nonemptydirectory1", "nonemptydirectory2", "nonemptydirectory1${DS}subfile1.txt", "nonemptydirectory2${DS}subemptydirectory1")
+ $expandedItems.Length | Should -Be $itemsInArchive.Count
foreach ($item in $itemsInArchive) {
$item | Should -BeIn $expandedItems
}
@@ -550,5 +553,19 @@ Describe("Expand-Archive Tests") {
}
}
+ Context "PassThru tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -Path Test:/file1.txt
+ $archivePath = "TestDrive:/archive.zip"
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath
+ }
+
+ It "Returns a System.IO.FileInfo object when PassThru is specified" {
+ $output = Expand-Archive -Path $archivePath -DestinationPath TestDrive:/archive_contents -PassThru
+ $output.GetType()
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/ErrorMessages.cs b/src/ErrorMessages.cs
index 805b74d..810141e 100644
--- a/src/ErrorMessages.cs
+++ b/src/ErrorMessages.cs
@@ -40,6 +40,8 @@ internal static string GetErrorMessage(ErrorCode errorCode)
ErrorCode.InsufficientPermissionsToAccessPath => Messages.InsufficientPermssionsToAccessPathMessage,
ErrorCode.OverwriteDestinationPathFailed => Messages.OverwriteDestinationPathFailed,
ErrorCode.CannotOverwriteWorkingDirectory => Messages.CannotOverwriteWorkingDirectoryMessage,
+ ErrorCode.PathResolvedToMultiplePaths => Messages.PathResolvedToMultiplePathsMessage,
+ ErrorCode.CannotDetermineDestinationPath => Messages.CannotDetermineDestinationPath,
_ => throw new ArgumentOutOfRangeException(nameof(errorCode))
};
}
@@ -76,5 +78,7 @@ internal enum ErrorCode
CannotOverwriteWorkingDirectory,
// Expand-Archive: used when a path resolved to multiple paths when only one was needed
PathResolvedToMultiplePaths,
+ // Expand-Archive: used when the DestinationPath could not be determined
+ CannotDetermineDestinationPath
}
}
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 15371db..4ddc1cd 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -1,8 +1,10 @@
using Microsoft.PowerShell.Archive.Localized;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.IO.Compression;
+using System.IO.Enumeration;
using System.Linq;
using System.Management.Automation;
using System.Text;
@@ -22,9 +24,9 @@ public class ExpandArchiveCommand: ArchiveCommandBase
[ValidateNotNullOrEmpty]
public string LiteralPath { get; set; } = String.Empty;
- [Parameter(Position = 2, Mandatory = true)]
+ [Parameter(Position = 2)]
[ValidateNotNullOrEmpty]
- public string DestinationPath { get; set; } = String.Empty;
+ public string? DestinationPath { get; set; }
[Parameter]
public ExpandArchiveWriteMode WriteMode { get; set; } = ExpandArchiveWriteMode.Expand;
@@ -54,11 +56,7 @@ public ExpandArchiveCommand()
protected override void BeginProcessing()
{
- // Resolve DestinationPath
- _destinationPathInfo = _pathHelper.ResolveToSingleFullyQualifiedPath(path: DestinationPath, hasWildcards: false);
- DestinationPath = _destinationPathInfo.FullName;
-
- ValidateDestinationPath();
+
}
protected override void ProcessRecord()
@@ -78,12 +76,23 @@ protected override void EndProcessing()
// Determine archive format based on sourcePath
Format = DetermineArchiveFormat(destinationPath: sourcePath.FullName, archiveFormat: Format);
- // Get an archive from source path -- this is where we will switch between different types of archives
- using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: sourcePath.FullName, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
try
{
- // If the destination path is a file that needs to be overwriten, delete it
+ // Get an archive from source path -- this is where we will switch between different types of archives
+ using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: sourcePath.FullName, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
+
+ if (DestinationPath is null)
+ {
+ // If DestinationPath was not specified, try to determine it automatically based on the source path
+ // We should do this here because the destination path depends on whether the archive contains a single top-level directory or not
+ DestinationPath = DetermineDestinationPath(archive);
+ }
+ // Resolve DestinationPath and validate it
+ _destinationPathInfo = _pathHelper.ResolveToSingleFullyQualifiedPath(path: DestinationPath, hasWildcards: false);
+ DestinationPath = _destinationPathInfo.FullName;
+ ValidateDestinationPath(sourcePath);
+ // If the destination path is a file that needs to be overwriten, delete it
if (_destinationPathInfo.Exists && !_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory) && WriteMode == ExpandArchiveWriteMode.Overwrite)
{
if (ShouldProcess(target: _destinationPathInfo.FullName, action: "Overwrite"))
@@ -142,7 +151,6 @@ private void ProcessArchiveEntry(IEntry entry)
WriteError(errorRecord);
return;
}
-
System.IO.FileSystemInfo postExpandPathInfo = new System.IO.FileInfo(postExpandPath);
@@ -199,7 +207,8 @@ private void ProcessArchiveEntry(IEntry entry)
}
}
- private void ValidateDestinationPath()
+ // TODO: Refactor this
+ private void ValidateDestinationPath(FileSystemInfo sourcePath)
{
ErrorCode? errorCode = null;
@@ -229,6 +238,22 @@ private void ValidateDestinationPath()
var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode.Value, errorItem: _destinationPathInfo.FullName);
ThrowTerminatingError(errorRecord);
}
+
+ // Ensure sourcePath is not the same as the destination path when the cmdlet is in overwrite mode
+ // When the cmdlet is not in overwrite mode, other errors will be thrown when validating DestinationPath before it even gets to this line
+ if (PathHelper.ArePathsSame(sourcePath, _destinationPathInfo) && WriteMode == ExpandArchiveWriteMode.Overwrite)
+ {
+ if (ParameterSetName == "Path")
+ {
+ errorCode = ErrorCode.SamePathAndDestinationPath;
+ }
+ else
+ {
+ errorCode = ErrorCode.SameLiteralPathAndDestinationPath;
+ }
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode.Value, errorItem: sourcePath.FullName);
+ ThrowTerminatingError(errorRecord);
+ }
}
private void ValidateSourcePath(System.IO.FileSystemInfo sourcePath)
@@ -246,22 +271,6 @@ private void ValidateSourcePath(System.IO.FileSystemInfo sourcePath)
var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.DestinationExistsAsDirectory, errorItem: sourcePath.FullName);
ThrowTerminatingError(errorRecord);
}
-
- // Ensure sourcePath is not the same as the destination path when the cmdlet is in overwrite mode
- // When the cmdlet is not in overwrite mode, other errors will be thrown when validating DestinationPath before it even gets to this line
- if (PathHelper.ArePathsSame(sourcePath, _destinationPathInfo) && WriteMode == ExpandArchiveWriteMode.Overwrite)
- {
- ErrorCode errorCode;
- if (ParameterSetName == "Path")
- {
- errorCode = ErrorCode.SamePathAndDestinationPath;
- } else
- {
- errorCode = ErrorCode.SameLiteralPathAndDestinationPath;
- }
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode, errorItem: sourcePath.FullName);
- ThrowTerminatingError(errorRecord);
- }
}
private string GetPostExpansionPath(string entryName, string destinationPath)
@@ -287,21 +296,32 @@ private bool IsPathInvalid(string path)
private string DetermineDestinationPath(IArchive archive)
{
var workingDirectory = SessionState.Path.CurrentFileSystemLocation.ProviderPath;
- if (archive.HasTopLevelDirectoryOnly())
+ string? destinationDirectory = null;
+
+ // If the archive has a single top-level directory only, the destination will be: "working directory"
+ // This makes it easier for the cmdlet to expand the directory without needing addition checks
+ if (archive.HasTopLevelDirectory())
{
-
- } else
+ destinationDirectory = workingDirectory;
+ }
+ // Otherwise, the destination path will be: "working directory/archive file name"
+ else
{
- // destination path will be "working directory/archive file name"
var filename = System.IO.Path.GetFileName(archive.Path);
- // If filename does not have an extension, throw a terminating error because the cmdlet
- // cannot determine what destination path should be
- if (System.IO.Path.GetExtension(filename) == String.Empty)
+ // If filename does have an exension, remove the extension and set the filename minus extension as destinationDirectory
+ if (System.IO.Path.GetExtension(filename) != string.Empty)
{
- var errorRecord =
+ destinationDirectory = System.IO.Path.ChangeExtension(path: filename, extension: string.Empty);
}
- return System.IO.Path.Combine(workingDirectory, filename);
}
+
+ if (destinationDirectory is null)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.CannotDetermineDestinationPath);
+ ThrowTerminatingError(errorRecord);
+ }
+ Debug.Assert(destinationDirectory is not null);
+ return System.IO.Path.Combine(workingDirectory, destinationDirectory);
}
#endregion
diff --git a/src/IArchive.cs b/src/IArchive.cs
index afba831..a5d33be 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -34,6 +34,6 @@ internal interface IArchive: IDisposable
internal void Expand(string destinationPath);
// Does the archive have only a top-level directory?
- internal bool HasTopLevelDirectoryOnly();
+ internal bool HasTopLevelDirectory();
}
}
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index b2fe38f..b77c9e2 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -152,6 +152,9 @@
The destination {0} cannot be overwritten because it is a non-empty directory.
+
+ Create
+
The path(s) {0} have been specified more than once.
@@ -170,6 +173,9 @@
The path {0} could not be found.
+
+ The path {0} refers to multiple paths, but only one was expected. Perhaps this occured because multiple paths matched the wildcard (if applicable).
+
{0}% complete
@@ -182,4 +188,7 @@
A path supplied to -Path is the same as the path supplied to -DestinationPath.
+
+ The destination path cannot be determined automatically. Please use the -DestinationPath parameter to enter a destination path.
+
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Archive.csproj b/src/Microsoft.PowerShell.Archive.csproj
index b8bd133..f8bb9f9 100644
--- a/src/Microsoft.PowerShell.Archive.csproj
+++ b/src/Microsoft.PowerShell.Archive.csproj
@@ -25,14 +25,6 @@
-
-
- True
- True
- Messages.resx
-
-
-
ResXFileCodeGenerator
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index 5ae2e4d..070d6dd 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -25,6 +25,8 @@ internal class TarArchive : IArchive
string IArchive.Path => _path;
+ int IArchive.NumberOfEntries => throw new NotImplementedException();
+
public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
{
_mode = mode;
@@ -76,5 +78,10 @@ public void Dispose()
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
+
+ bool IArchive.HasTopLevelDirectory()
+ {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index 9b96595..eae5539 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -3,8 +3,8 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.IO.Compression;
-using System.Text;
namespace Microsoft.PowerShell.Archive
{
@@ -16,11 +16,11 @@ internal class ZipArchive : IArchive
private readonly string _archivePath;
- private readonly System.IO.FileStream _archiveStream;
+ private readonly FileStream _archiveStream;
private readonly System.IO.Compression.ZipArchive _zipArchive;
- private readonly System.IO.Compression.CompressionLevel _compressionLevel;
+ private readonly CompressionLevel _compressionLevel;
private const char ZipArchiveDirectoryPathTerminator = '/';
@@ -32,7 +32,7 @@ internal class ZipArchive : IArchive
int IArchive.NumberOfEntries => _zipArchive.Entries.Count;
- public ZipArchive(string archivePath, ArchiveMode mode, System.IO.FileStream archiveStream, CompressionLevel compressionLevel)
+ public ZipArchive(string archivePath, ArchiveMode mode, FileStream archiveStream, CompressionLevel compressionLevel)
{
_disposedValue = false;
_mode = mode;
@@ -162,16 +162,26 @@ public void Dispose()
GC.SuppressFinalize(this);
}
- bool IArchive.HasTopLevelDirectoryOnly()
+ bool IArchive.HasTopLevelDirectory()
{
- if (_zipArchive.Entries.Count == 0 || _zipArchive.Entries.Count > 1)
+ int topLevelDirectoriesCount = 0;
+ foreach (var entry in _zipArchive.Entries)
{
- return false;
+ if (entry.FullName.EndsWith(ZipArchiveDirectoryPathTerminator) &&
+ entry.FullName.LastIndexOf(ZipArchiveDirectoryPathTerminator, entry.FullName.Length - 2) == -1)
+ {
+ topLevelDirectoriesCount++;
+ if (topLevelDirectoriesCount > 1)
+ {
+ break;
+ }
+ } else
+ {
+ return false;
+ }
}
- // At this point, we know the archive has one entry only
- // We can determine if the entry is a directory by checking if the entry name ends with '/'
- return _zipArchive.Entries[0].FullName.EndsWith(ZipArchiveDirectoryPathTerminator);
+ return topLevelDirectoriesCount == 1;
}
internal class ZipArchiveEntry : IEntry
@@ -195,6 +205,12 @@ void IEntry.ExpandTo(string destinationPath)
System.IO.Directory.SetLastWriteTime(destinationPath, lastWriteTime.DateTime);
} else
{
+ // If the parent directory does not exist, create it
+ string? parentDirectory = Path.GetDirectoryName(destinationPath);
+ if (parentDirectory is not null && !Directory.Exists(parentDirectory))
+ {
+ Directory.CreateDirectory(parentDirectory);
+ }
_entry.ExtractToFile(destinationPath);
}
}
From e5237deabad0d4bc5c1fc434b426f42b6a1399eb Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Mon, 8 Aug 2022 18:01:01 -0700
Subject: [PATCH 09/34] added tests
---
Tests/Compress-Archive.Tests.ps1 | 79 +++++++-----
Tests/Expand-Archive.Tests.ps1 | 167 ++++++++++++++------------
src/CompressArchiveCommand.cs | 12 +-
src/ExpandArchiveCommand.cs | 126 +++++++++----------
src/Localized/Messages.resx | 3 +
src/Microsoft.PowerShell.Archive.psd1 | 2 +-
src/PathHelper.cs | 82 +++++++++++--
7 files changed, 283 insertions(+), 188 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 0ec9a46..9cbc7c1 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -281,8 +281,12 @@ BeforeDiscovery {
New-Item $TestDrive$($DS)EmptyFile -Type File | Out-Null
# Create a file whose last write time is before 1980
- $content | Out-File -FilePath $TestDrive$($DS)OldFile.txt
- Set-ItemProperty -Path $TestDrive$($DS)OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
+ $content | Out-File -FilePath TestDrive:/OldFile.txt
+ Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
+
+ # Create a directory whose last write time is before 1980
+ New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
}
It "Compresses a single file" {
@@ -408,11 +412,8 @@ BeforeDiscovery {
}
It "Compresses a directory whose last write time is before 1980" {
- New-Item -Path "$TestDrive/olddirectory" -ItemType Directory
- Set-ItemProperty -Path "$TestDrive/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
-
- $sourcePath = "$TestDrive$($DS)olddirectory"
- $destinationPath = "$TestDrive$($DS)archive12.zip"
+ $sourcePath = "TestDrive:/olddirectory"
+ $destinationPath = "${TestDrive}/archive12.zip"
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
$destinationPath | Should -Exist
@@ -438,6 +439,30 @@ BeforeDiscovery {
$archive.Dispose()
$archiveStream.Dispose()
}
+
+ It "Writes a warning when compressing a file whose last write time is before 1980" {
+ $sourcePath = "TestDrive:/OldFile.txt"
+ $destinationPath = "${TestDrive}/archive13.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
+ $warnings.Length | Should -Be 1
+ }
+
+ It "Writes a warning when compresing a directory whose last write time is before 1980" {
+ $sourcePath = "TestDrive:/olddirectory"
+ $destinationPath = "${TestDrive}/archive14.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
+ $warnings.Length | Should -Be 1
+ }
}
Context "Update tests" -Skip {
@@ -663,22 +688,12 @@ BeforeDiscovery {
Context "Special and Wildcard Characters Tests" {
BeforeAll {
-<<<<<<< HEAD
New-Item TestDrive:/SourceDir -Type Directory | Out-Null
$content = "Some Data"
$content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
New-Item -LiteralPath "$TestDrive$($DS)Source[]Dir" -Type Directory | Out-Null
$content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)file1[].txt
-=======
- New-Item $TestDrive$($DS)SourceDir -Type Directory | Out-Null
-
- New-Item -Path "$TestDrive$($DS)Source`[`]Dir" -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)Sample-1.txt
- $content | Out-File -LiteralPath $TestDrive$($DS)file1[].txt
->>>>>>> 8b3dcd5 (worked on Expand-Archive, added IEntry class, added support for ShouldProcess in Expand-Archive)
}
It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
@@ -692,16 +707,9 @@ BeforeDiscovery {
It "Accepts DestinationPath parameter with [ but no matching ]" {
$sourcePath = "TestDrive:/SourceDir"
$destinationPath = "TestDrive:/archive[2.zip"
-
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
-<<<<<<< HEAD
$destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
Remove-Item -LiteralPath $destinationPath
-=======
- Test-Path -LiteralPath $destinationPath | Should -Be $true
- Test-ZipArchive $destinationPath @("SourceDir/", "SourceDir/Sample-1.txt")
- Remove-Item -LiteralPath $destinationPath -Force
->>>>>>> 8b3dcd5 (worked on Expand-Archive, added IEntry class, added support for ShouldProcess in Expand-Archive)
}
It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
@@ -734,15 +742,26 @@ BeforeDiscovery {
}
}
- Context "test" -Tag lol {
+ Context "PassThru tests" {
BeforeAll {
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/Sample-1.txt
- Compress-Archive -Path TestDrive:/Sample-1.txt -DestinationPath TestDrive:/archive1.zip
+ New-Item -Path TestDrive:/file.txt -ItemType File
+ }
+
+ It "Returns an object of type System.IO.FileInfo when PassThru is specified" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip -PassThru
+ $output | Should -BeOfType System.IO.FileInfo
+ $destinationPath = Join-Path $TestDrive "archive1.zip"
+ $output.FullName | Should -Be $destinationPath
+ }
+
+ It "Does not return an object when PassThru is not specified" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
+ $output | Should -BeNullOrEmpty
}
- It "test custom assetion" {
- "${TestDrive}/archive1.zip" | Should -BeZipArchiveOnlyContaining @("Sample-1.txt")
+ It "Does not return an object when PassThru is false" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive3.zip -PassThru:$false
+ $output | Should -BeNullOrEmpty
}
}
}
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index ccb5b32..bd6ea25 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -2,20 +2,7 @@
Describe("Expand-Archive Tests") {
BeforeAll {
- function Add-CompressionAssemblies {
- Add-Type -AssemblyName System.IO.Compression
- if ($psedition -eq "Core")
- {
- Add-Type -AssemblyName System.IO.Compression.ZipFile
- }
- else
- {
- Add-Type -AssemblyName System.IO.Compression.FileSystem
- }
- }
$CmdletClassName = "Microsoft.PowerShell.Archive.ExpandArchiveCommand"
- $DS = [System.IO.Path]::DirectorySeparatorChar
- Add-CompressionAssemblies
# Progress perference
$originalProgressPref = $ProgressPreference
@@ -65,19 +52,19 @@ Describe("Expand-Archive Tests") {
}
# Set up files for tests
- New-Item $TestDrive$($DS)SourceDir -Type Directory | Out-Null
+ New-Item $TestDrive/SourceDir -Type Directory | Out-Null
$content = "Some Data"
- $content | Out-File -FilePath $TestDrive$($DS)Sample-1.txt
+ $content | Out-File -FilePath $TestDrive/Sample-1.txt
# Create archives called archive1.zip and archive2.zip
- Compress-Archive -Path $TestDrive$($DS)Sample-1.txt -DestinationPath $TestDrive$($DS)archive1.zip
- Compress-Archive -Path $TestDrive$($DS)Sample-1.txt -DestinationPath $TestDrive$($DS)archive2.zip
+ Compress-Archive -Path $TestDrive/Sample-1.txt -DestinationPath $TestDrive/archive1.zip
+ Compress-Archive -Path $TestDrive/Sample-1.txt -DestinationPath $TestDrive/archive2.zip
}
It "Validate errors with NULL & EMPTY values for Path, LiteralPath, and DestinationPath" {
- $sourcePath = "$TestDrive$($DS)SourceDir"
- $destinationPath = "$TestDrive$($DS)SampleSingleFile.zip"
+ $sourcePath = "$TestDrive/SourceDir"
+ $destinationPath = "$TestDrive/SampleSingleFile.zip"
ExpandArchivePathParameterSetValidator $null $destinationPath
ExpandArchivePathParameterSetValidator $sourcePath $null
@@ -96,8 +83,8 @@ Describe("Expand-Archive Tests") {
ExpandArchiveLiteralPathParameterSetValidator "" ""
}
- It "Throws when invalid path non-existing path is supplied for Path or LiteralPath parameters" {
- $path = "$TestDrive$($DS)non-existant.zip"
+ It "Throws when non-existing path is supplied for Path or LiteralPath parameters" {
+ $path = "$TestDrive/non-existant.zip"
$destinationPath = "$TestDrive($DS)DestinationFolder"
try
{
@@ -146,9 +133,9 @@ Describe("Expand-Archive Tests") {
It "Throws an error when multiple paths are supplied as input to Path parameter" {
$sourcePath = @(
- "$TestDrive$($DS)SourceDir$($DS)archive1.zip",
- "$TestDrive$($DS)SourceDir$($DS)archive2.zip")
- $destinationPath = "$TestDrive$($DS)DestinationFolder"
+ "$TestDrive/SourceDir/archive1.zip",
+ "$TestDrive/SourceDir/archive2.zip")
+ $destinationPath = "$TestDrive/DestinationFolder"
try
{
@@ -163,9 +150,9 @@ Describe("Expand-Archive Tests") {
It "Throws an error when multiple paths are supplied as input to LiteralPath parameter" {
$sourcePath = @(
- "$TestDrive$($DS)SourceDir$($DS)archive1.zip",
- "$TestDrive$($DS)SourceDir$($DS)archive2.zip")
- $destinationPath = "$TestDrive$($DS)DestinationFolder"
+ "$TestDrive/SourceDir/archive1.zip",
+ "$TestDrive/SourceDir/archive2.zip")
+ $destinationPath = "$TestDrive/DestinationFolder"
try
{
@@ -180,10 +167,10 @@ Describe("Expand-Archive Tests") {
## From 504
It "Validate that Source Path can be at SystemDrive location" -Skip {
- $sourcePath = "$env:SystemDrive$($DS)SourceDir"
- $destinationPath = "$TestDrive$($DS)SampleFromSystemDrive.zip"
+ $sourcePath = "$env:SystemDrive/SourceDir"
+ $destinationPath = "$TestDrive/SampleFromSystemDrive.zip"
New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux
- "Some Data" | Out-File -FilePath $sourcePath$($DS)SampleSourceFileForArchive.txt
+ "Some Data" | Out-File -FilePath $sourcePath/SampleSourceFileForArchive.txt
try
{
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
@@ -196,7 +183,7 @@ Describe("Expand-Archive Tests") {
}
It "Throws an error when Path and DestinationPath are the same and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $sourcePath = "$TestDrive/archive1.zip"
$destinationPath = $sourcePath
try {
@@ -208,7 +195,7 @@ Describe("Expand-Archive Tests") {
}
It "Throws an error when LiteralPath and DestinationPath are the same and WriteMode -Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $sourcePath = "$TestDrive/archive1.zip"
$destinationPath = $sourcePath
try {
@@ -218,6 +205,18 @@ Describe("Expand-Archive Tests") {
$_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,$CmdletClassName"
}
}
+
+ It "Throws an error when an invalid path is supplied to DestinationPath" {
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "Variable:/PWD"
+
+ try {
+ Expand-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect an error when an invalid path is supplied to DestinationPath"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "InvalidPath,$CmdletClassName"
+ }
+ }
}
Context "DestinationPath and Overwrite Tests" {
@@ -240,32 +239,32 @@ Describe("Expand-Archive Tests") {
# last write times
BeforeAll {
- New-Item -Path "$TestDrive$($DS)file1.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "$TestDrive$($DS)file1.txt"
- Compress-Archive -Path "$TestDrive$($DS)file1.txt" -DestinationPath "$TestDrive$($DS)archive1.zip"
+ New-Item -Path "$TestDrive/file1.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "$TestDrive/file1.txt"
+ Compress-Archive -Path "$TestDrive/file1.txt" -DestinationPath "$TestDrive/archive1.zip"
- New-Item -Path "$TestDrive$($DS)directory1" -ItemType Directory
+ New-Item -Path "$TestDrive/directory1" -ItemType Directory
# Create archive2.zip containing directory1
- Compress-Archive -Path "$TestDrive$($DS)directory1" -DestinationPath "$TestDrive$($DS)archive2.zip"
+ Compress-Archive -Path "$TestDrive/directory1" -DestinationPath "$TestDrive/archive2.zip"
- New-Item -Path "$TestDrive$($DS)ParentDir" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ParentDir/file1.txt" -ItemType Directory
+ New-Item -Path "$TestDrive/ParentDir" -ItemType Directory
+ New-Item -Path "$TestDrive/ParentDir/file1.txt" -ItemType Directory
# Create a dir that is a container for items to be overwritten
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2" -ItemType File
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" -ItemType File
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4/file1.txt" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4/file1.txt/somefile" -ItemType File
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/file2" -ItemType File
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir1" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir1/file1.txt" -ItemType File
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir2" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir2/file1.txt" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir4" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir4/file1.txt" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir4/file1.txt/somefile" -ItemType File
# Create directory to override
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3" -ItemType Directory
- New-Item -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3/directory1" -ItemType File
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir3" -ItemType Directory
+ New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir3/directory1" -ItemType File
# Set the error action preference so non-terminating errors aren't displayed
$ErrorActionPreference = 'SilentlyContinue'
@@ -277,8 +276,8 @@ Describe("Expand-Archive Tests") {
}
It "Throws an error when DestinationPath is an existing file" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)file1.txt"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/file1.txt"
try {
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
@@ -288,7 +287,7 @@ Describe("Expand-Archive Tests") {
}
It "Does not throw an error when a directory in the archive has the same destination path as an existing directory" {
- $sourcePath = "$TestDrive$($DS)archive2.zip"
+ $sourcePath = "$TestDrive/archive2.zip"
$destinationPath = "$TestDrive"
try {
@@ -299,7 +298,7 @@ Describe("Expand-Archive Tests") {
}
It "Writes a non-terminating error when a file in the archive has a destination path that already exists" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
+ $sourcePath = "$TestDrive/archive1.zip"
$destinationPath = "$TestDrive"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorVariable error
@@ -308,8 +307,8 @@ Describe("Expand-Archive Tests") {
}
It "Writes a non-terminating error when a file in the archive has a destination path that is an existing directory containing at least 1 item and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir4"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir4"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 1
@@ -317,8 +316,8 @@ Describe("Expand-Archive Tests") {
}
It "Writes a non-terminating error when a file in the archive has a destination path that is the working directory and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)ParentDir"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/ParentDir"
Push-Location "$destinationPath/file1.txt"
@@ -332,52 +331,52 @@ Describe("Expand-Archive Tests") {
}
It "Overwrites a file when it is DestinationPath and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/file2"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/ItemsToOverwriteContainer/file2"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/file2/file1.txt" -PathType Leaf
+ Test-Path "$TestDrive/ItemsToOverwriteContainer/file2/file1.txt" -PathType Leaf
}
It "Overwrites a file whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" -Tag td {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir1"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" -PathType Leaf
+ Test-Path "$TestDrive/ItemsToOverwriteContainer/subdir1/file1.txt" -PathType Leaf
# Ensure the contents of file1.txt is "Hello, World!"
- Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
+ Get-Content -Path "$TestDrive/ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
}
It "Overwrites a directory whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir2"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" -PathType Leaf
+ Test-Path "$TestDrive/ItemsToOverwriteContainer/subdir2/file1.txt" -PathType Leaf
# Ensure the contents of file1.txt is "Hello, World!"
- Get-Content -Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir2/file1.txt" | Should -Be "Hello, World!"
+ Get-Content -Path "$TestDrive/ItemsToOverwriteContainer/subdir2/file1.txt" | Should -Be "Hello, World!"
}
It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive$($DS)archive2.zip"
- $destinationPath = "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3"
+ $sourcePath = "$TestDrive/archive2.zip"
+ $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir3"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive$($DS)ItemsToOverwriteContainer/subdir3/directory1" -PathType Container
+ Test-Path "$TestDrive/ItemsToOverwriteContainer/subdir3/directory1" -PathType Container
}
}
@@ -416,8 +415,8 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive when DestinationPath is an existing directory" {
- $sourcePath = "$TestDrive$($DS)archive1.zip"
- $destinationPath = "$TestDrive$($DS)directory1"
+ $sourcePath = "$TestDrive/archive1.zip"
+ $destinationPath = "$TestDrive/directory1"
try {
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
@@ -558,12 +557,24 @@ Describe("Expand-Archive Tests") {
New-Item -Path TestDrive:/file1.txt -ItemType File
"Hello, World!" | Out-File -Path Test:/file1.txt
$archivePath = "TestDrive:/archive.zip"
- Compress-Archive -Path TestDrive:/file1.txt -DestinationPath
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $archivePath
+ }
+
+ It "Returns a System.IO.DirectoryInfo object when PassThru is specified" {
+ $destinationPath = "{TestDrive}/archive_contents"
+ $output = Expand-Archive -Path $archivePath -DestinationPath $destinationPath -PassThru
+ $output | Should -BeOfType System.IO.DirectoryInfo
+ $output.FullName | SHould -Be $destinationPath
+ }
+
+ It "Does not return an object when PassThru is not specified" {
+ $output = Expand-Archive -Path $archivePath -DestinationPath TestDrive:/archive_contents2
+ $output | Should -BeNullOrEmpty
}
- It "Returns a System.IO.FileInfo object when PassThru is specified" {
- $output = Expand-Archive -Path $archivePath -DestinationPath TestDrive:/archive_contents -PassThru
- $output.GetType()
+ It "Does not return an object when PassThru is false" {
+ $output = Compress-Archive -Path $archivePath -DestinationPath TestDrive:/archive_contents -PassThru:$false
+ $output | Should -BeNullOrEmpty
}
}
diff --git a/src/CompressArchiveCommand.cs b/src/CompressArchiveCommand.cs
index cadecd0..e76aea5 100644
--- a/src/CompressArchiveCommand.cs
+++ b/src/CompressArchiveCommand.cs
@@ -94,7 +94,7 @@ public CompressArchiveCommand()
protected override void BeginProcessing()
{
// This resolves the path to a fully qualified path and handles provider exceptions
- DestinationPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(DestinationPath);
+ DestinationPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path: DestinationPath, pathMustExist: false);
ValidateDestinationPath();
}
@@ -104,7 +104,7 @@ protected override void ProcessRecord()
{
Debug.Assert(Path is not null);
foreach (var path in Path) {
- var resolvedPaths = _pathHelper.GetResolvedPathFromPSProviderPath(path, _nonexistentPaths);
+ var resolvedPaths = _pathHelper.GetResolvedPathFromPSProviderPathWhileCapturingNonexistentPaths(path, _nonexistentPaths);
if (resolvedPaths is not null) {
foreach (var resolvedPath in resolvedPaths) {
// Add resolvedPath to _path
@@ -118,7 +118,7 @@ protected override void ProcessRecord()
{
Debug.Assert(LiteralPath is not null);
foreach (var path in LiteralPath) {
- var unresolvedPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path, _nonexistentPaths);
+ var unresolvedPath = _pathHelper.GetUnresolvedPathFromPSProviderPathWhileCapturingNonexistentPaths(path, _nonexistentPaths);
if (unresolvedPath is not null) {
// Add unresolvedPath to _path
AddPathToPaths(pathToAdd: unresolvedPath);
@@ -155,6 +155,7 @@ protected override void EndProcessing()
// Get archive entries
// If a path causes an exception (e.g., SecurityException), _pathHelper should handle it
+ Debug.Assert(_paths is not null);
List archiveAdditions = _pathHelper.GetArchiveAdditions(_paths);
// Remove references to _paths, Path, and LiteralPath to free up memory
@@ -203,6 +204,11 @@ protected override void EndProcessing()
if (ShouldProcess(target: entry.FileSystemInfo.FullName, action: Messages.Add))
{
+ // Warn the user if the LastWriteTime of the file/directory is before 1980
+ if (entry.FileSystemInfo.LastWriteTime.Year < 1980 && Format == ArchiveFormat.Zip) {
+ WriteWarning(string.Format(Messages.LastWriteTimeBefore1980Warning, entry.FileSystemInfo.FullName));
+ }
+
archive?.AddFileSystemEntry(entry);
// Write a verbose message saying this item was added to the archive
var addedItemMessage = string.Format(Messages.AddedItemToArchiveVerboseMessage, entry.FileSystemInfo.FullName);
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 4ddc1cd..3b6a0e5 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -16,11 +16,16 @@ namespace Microsoft.PowerShell.Archive
[OutputType(typeof(System.IO.FileSystemInfo))]
public class ExpandArchiveCommand: ArchiveCommandBase
{
- [Parameter(Position=0, Mandatory = true, ParameterSetName = "Path", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
+ private enum ParameterSet {
+ Path,
+ LiteralPath
+ }
+
+ [Parameter(Position=0, Mandatory = true, ParameterSetName = nameof(ParameterSet.Path), ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
[ValidateNotNullOrEmpty]
public string Path { get; set; } = String.Empty;
- [Parameter(Mandatory = true, ParameterSetName = "LiteralPath", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
+ [Parameter(Mandatory = true, ParameterSetName = nameof(ParameterSet.LiteralPath))]
[ValidateNotNullOrEmpty]
public string LiteralPath { get; set; } = String.Empty;
@@ -45,6 +50,8 @@ public class ExpandArchiveCommand: ArchiveCommandBase
private bool _didCreateOutput;
+ private string? _sourcePath;
+
#endregion
public ExpandArchiveCommand()
@@ -67,47 +74,45 @@ protected override void ProcessRecord()
protected override void EndProcessing()
{
// Resolve Path or LiteralPath
- bool checkForWildcards = ParameterSetName == "Path";
+ bool checkForWildcards = ParameterSetName == nameof(ParameterSet.Path);
string path = checkForWildcards ? Path : LiteralPath;
- System.IO.FileSystemInfo sourcePath = _pathHelper.ResolveToSingleFullyQualifiedPath(path: path, hasWildcards: checkForWildcards);
-
- ValidateSourcePath(sourcePath);
+ ValidateSourcePath(path);
+ Debug.Assert(_sourcePath is not null);
// Determine archive format based on sourcePath
- Format = DetermineArchiveFormat(destinationPath: sourcePath.FullName, archiveFormat: Format);
+ Format = DetermineArchiveFormat(destinationPath: _sourcePath, archiveFormat: Format);
try
{
// Get an archive from source path -- this is where we will switch between different types of archives
- using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: sourcePath.FullName, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
+ using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: _sourcePath, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
if (DestinationPath is null)
{
// If DestinationPath was not specified, try to determine it automatically based on the source path
// We should do this here because the destination path depends on whether the archive contains a single top-level directory or not
DestinationPath = DetermineDestinationPath(archive);
+ } else {
+ // Resolve DestinationPath and validate it
+ DestinationPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path: DestinationPath, pathMustExist: true);
}
- // Resolve DestinationPath and validate it
- _destinationPathInfo = _pathHelper.ResolveToSingleFullyQualifiedPath(path: DestinationPath, hasWildcards: false);
- DestinationPath = _destinationPathInfo.FullName;
- ValidateDestinationPath(sourcePath);
+ ValidateDestinationPath();
+ Debug.Assert(DestinationPath is not null);
// If the destination path is a file that needs to be overwriten, delete it
- if (_destinationPathInfo.Exists && !_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory) && WriteMode == ExpandArchiveWriteMode.Overwrite)
+ if (File.Exists(DestinationPath) && WriteMode == ExpandArchiveWriteMode.Overwrite)
{
- if (ShouldProcess(target: _destinationPathInfo.FullName, action: "Overwrite"))
+ if (ShouldProcess(target: DestinationPath, action: "Overwrite"))
{
- _destinationPathInfo.Delete();
- System.IO.Directory.CreateDirectory(_destinationPathInfo.FullName);
- _destinationPathInfo = new System.IO.DirectoryInfo(_destinationPathInfo.FullName);
+ File.Delete(DestinationPath);
+ System.IO.Directory.CreateDirectory(DestinationPath);
}
}
// If the destination path does not exist, create it
- if (!_destinationPathInfo.Exists && ShouldProcess(target: _destinationPathInfo.FullName, action: "Create"))
+ if (!Directory.Exists(DestinationPath) && ShouldProcess(target: DestinationPath, action: "Create"))
{
- System.IO.Directory.CreateDirectory(_destinationPathInfo.FullName);
- _destinationPathInfo = new System.IO.DirectoryInfo(_destinationPathInfo.FullName);
+ System.IO.Directory.CreateDirectory(DestinationPath);
}
// Get the next entry in the archive and process it
@@ -135,6 +140,8 @@ protected override void StopProcessing()
private void ProcessArchiveEntry(IEntry entry)
{
+ Debug.Assert(DestinationPath is not null);
+
// The location of the entry post-expanding of the archive
string postExpandPath = GetPostExpansionPath(entryName: entry.Name, destinationPath: _destinationPathInfo.FullName);
@@ -196,7 +203,6 @@ private void ProcessArchiveEntry(IEntry entry)
if (hasCollision)
{
postExpandPathInfo.Delete();
- System.Threading.Thread.Sleep(1000);
}
// Only expand the entry if there is a need to expand
// There is a need to expand unless the entry is a directory and the postExpandPath is also a directory
@@ -207,68 +213,54 @@ private void ProcessArchiveEntry(IEntry entry)
}
}
- // TODO: Refactor this
- private void ValidateDestinationPath(FileSystemInfo sourcePath)
+ private void ValidateDestinationPath()
{
- ErrorCode? errorCode = null;
-
- // In this case, DestinationPath does not exist
- if (!_destinationPathInfo.Exists)
- {
- // Do nothing
- }
- // Check if DestinationPath is an existing directory
- else if (_destinationPathInfo.Attributes.HasFlag(FileAttributes.Directory))
- {
- // Do nothing
- }
- // If DestinationPath is an existing file
- else
- {
- // Throw an error if DestinationPath exists and the cmdlet is not in Overwrite mode
- if (WriteMode == ExpandArchiveWriteMode.Expand)
- {
- errorCode = ErrorCode.DestinationExists;
- }
- }
+ Debug.Assert(DestinationPath is not null);
- if (errorCode != null)
- {
- // Throw an error -- since we are validating DestinationPath, the problem is with DestinationPath
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode.Value, errorItem: _destinationPathInfo.FullName);
+ // Throw an error if DestinationPath exists and the cmdlet is not in Overwrite mode
+ if (File.Exists(DestinationPath) && WriteMode == ExpandArchiveWriteMode.Expand) {
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.CannotDetermineDestinationPath, errorItem: DestinationPath);
ThrowTerminatingError(errorRecord);
}
// Ensure sourcePath is not the same as the destination path when the cmdlet is in overwrite mode
// When the cmdlet is not in overwrite mode, other errors will be thrown when validating DestinationPath before it even gets to this line
- if (PathHelper.ArePathsSame(sourcePath, _destinationPathInfo) && WriteMode == ExpandArchiveWriteMode.Overwrite)
+ if (_sourcePath == DestinationPath && WriteMode == ExpandArchiveWriteMode.Overwrite)
{
- if (ParameterSetName == "Path")
- {
- errorCode = ErrorCode.SamePathAndDestinationPath;
- }
- else
- {
- errorCode = ErrorCode.SameLiteralPathAndDestinationPath;
- }
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode.Value, errorItem: sourcePath.FullName);
+ ErrorCode errorCode = (ParameterSetName == nameof(ParameterSet.Path)) ? ErrorCode.SamePathAndDestinationPath : ErrorCode.SameLiteralPathAndDestinationPath;
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: errorCode, errorItem: DestinationPath);
ThrowTerminatingError(errorRecord);
}
}
- private void ValidateSourcePath(System.IO.FileSystemInfo sourcePath)
+ private void ValidateSourcePath(string path)
{
- // Throw a terminating error if sourcePath does not exist
- if (!sourcePath.Exists)
- {
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.PathNotFound, errorItem: sourcePath.FullName);
- ThrowTerminatingError(errorRecord);
+ // Resolve path
+ if (ParameterSetName == nameof(ParameterSet.Path)) {
+ // Set nonexistentPaths to null because we don't want to capture any nonexistent paths
+ var resolvedPaths = _pathHelper.GetResolvedPathFromPSProviderPath(path: path, pathMustExist: true);
+ Debug.Assert(resolvedPaths is not null);
+
+ // If the path resolves to multiple paths, throw a terminating error
+ if (resolvedPaths.Count > 1) {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.PathResolvedToMultiplePaths, path);
+ ThrowTerminatingError(errorRecord);
+ }
+
+ // Set _sourcePath to the first & only path in resolvedPaths
+ _sourcePath = resolvedPaths[0];
+ } else {
+ // Set nonexistentPaths to null because we don't want to capture any nonexistent paths
+ var resolvedPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path: path, pathMustExist: true);
+ Debug.Assert(resolvedPath is not null);
+ // Set _sourcePath to resolvedPath
+ _sourcePath = resolvedPath;
}
- // Throw a terminating error if sourcePath is a directory
- if (sourcePath.Attributes.HasFlag(FileAttributes.Directory))
+ // Throw a terminating error if _sourcePath is a directory
+ if (Directory.Exists(_sourcePath))
{
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.DestinationExistsAsDirectory, errorItem: sourcePath.FullName);
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.DestinationExistsAsDirectory, errorItem: _sourcePath);
ThrowTerminatingError(errorRecord);
}
}
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index b77c9e2..7d28961 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -191,4 +191,7 @@
The destination path cannot be determined automatically. Please use the -DestinationPath parameter to enter a destination path.
+
+ The last write time of the path {0} is before 1980. Since zip does not support dates before January 1, 1980, the last write time will be set to January 1, 1980.
+
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1
index 46df1a8..6dd9f7d 100644
--- a/src/Microsoft.PowerShell.Archive.psd1
+++ b/src/Microsoft.PowerShell.Archive.psd1
@@ -7,7 +7,7 @@ Copyright = '(c) Microsoft. All rights reserved.'
Description = 'PowerShell module for creating and expanding archives.'
PowerShellVersion = '7.2.5'
NestedModules = @('Microsoft.PowerShell.Archive.dll')
-CmdletsToExport = @('Compress-Archive')
+CmdletsToExport = @('Compress-Archive', 'Expand-Archive')
PrivateData = @{
PSData = @{
Tags = @('Archive', 'Zip', 'Compress')
diff --git a/src/PathHelper.cs b/src/PathHelper.cs
index b5262ed..9de80cb 100644
--- a/src/PathHelper.cs
+++ b/src/PathHelper.cs
@@ -212,7 +212,7 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
return relativePathToWorkingDirectory is not null;
}
- internal System.Collections.ObjectModel.Collection? GetResolvedPathFromPSProviderPath(string path, HashSet nonexistentPaths) {
+ internal System.Collections.ObjectModel.Collection? GetResolvedPathFromPSProviderPath(string path, bool pathMustExist) {
// Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord
Exception? exception = null;
System.Collections.ObjectModel.Collection? fullyQualifiedPaths = null;
@@ -251,8 +251,67 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
{
exception = invalidOperationException;
}
- // If a path can't be found, write an error
- catch (System.Management.Automation.ItemNotFoundException)
+ // If a path can't be found, add it the set of non-existant paths
+ catch (System.Management.Automation.ItemNotFoundException itemNotFoundException)
+ {
+ if (pathMustExist) {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.PathNotFound);
+ _cmdlet.ThrowTerminatingError(errorRecord);
+ }
+ }
+
+ // If an exception was caught, write a non-terminating error
+ if (exception is not null)
+ {
+ var errorRecord = new ErrorRecord(exception: exception, errorId: nameof(ErrorCode.InvalidPath), errorCategory: ErrorCategory.InvalidArgument,
+ targetObject: path);
+ _cmdlet.ThrowTerminatingError(errorRecord);
+ }
+
+ return fullyQualifiedPaths;
+ }
+
+ internal System.Collections.ObjectModel.Collection? GetResolvedPathFromPSProviderPathWhileCapturingNonexistentPaths(string path, HashSet nonexistentPaths) {
+ // Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord
+ Exception? exception = null;
+ System.Collections.ObjectModel.Collection? fullyQualifiedPaths = null;
+ try
+ {
+ // Resolve path
+ var resolvedPaths = _cmdlet.SessionState.Path.GetResolvedProviderPathFromPSPath(path, out var providerInfo);
+
+ // If the path is from the filesystem, set it to fullyQualifiedPaths so it can be returned
+ // Otherwise, create an exception so an error will be written
+ if (providerInfo.Name != FileSystemProviderName)
+ {
+ var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.InvalidPath);
+ exception = new ArgumentException(exceptionMsg);
+ } else {
+ fullyQualifiedPaths = resolvedPaths;
+ }
+ }
+ catch (System.Management.Automation.ProviderNotFoundException providerNotFoundException)
+ {
+ exception = providerNotFoundException;
+ }
+ catch (System.Management.Automation.DriveNotFoundException driveNotFoundException)
+ {
+ exception = driveNotFoundException;
+ }
+ catch (System.Management.Automation.ProviderInvocationException providerInvocationException)
+ {
+ exception = providerInvocationException;
+ }
+ catch (System.Management.Automation.PSNotSupportedException notSupportedException)
+ {
+ exception = notSupportedException;
+ }
+ catch (System.Management.Automation.PSInvalidOperationException invalidOperationException)
+ {
+ exception = invalidOperationException;
+ }
+ // If a path can't be found, add it the set of non-existant paths
+ catch (System.Management.Automation.ItemNotFoundException itemNotFoundException)
{
nonexistentPaths.Add(path);
}
@@ -268,9 +327,9 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
return fullyQualifiedPaths;
}
- // Resolves a literal path. Does not check if the path exists.
- // If an exception occurs with a provider, it throws a terminating error
- internal string? GetUnresolvedPathFromPSProviderPath(string path) {
+ // Resolves a literal path. If it does not exist, it adds the path to nonexistentPaths.
+ // If an exception occurs with a provider, it writes a non-terminating error
+ internal string? GetUnresolvedPathFromPSProviderPath(string path, bool pathMustExist) {
// Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord
Exception? exception = null;
string? fullyQualifiedPath = null;
@@ -286,6 +345,11 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.InvalidPath);
exception = new ArgumentException(exceptionMsg);
}
+ // If the path does not exist, create an exception
+ else if (pathMustExist && !Path.Exists(resolvedPath)) {
+ var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.PathNotFound);
+ exception = new ArgumentException(exceptionMsg);
+ }
else
{
fullyQualifiedPath = resolvedPath;
@@ -312,7 +376,7 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
exception = invalidOperationException;
}
- // If an exception was caught, write a non-terminating error of throwError == false. Otherwise, throw a terminating errror
+ // If an exception was caught, write a non-terminating error
if (exception is not null)
{
var errorRecord = new ErrorRecord(exception: exception, errorId: nameof(ErrorCode.InvalidPath), errorCategory: ErrorCategory.InvalidArgument,
@@ -325,7 +389,7 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
// Resolves a literal path. If it does not exist, it adds the path to nonexistentPaths.
// If an exception occurs with a provider, it writes a non-terminating error
- internal string? GetUnresolvedPathFromPSProviderPath(string path, HashSet nonexistentPaths) {
+ internal string? GetUnresolvedPathFromPSProviderPathWhileCapturingNonexistentPaths(string path, HashSet nonexistentPaths) {
// Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord
Exception? exception = null;
string? fullyQualifiedPath = null;
@@ -341,7 +405,7 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.InvalidPath);
exception = new ArgumentException(exceptionMsg);
}
- // If the path does not exist, create an exception
+ // If the path does not exist, capture it
else if (!Path.Exists(resolvedPath)) {
nonexistentPaths.Add(resolvedPath);
}
From e68ea094a9bb40c178200103f0742c8b56fd6a65 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 9 Aug 2022 14:44:17 -0700
Subject: [PATCH 10/34] added tests for special and wildcard tests, invalid
DestinationPath, file permissions, and fixed a bug where the wrong error ID
was used
---
Tests/Compress-Archive.Tests.ps1 | 12 +
Tests/Expand-Archive.Tests.ps1 | 428 +++++++++++++++++--------------
src/ExpandArchiveCommand.cs | 9 +-
src/PathHelper.cs | 10 +-
4 files changed, 262 insertions(+), 197 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 9cbc7c1..dc3290f 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -225,6 +225,18 @@ BeforeDiscovery {
$_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
+
+ It "Throws an error when an invalid path is supplied to DestinationPath" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "Variable:/PWD"
+
+ try {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect an error when an invalid path is supplied to DestinationPath"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "InvalidPath,$CmdletClassName"
+ }
+ }
}
Context "WriteMode tests" {
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index bd6ea25..fc8a636 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -15,77 +15,50 @@ Describe("Expand-Archive Tests") {
Context "Parameter set validation tests" {
BeforeAll {
- function ExpandArchivePathParameterSetValidator {
- param
- (
- [string] $path,
- [string] $destinationPath
- )
-
- try
- {
- Expand-Archive -Path $path -DestinationPath $destinationPath
- throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to Path parameterset."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,$CmdletClassName"
- }
- }
-
- function ExpandArchiveLiteralPathParameterSetValidator {
- param
- (
- [string] $literalPath,
- [string] $destinationPath
- )
-
- try
- {
- Expand-Archive -LiteralPath $literalPath -DestinationPath $destinationPath
- throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,$CmdletClassName"
- }
- }
-
# Set up files for tests
- New-Item $TestDrive/SourceDir -Type Directory | Out-Null
+ New-Item TestDrive:/SourceDir -Type Directory | Out-Null
$content = "Some Data"
- $content | Out-File -FilePath $TestDrive/Sample-1.txt
+ $content | Out-File -FilePath TestDrive:/Sample-1.txt
# Create archives called archive1.zip and archive2.zip
- Compress-Archive -Path $TestDrive/Sample-1.txt -DestinationPath $TestDrive/archive1.zip
- Compress-Archive -Path $TestDrive/Sample-1.txt -DestinationPath $TestDrive/archive2.zip
+ Compress-Archive -Path TestDrive:/Sample-1.txt -DestinationPath TestDrive:/archive1.zip
+ Compress-Archive -Path TestDrive:/Sample-1.txt -DestinationPath TestDrive:/archive2.zip
}
- It "Validate errors with NULL & EMPTY values for Path, LiteralPath, and DestinationPath" {
- $sourcePath = "$TestDrive/SourceDir"
- $destinationPath = "$TestDrive/SampleSingleFile.zip"
-
- ExpandArchivePathParameterSetValidator $null $destinationPath
- ExpandArchivePathParameterSetValidator $sourcePath $null
- ExpandArchivePathParameterSetValidator $null $null
-
- ExpandArchivePathParameterSetValidator "" $destinationPath
- ExpandArchivePathParameterSetValidator $sourcePath ""
- ExpandArchivePathParameterSetValidator "" ""
+ It "Validate errors with NULL & EMPTY values for Path, LiteralPath, and DestinationPath" -ForEach @(
+ @{ Path = $null; DestinationPath = "TestDrive:/destination" }
+ @{ Path = "TestDrive:/archive1.zip"; DestinationPath = $null }
+ @{ Path = $null; DestinationPath = $null }
+ @{ Path = ""; DestinationPath = "TestDrive:/destination" }
+ @{ Path = "TestDrive:/archive1.zip"; DestinationPath = "" }
+ @{ Path = ""; DestinationPath = "" }
+ ) {
- ExpandArchiveLiteralPathParameterSetValidator $null $destinationPath
- ExpandArchiveLiteralPathParameterSetValidator $sourcePath $null
- ExpandArchiveLiteralPathParameterSetValidator $null $null
+ try
+ {
+ Expand-Archive -Path $Path -DestinationPath $DestinationPath
+ throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to Path parameterset."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,$CmdletClassName"
+ }
- ExpandArchiveLiteralPathParameterSetValidator "" $destinationPath
- ExpandArchiveLiteralPathParameterSetValidator $sourcePath ""
- ExpandArchiveLiteralPathParameterSetValidator "" ""
+ try
+ {
+ Expand-Archive -LiteralPath $Path -DestinationPath $DestinationPath
+ throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,$CmdletClassName"
+ }
}
It "Throws when non-existing path is supplied for Path or LiteralPath parameters" {
- $path = "$TestDrive/non-existant.zip"
- $destinationPath = "$TestDrive($DS)DestinationFolder"
+ $path = "TestDrive:/non-existant.zip"
+ $destinationPath = "TestDrive:($DS)DestinationFolder"
try
{
Expand-Archive -Path $path -DestinationPath $destinationPath
@@ -107,9 +80,9 @@ Describe("Expand-Archive Tests") {
}
}
- It "Throws when invalid path non-filesystem path is supplied for Path or LiteralPath parameters" {
- $path = "Variable:DS"
- $destinationPath = "$TestDrive($DS)DestinationFolder"
+ It "Throws when path non-filesystem path is supplied for Path or LiteralPath parameters" {
+ $path = "Variable:/PWD"
+ $destinationPath = "TestDrive:($DS)DestinationFolder"
try
{
Expand-Archive -Path $path -DestinationPath $destinationPath
@@ -133,9 +106,9 @@ Describe("Expand-Archive Tests") {
It "Throws an error when multiple paths are supplied as input to Path parameter" {
$sourcePath = @(
- "$TestDrive/SourceDir/archive1.zip",
- "$TestDrive/SourceDir/archive2.zip")
- $destinationPath = "$TestDrive/DestinationFolder"
+ "TestDrive:/SourceDir/archive1.zip",
+ "TestDrive:/SourceDir/archive2.zip")
+ $destinationPath = "TestDrive:/DestinationFolder"
try
{
@@ -150,9 +123,9 @@ Describe("Expand-Archive Tests") {
It "Throws an error when multiple paths are supplied as input to LiteralPath parameter" {
$sourcePath = @(
- "$TestDrive/SourceDir/archive1.zip",
- "$TestDrive/SourceDir/archive2.zip")
- $destinationPath = "$TestDrive/DestinationFolder"
+ "TestDrive:/SourceDir/archive1.zip",
+ "TestDrive:/SourceDir/archive2.zip")
+ $destinationPath = "TestDrive:/DestinationFolder"
try
{
@@ -168,7 +141,7 @@ Describe("Expand-Archive Tests") {
## From 504
It "Validate that Source Path can be at SystemDrive location" -Skip {
$sourcePath = "$env:SystemDrive/SourceDir"
- $destinationPath = "$TestDrive/SampleFromSystemDrive.zip"
+ $destinationPath = "TestDrive:/SampleFromSystemDrive.zip"
New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux
"Some Data" | Out-File -FilePath $sourcePath/SampleSourceFileForArchive.txt
try
@@ -183,7 +156,7 @@ Describe("Expand-Archive Tests") {
}
It "Throws an error when Path and DestinationPath are the same and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive/archive1.zip"
+ $sourcePath = "TestDrive:/archive1.zip"
$destinationPath = $sourcePath
try {
@@ -195,7 +168,7 @@ Describe("Expand-Archive Tests") {
}
It "Throws an error when LiteralPath and DestinationPath are the same and WriteMode -Overwrite is specified" {
- $sourcePath = "$TestDrive/archive1.zip"
+ $sourcePath = "TestDrive:/archive1.zip"
$destinationPath = $sourcePath
try {
@@ -207,7 +180,7 @@ Describe("Expand-Archive Tests") {
}
It "Throws an error when an invalid path is supplied to DestinationPath" {
- $sourcePath = "$TestDrive/archive1.zip"
+ $sourcePath = "TestDrive:/archive1.zip"
$destinationPath = "Variable:/PWD"
try {
@@ -220,51 +193,33 @@ Describe("Expand-Archive Tests") {
}
Context "DestinationPath and Overwrite Tests" {
- # error when destination path is a file and overwrite is not specified
- # error when output has same name as existant file and overwrite is not specified
-
- # no error when destination path is existing folder
- # no error when output is folder
-
- # output is directory w/ at least 1 item
- # output has same name as current working directory
-
- # overwrite file works
- # overwrite output file works done
- # overwrite file w/file done
- # overwrite output file w/directory
- # overwrite directory w/file
- # overwrite non-existant path works
-
- # last write times
-
BeforeAll {
- New-Item -Path "$TestDrive/file1.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "$TestDrive/file1.txt"
- Compress-Archive -Path "$TestDrive/file1.txt" -DestinationPath "$TestDrive/archive1.zip"
+ New-Item -Path "TestDrive:/file1.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "TestDrive:/file1.txt"
+ Compress-Archive -Path "TestDrive:/file1.txt" -DestinationPath "TestDrive:/archive1.zip"
- New-Item -Path "$TestDrive/directory1" -ItemType Directory
+ New-Item -Path "TestDrive:/directory1" -ItemType Directory
# Create archive2.zip containing directory1
- Compress-Archive -Path "$TestDrive/directory1" -DestinationPath "$TestDrive/archive2.zip"
+ Compress-Archive -Path "TestDrive:/directory1" -DestinationPath "TestDrive:/archive2.zip"
- New-Item -Path "$TestDrive/ParentDir" -ItemType Directory
- New-Item -Path "$TestDrive/ParentDir/file1.txt" -ItemType Directory
+ New-Item -Path "TestDrive:/ParentDir" -ItemType Directory
+ New-Item -Path "TestDrive:/ParentDir/file1.txt" -ItemType Directory
# Create a dir that is a container for items to be overwritten
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/file2" -ItemType File
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir1" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir1/file1.txt" -ItemType File
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir2" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir2/file1.txt" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir4" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir4/file1.txt" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir4/file1.txt/somefile" -ItemType File
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/file2" -ItemType File
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir1" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir1/file1.txt" -ItemType File
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir2" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir2/file1.txt" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir4" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir4/file1.txt" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir4/file1.txt/somefile" -ItemType File
# Create directory to override
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir3" -ItemType Directory
- New-Item -Path "$TestDrive/ItemsToOverwriteContainer/subdir3/directory1" -ItemType File
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir3" -ItemType Directory
+ New-Item -Path "TestDrive:/ItemsToOverwriteContainer/subdir3/directory1" -ItemType File
# Set the error action preference so non-terminating errors aren't displayed
$ErrorActionPreference = 'SilentlyContinue'
@@ -275,9 +230,9 @@ Describe("Expand-Archive Tests") {
$ErrorActionPreference = 'Continue'
}
- It "Throws an error when DestinationPath is an existing file" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/file1.txt"
+ It "Throws an error when DestinationPath is an existing file" -Tag debug2 {
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/file1.txt"
try {
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
@@ -287,8 +242,8 @@ Describe("Expand-Archive Tests") {
}
It "Does not throw an error when a directory in the archive has the same destination path as an existing directory" {
- $sourcePath = "$TestDrive/archive2.zip"
- $destinationPath = "$TestDrive"
+ $sourcePath = "TestDrive:/archive2.zip"
+ $destinationPath = "TestDrive:"
try {
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
@@ -298,8 +253,8 @@ Describe("Expand-Archive Tests") {
}
It "Writes a non-terminating error when a file in the archive has a destination path that already exists" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorVariable error
$error.Count | Should -Be 1
@@ -307,8 +262,8 @@ Describe("Expand-Archive Tests") {
}
It "Writes a non-terminating error when a file in the archive has a destination path that is an existing directory containing at least 1 item and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir4"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/ItemsToOverwriteContainer/subdir4"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 1
@@ -316,8 +271,8 @@ Describe("Expand-Archive Tests") {
}
It "Writes a non-terminating error when a file in the archive has a destination path that is the working directory and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/ParentDir"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/ParentDir"
Push-Location "$destinationPath/file1.txt"
@@ -331,52 +286,52 @@ Describe("Expand-Archive Tests") {
}
It "Overwrites a file when it is DestinationPath and -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/ItemsToOverwriteContainer/file2"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/ItemsToOverwriteContainer/file2"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive/ItemsToOverwriteContainer/file2/file1.txt" -PathType Leaf
+ Test-Path "TestDrive:/ItemsToOverwriteContainer/file2/file1.txt" -PathType Leaf
}
It "Overwrites a file whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" -Tag td {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir1"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/ItemsToOverwriteContainer/subdir1"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive/ItemsToOverwriteContainer/subdir1/file1.txt" -PathType Leaf
+ Test-Path "TestDrive:/ItemsToOverwriteContainer/subdir1/file1.txt" -PathType Leaf
# Ensure the contents of file1.txt is "Hello, World!"
- Get-Content -Path "$TestDrive/ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
+ Get-Content -Path "TestDrive:/ItemsToOverwriteContainer/subdir1/file1.txt" | Should -Be "Hello, World!"
}
It "Overwrites a directory whose path is the same as the destination path of a file in the archive when -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir2"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/ItemsToOverwriteContainer/subdir2"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive/ItemsToOverwriteContainer/subdir2/file1.txt" -PathType Leaf
+ Test-Path "TestDrive:/ItemsToOverwriteContainer/subdir2/file1.txt" -PathType Leaf
# Ensure the contents of file1.txt is "Hello, World!"
- Get-Content -Path "$TestDrive/ItemsToOverwriteContainer/subdir2/file1.txt" | Should -Be "Hello, World!"
+ Get-Content -Path "TestDrive:/ItemsToOverwriteContainer/subdir2/file1.txt" | Should -Be "Hello, World!"
}
It "Overwrites a file whose path is the same as the destination path of a directory in the archive when -WriteMode Overwrite is specified" {
- $sourcePath = "$TestDrive/archive2.zip"
- $destinationPath = "$TestDrive/ItemsToOverwriteContainer/subdir3"
+ $sourcePath = "TestDrive:/archive2.zip"
+ $destinationPath = "TestDrive:/ItemsToOverwriteContainer/subdir3"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite -ErrorVariable error
$error.Count | Should -Be 0
# Ensure the file in archive1.zip was expanded
- Test-Path "$TestDrive/ItemsToOverwriteContainer/subdir3/directory1" -PathType Container
+ Test-Path "TestDrive:/ItemsToOverwriteContainer/subdir3/directory1" -PathType Container
}
}
@@ -387,25 +342,26 @@ Describe("Expand-Archive Tests") {
# expand archive works when -DestinationPath is not specified (and there are mutiple top level items)
BeforeAll {
- New-Item -Path "$TestDrive/file1.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "$TestDrive/file1.txt"
- Compress-Archive -Path "$TestDrive/file1.txt" -DestinationPath "$TestDrive/archive1.zip"
+ New-Item -Path "TestDrive:/file1.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "TestDrive:/file1.txt"
+ Compress-Archive -Path "TestDrive:/file1.txt" -DestinationPath "TestDrive:/archive1.zip"
- New-Item -Path "$TestDrive/directory2" -ItemType Directory
- New-Item -Path "$TestDrive/directory3" -ItemType Directory
- New-Item -Path "$TestDrive/directory4" -ItemType Directory
- New-Item -Path "$TestDrive/directory5" -ItemType Directory
+ New-Item -Path "TestDrive:/directory2" -ItemType Directory
+ New-Item -Path "TestDrive:/directory3" -ItemType Directory
+ New-Item -Path "TestDrive:/directory4" -ItemType Directory
+ New-Item -Path "TestDrive:/directory5" -ItemType Directory
+ New-Item -Path "TestDrive:/directory6" -ItemType Directory
- New-Item -Path "$TestDrive/DirectoryToArchive" -ItemType Directory
- Compress-Archive -Path "$TestDrive/DirectoryToArchive" -DestinationPath "$TestDrive/archive2.zip"
+ New-Item -Path "TestDrive:/DirectoryToArchive" -ItemType Directory
+ Compress-Archive -Path "TestDrive:/DirectoryToArchive" -DestinationPath "TestDrive:/archive2.zip"
# Create an archive containing a file and an empty folder
- Compress-Archive -Path "$TestDrive/file1.txt","$TestDrive/DirectoryToArchive" -DestinationPath "$TestDrive/archive3.zip"
+ Compress-Archive -Path "TestDrive:/file1.txt","TestDrive:/DirectoryToArchive" -DestinationPath "TestDrive:/archive3.zip"
}
It "Expands an archive when a non-existent directory is specified as -DestinationPath" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/directory1"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/directory1"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
@@ -414,9 +370,9 @@ Describe("Expand-Archive Tests") {
$itemsInDestinationPath[0].Name | Should -Be "file1.txt"
}
- It "Expands an archive when DestinationPath is an existing directory" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/directory1"
+ It "Expands an archive when DestinationPath is an existing directory" -Tag debug3 {
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/directory2"
try {
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -ErrorAction Stop
@@ -426,8 +382,8 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive to the working directory when it is specified as -DestinationPath" {
- $sourcePath = "$TestDrive/archive1.zip"
- $destinationPath = "$TestDrive/directory2"
+ $sourcePath = "TestDrive:/archive1.zip"
+ $destinationPath = "TestDrive:/directory3"
Push-Location $destinationPath
@@ -440,26 +396,24 @@ Describe("Expand-Archive Tests") {
Pop-Location
}
- It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" -Tag this1{
- $sourcePath = "$TestDrive/archive2.zip"
- $destinationPath = "$TestDrive/directory3"
+ It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" {
+ $sourcePath = "TestDrive:/archive2.zip"
+ $destinationPath = "TestDrive:/directory4"
Push-Location $destinationPath
Expand-Archive -Path $sourcePath
- $itemsInDestinationPath = Get-ChildItem "$TestDrive/directory3" -Recurse
+ $itemsInDestinationPath = Get-ChildItem $destinationPath -Recurse
$itemsInDestinationPath.Count | Should -Be 1
$itemsInDestinationPath[0].Name | Should -Be "DirectoryToArchive"
- Test-Path -Path "$TestDrive/directory3/DirectoryToArchive" -PathType Container
-
Pop-Location
}
It "Expands an archive containing multiple top-level items to a directory with that archive's name when -DestinationPath is not specified" {
- $sourcePath = "$TestDrive/archive3.zip"
- $destinationPath = "$TestDrive/directory4"
+ $sourcePath = "TestDrive:/archive3.zip"
+ $destinationPath = "TestDrive:/directory5"
Push-Location $destinationPath
@@ -468,8 +422,8 @@ Describe("Expand-Archive Tests") {
$itemsInDestinationPath = Get-ChildItem $destinationPath -Name -Recurse
$itemsInDestinationPath.Count | Should -Be 3
"archive3" | Should -BeIn $itemsInDestinationPath
- "archive3${DS}DirectoryToArchive" | Should -BeIn $itemsInDestinationPath
- "archive3${DS}file1.txt" | Should -BeIn $itemsInDestinationPath
+ (Join-Path "archive3" "DirectoryToArchive") | Should -BeIn $itemsInDestinationPath
+ (Join-Path "archive3" "file1.txt") | Should -BeIn $itemsInDestinationPath
Pop-Location
@@ -478,32 +432,32 @@ Describe("Expand-Archive Tests") {
It "Expands an archive containing multiple files, non-empty directories, and empty directories" {
# Create an archive containing multiple files, non-empty directories, and empty directories
- New-Item -Path "$TestDrive/file2.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "$TestDrive/file2.txt"
- New-Item -Path "$TestDrive/file3.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "$TestDrive/file3.txt"
+ New-Item -Path "TestDrive:/file2.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "TestDrive:/file2.txt"
+ New-Item -Path "TestDrive:/file3.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "TestDrive:/file3.txt"
- New-Item -Path "$TestDrive/emptydirectory1" -ItemType Directory
- New-Item -Path "$TestDrive/emptydirectory2" -ItemType Directory
+ New-Item -Path "TestDrive:/emptydirectory1" -ItemType Directory
+ New-Item -Path "TestDrive:/emptydirectory2" -ItemType Directory
- New-Item -Path "$TestDrive/nonemptydirectory1" -ItemType Directory
- New-Item -Path "$TestDrive/nonemptydirectory2" -ItemType Directory
+ New-Item -Path "TestDrive:/nonemptydirectory1" -ItemType Directory
+ New-Item -Path "TestDrive:/nonemptydirectory2" -ItemType Directory
- New-Item -Path "$TestDrive/nonemptydirectory1/subfile1.txt" -ItemType File
- New-Item -Path "$TestDrive/nonemptydirectory2/subemptydirectory1" -ItemType Directory
+ New-Item -Path "TestDrive:/nonemptydirectory1/subfile1.txt" -ItemType File
+ New-Item -Path "TestDrive:/nonemptydirectory2/subemptydirectory1" -ItemType Directory
- $archive4Paths = @("$TestDrive/file2.txt", "$TestDrive/file3.txt", "$TestDrive/emptydirectory1", "$TestDrive/emptydirectory2", "$TestDrive/nonemptydirectory1", "$TestDrive/nonemptydirectory2")
+ $archive4Paths = @("TestDrive:/file2.txt", "TestDrive:/file3.txt", "TestDrive:/emptydirectory1", "TestDrive:/emptydirectory2", "TestDrive:/nonemptydirectory1", "TestDrive:/nonemptydirectory2")
- Compress-Archive -Path $archive4Paths -DestinationPath "$TestDrive/archive4.zip"
+ Compress-Archive -Path $archive4Paths -DestinationPath "TestDrive:/archive4.zip"
- $sourcePath = "$TestDrive/archive4.zip"
- $destinationPath = "$TestDrive/directory5"
+ $sourcePath = "TestDrive:/archive4.zip"
+ $destinationPath = "TestDrive:/directory6"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
$expandedItems = Get-ChildItem $destinationPath -Recurse -Name
- $itemsInArchive = @("file2.txt", "file3.txt", "emptydirectory1", "emptydirectory2", "nonemptydirectory1", "nonemptydirectory2", "nonemptydirectory1${DS}subfile1.txt", "nonemptydirectory2${DS}subemptydirectory1")
+ $itemsInArchive = @("file2.txt", "file3.txt", "emptydirectory1", "emptydirectory2", "nonemptydirectory1", "nonemptydirectory2", (Join-Path "nonemptydirectory1" "subfile1.txt"), (Join-Path "nonemptydirectory2" "subemptydirectory1"))
$expandedItems.Length | Should -Be $itemsInArchive.Count
foreach ($item in $itemsInArchive) {
@@ -512,15 +466,15 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive containing a file whose LastWriteTime is in the past" {
- New-Item -Path "$TestDrive/oldfile.txt" -ItemType File
- Set-ItemProperty -Path "$TestDrive/oldfile.txt" -Name "LastWriteTime" -Value '2003-01-16 14:44'
- Compress-Archive -Path "$TestDrive/oldfile.txt" -DestinationPath "$TestDrive/archive_oldfile.zip"
+ New-Item -Path "TestDrive:/oldfile.txt" -ItemType File
+ Set-ItemProperty -Path "TestDrive:/oldfile.txt" -Name "LastWriteTime" -Value '2003-01-16 14:44'
+ Compress-Archive -Path "TestDrive:/oldfile.txt" -DestinationPath "TestDrive:/archive_oldfile.zip"
- $sourcePath = "$TestDrive/archive_oldfile.zip"
- $destinationPath = "$TestDrive/destination6"
+ $sourcePath = "TestDrive:/archive_oldfile.zip"
+ $destinationPath = "TestDrive:/destination7"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
- $lastWriteTime = Get-ItemPropertyValue -Path "$TestDrive/oldfile.txt" -Name "LastWriteTime"
+ $lastWriteTime = Get-ItemPropertyValue -Path (Join-Path $destinationPath "oldfile.txt") -Name "LastWriteTime"
$lastWriteTime.Year | Should -Be 2003
$lastWriteTime.Month | Should -Be 1
@@ -532,15 +486,15 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive containing a directory whose LastWriteTime is in the past" {
- New-Item -Path "$TestDrive/olddirectory" -ItemType Directory
- Set-ItemProperty -Path "$TestDrive/olddirectory" -Name "LastWriteTime" -Value '2003-01-16 14:44'
- Compress-Archive -Path "$TestDrive/olddirectory" -DestinationPath "$TestDrive/archive_olddirectory.zip"
+ New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '2003-01-16 14:44'
+ Compress-Archive -Path "TestDrive:/olddirectory" -DestinationPath "TestDrive:/archive_olddirectory.zip"
- $sourcePath = "$TestDrive/archive_olddirectory.zip"
- $destinationPath = "$TestDrive/destination_olddirectory"
+ $sourcePath = "TestDrive:/archive_olddirectory.zip"
+ $destinationPath = "TestDrive:/destination_olddirectory"
Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
- $lastWriteTime = Get-ItemPropertyValue -Path "$TestDrive/destination_olddirectory/olddirectory" -Name "LastWriteTime"
+ $lastWriteTime = Get-ItemPropertyValue -Path "TestDrive:/destination_olddirectory/olddirectory" -Name "LastWriteTime"
$lastWriteTime.Year | Should -Be 2003
$lastWriteTime.Month | Should -Be 1
@@ -555,16 +509,16 @@ Describe("Expand-Archive Tests") {
Context "PassThru tests" {
BeforeAll {
New-Item -Path TestDrive:/file1.txt -ItemType File
- "Hello, World!" | Out-File -Path Test:/file1.txt
+ "Hello, World!" | Out-File -Path TestDrive:/file1.txt
$archivePath = "TestDrive:/archive.zip"
Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $archivePath
}
It "Returns a System.IO.DirectoryInfo object when PassThru is specified" {
- $destinationPath = "{TestDrive}/archive_contents"
+ $destinationPath = "TestDrive:/archive_contents"
$output = Expand-Archive -Path $archivePath -DestinationPath $destinationPath -PassThru
$output | Should -BeOfType System.IO.DirectoryInfo
- $output.FullName | SHould -Be $destinationPath
+ $output.FullName | Should -Be (Convert-Path $destinationPath)
}
It "Does not return an object when PassThru is not specified" {
@@ -573,10 +527,100 @@ Describe("Expand-Archive Tests") {
}
It "Does not return an object when PassThru is false" {
- $output = Compress-Archive -Path $archivePath -DestinationPath TestDrive:/archive_contents -PassThru:$false
+ $output = Expand-Archive -Path $archivePath -DestinationPath TestDrive:/archive_contents3 -PassThru:$false
$output | Should -BeNullOrEmpty
}
}
-
+ Context "Special and Wildcard Character Tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_containing_file.zip
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_with_number_1.zip
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_with_number_2.zip
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_with_[.zip
+ }
+
+ AfterAll {
+ Remove-Item -LiteralPath "TestDrive:/archive_with_[.zip"
+ }
+
+ It "Expands an archive when -Path contains wildcard character and resolves to 1 path" {
+ Expand-Archive -Path TestDrive:/archive_containing* -DestinationPath TestDrive:/destination1
+ (Convert-Path TestDrive:/destination1/file.txt) | Should -Exist
+ }
+
+ It "Throws a terminating error when archive when -Path contains wildcard character and resolves to multiple paths" {
+ try {
+ Expand-Archive -Path TestDrive:/archive_with* -DestinationPath TestDrive:/destination2
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "PathResolvedToMultiplePaths,$CmdletClassName"
+ }
+ }
+
+ It "Expands an archive when -LiteralPath contains [ but no matching ]" {
+ Expand-Archive -LiteralPath TestDrive:/archive_with_[.zip -DestinationPath TestDrive:/destination3
+ (Convert-Path TestDrive:/destination3/file.txt) | Should -Exist
+ }
+
+ It "Expands an archive when -DestinationPath contains [ but no matching ]" {
+ Expand-Archive -Path TestDrive:/archive_containing_file.zip -DestinationPath TestDrive:/destination[
+ Test-Path -LiteralPath TestDrive:/destination[/file.txt | Should -Be $true
+ Remove-Item -LiteralPath "${TestDrive}/destination[" -Recurse
+ }
+ }
+
+ Context "File permssions, attributes, etc tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+ # Create a readonly archive
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/readonly.zip
+ Set-ItemProperty -Path TestDrive:/readonly.zip -Name "IsReadOnly" -Value $true
+
+ # Create an archive in-use
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip
+ $fileMode = [System.IO.FileMode]::Open
+ $fileAccess = [System.IO.FileAccess]::Read
+ $fileShare = [System.IO.FileShare]::Read
+ $archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/archive_in_use.zip",$fileMode,$fileAccess,$fileShare
+
+ # Create an archive containing an entry with non-latin characters
+ New-Item TestDrive:/ملف -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/ملف
+ $archiveWithNonLatinEntryPath = Join-Path $TestDrive "archive_with_nonlatin_entry.zip"
+ if ($IsWindows) {
+ 7z.exe a $archiveWithNonLatinEntryPath (Join-Path $TestDrive ملف)
+ } else {
+ 7z a $archiveWithNonLatinEntryPath (Join-Path $TestDrive ملف)
+ }
+
+ }
+
+ AfterAll {
+ $archiveInUseStream.Dispose()
+ }
+
+ It "Expands a read-only archive" {
+ Expand-Archive -Path TestDrive:/readonly.zip -DestinationPath TestDrive:/readonly_output
+ "TestDrive:/readonly_output/file.txt" | Should -Exist
+ }
+
+ It "Expands an archive in-use" {
+ Expand-Archive -Path TestDrive:/archive_in_use.zip -DestinationPath TestDrive:/archive_in_use_output
+ "TestDrive:/archive_in_use_output/file.txt" | Should -Exist
+ }
+
+ It "Expands an archive containing an entry with non-latin characters" {
+ Expand-Archive -Path $archiveWithNonLatinEntryPath -DestinationPath TestDrive:/archive_with_nonlatin_entry_output
+ "TestDrive:/archive_with_nonlatin_entry_output/ملف" | Should -Exist
+ }
+ }
+
+ Context "Large File Tests" {
+
+ }
}
\ No newline at end of file
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 3b6a0e5..510bba6 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -94,7 +94,7 @@ protected override void EndProcessing()
DestinationPath = DetermineDestinationPath(archive);
} else {
// Resolve DestinationPath and validate it
- DestinationPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path: DestinationPath, pathMustExist: true);
+ DestinationPath = _pathHelper.GetUnresolvedPathFromPSProviderPath(path: DestinationPath, pathMustExist: false);
}
ValidateDestinationPath();
Debug.Assert(DestinationPath is not null);
@@ -129,6 +129,11 @@ protected override void EndProcessing()
// TODO: Change this later to write an error
throw unauthorizedAccessException;
}
+
+ // If PassThru is true, return a System.IO.DirectoryInfo object pointing to directory where archive expanded
+ if (PassThru) {
+ WriteObject(new DirectoryInfo(DestinationPath));
+ }
}
protected override void StopProcessing()
@@ -219,7 +224,7 @@ private void ValidateDestinationPath()
// Throw an error if DestinationPath exists and the cmdlet is not in Overwrite mode
if (File.Exists(DestinationPath) && WriteMode == ExpandArchiveWriteMode.Expand) {
- var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.CannotDetermineDestinationPath, errorItem: DestinationPath);
+ var errorRecord = ErrorMessages.GetErrorRecord(errorCode: ErrorCode.DestinationExists, errorItem: DestinationPath);
ThrowTerminatingError(errorRecord);
}
diff --git a/src/PathHelper.cs b/src/PathHelper.cs
index 9de80cb..9dbb9af 100644
--- a/src/PathHelper.cs
+++ b/src/PathHelper.cs
@@ -333,6 +333,7 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
// Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord
Exception? exception = null;
string? fullyQualifiedPath = null;
+ ErrorCode errorCode = ErrorCode.InvalidPath;
try
{
// Resolve path
@@ -347,8 +348,9 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
}
// If the path does not exist, create an exception
else if (pathMustExist && !Path.Exists(resolvedPath)) {
- var exceptionMsg = ErrorMessages.GetErrorMessage(ErrorCode.PathNotFound);
- exception = new ArgumentException(exceptionMsg);
+ errorCode = ErrorCode.PathNotFound;
+ var exceptionMsg = ErrorMessages.GetErrorMessage(errorCode);
+ throw new ItemNotFoundException(exceptionMsg);
}
else
{
@@ -374,12 +376,14 @@ private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string
catch (System.Management.Automation.PSInvalidOperationException invalidOperationException)
{
exception = invalidOperationException;
+ } catch (System.Management.Automation.ItemNotFoundException itemNotFoundException) {
+ exception = itemNotFoundException;
}
// If an exception was caught, write a non-terminating error
if (exception is not null)
{
- var errorRecord = new ErrorRecord(exception: exception, errorId: nameof(ErrorCode.InvalidPath), errorCategory: ErrorCategory.InvalidArgument,
+ var errorRecord = new ErrorRecord(exception: exception, errorId: errorCode.ToString(), errorCategory: ErrorCategory.InvalidArgument,
targetObject: path);
_cmdlet.ThrowTerminatingError(errorRecord);
}
From a0452f009a7b9a53061a64b01b4e5a74ebc9f961 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 9 Aug 2022 19:21:53 -0700
Subject: [PATCH 11/34] updated CI to run Expand-Archive tests, worked on tar
support
---
.azdevops/RunTests.ps1 | 2 +-
Tests/Compress-Archive.Tests.ps1 | 2 +-
src/ArchiveAddition.cs | 2 +-
src/IArchive.cs | 11 ----
src/IEntry.cs | 2 +-
src/TarArchive.cs | 99 +++++++++++++++++++++++++++-----
src/ZipArchive.cs | 25 +++-----
7 files changed, 97 insertions(+), 46 deletions(-)
diff --git a/.azdevops/RunTests.ps1 b/.azdevops/RunTests.ps1
index 0361d4f..f85198e 100644
--- a/.azdevops/RunTests.ps1
+++ b/.azdevops/RunTests.ps1
@@ -21,7 +21,7 @@ Import-Module -Name "Pester" -MinimumVersion $pesterMinVersion -MaximumVersion $
# Run tests
$OutputFile = "$PWD/build-unit-tests.xml"
$results = $null
-$results = Invoke-Pester -Script ./Tests/Compress-Archive.Tests.ps1 -OutputFile $OutputFile -PassThru -OutputFormat NUnitXml -Show Failed, Context, Describe, Fails
+$results = Invoke-Pester -Script ./Tests -OutputFile $OutputFile -PassThru -OutputFormat NUnitXml -Show Failed, Context, Describe, Fails
Write-Host "##vso[artifact.upload containerfolder=testResults;artifactname=testResults]$OutputFile"
if(!$results -or $results.FailedCount -gt 0 -or !$results.TotalCount)
{
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index dc3290f..5f1387b 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -234,7 +234,7 @@ BeforeDiscovery {
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
throw "Failed to detect an error when an invalid path is supplied to DestinationPath"
} catch {
- $_.FullyQualifiedErrorId | Should -Be "InvalidPath,$CmdletClassName"
+ $_.FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
}
diff --git a/src/ArchiveAddition.cs b/src/ArchiveAddition.cs
index d0e8337..2256488 100644
--- a/src/ArchiveAddition.cs
+++ b/src/ArchiveAddition.cs
@@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Archive
/// ArchiveAddition represents an filesystem entry that we want to add to or update in the archive.
/// ArchiveAddition DOES NOT represent an entry in the archive -- rather, it represents an entry to be created or updated using the information contained in an instance of this class.
///
- internal class ArchiveAddition
+ public class ArchiveAddition
{
///
/// The name of the file or directory in the archive.
diff --git a/src/IArchive.cs b/src/IArchive.cs
index a5d33be..c0e3b77 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -15,24 +15,13 @@ internal interface IArchive: IDisposable
// Get the fully qualified path of the archive
internal string Path { get; }
- // Number of entries
- internal int NumberOfEntries { get; }
-
// Add a file or folder to the archive. The entry name of the added item in the
// will be ArchiveEntry.Name.
// Throws an exception if the archive is in read mode.
internal void AddFileSystemEntry(ArchiveAddition entry);
- // Get the entries in the archive.
- // Throws an exception if the archive is in create mode.
- internal string[] GetEntries();
-
internal IEntry? GetNextEntry();
- // Expands an archive to a destination folder.
- // Throws an exception if the archive is not in read mode.
- internal void Expand(string destinationPath);
-
// Does the archive have only a top-level directory?
internal bool HasTopLevelDirectory();
}
diff --git a/src/IEntry.cs b/src/IEntry.cs
index 503e52b..13307b7 100644
--- a/src/IEntry.cs
+++ b/src/IEntry.cs
@@ -6,7 +6,7 @@
namespace Microsoft.PowerShell.Archive
{
- internal interface IEntry
+ public interface IEntry
{
public string Name { get; }
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index 070d6dd..9e7ef6a 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -17,16 +17,20 @@ internal class TarArchive : IArchive
private readonly string _path;
- private readonly TarWriter _tarWriter;
+ private TarWriter _tarWriter;
+
+ private TarReader? _tarReader;
private readonly FileStream _fileStream;
+ private FileStream _copyStream;
+
+ private string _copyPath;
+
ArchiveMode IArchive.Mode => _mode;
string IArchive.Path => _path;
- int IArchive.NumberOfEntries => throw new NotImplementedException();
-
public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
{
_mode = mode;
@@ -37,22 +41,49 @@ public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
void IArchive.AddFileSystemEntry(ArchiveAddition entry)
{
- _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entry.EntryName);
- }
-
- string[] IArchive.GetEntries()
- {
- throw new NotImplementedException();
+ if (_mode == ArchiveMode.Extract) {
+ throw new ArgumentException("Adding entries to the archive is not supported on Extract mode.");
+ }
+
+ // If the archive is in Update mode, we want to update the archive by copying it to a new archive
+ // and then adding the entries to that archive
+ if (_mode == ArchiveMode.Update) {
+
+ } else {
+ // If the archive mode is Create, no copy
+ _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entry.EntryName);
+ }
}
IEntry? IArchive.GetNextEntry()
{
- return null;
+ // If _tarReader is null, create it
+ if (_tarReader is null) {
+ _tarReader = new TarReader(archiveStream: _fileStream, leaveOpen: true);
+ }
+ var entry = _tarReader.GetNextEntry();
+ if (entry is null) {
+ return null;
+ }
+ // Create and return a TarArchiveEntry, which is a wrapper around entry
+ return new TarArchiveEntry(entry);
}
- void IArchive.Expand(string destinationPath)
- {
- throw new NotImplementedException();
+ private void CreateCopyStream() {
+ // Determine an appropritae and random filenname
+ string copyName = Path.GetRandomFileName();
+
+ // Directory of the copy will be the same as the directory of the archive
+ string directory = Path.GetDirectoryName(_path);
+
+ _copyPath = Path.Combine(directory, copyName);
+ _copyStream = new FileStream(_copyPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
+
+ // Create a tar reader that will read the contents of the archive
+ _tarReader = new TarReader(_fileStream, leaveOpen: false);
+
+ // Create a tar writer that will write the contents of the archive to the copy
+ _tarWriter = new TarWriter(_)
}
protected virtual void Dispose(bool disposing)
@@ -83,5 +114,47 @@ bool IArchive.HasTopLevelDirectory()
{
throw new NotImplementedException();
}
+
+ internal class TarArchiveEntry : IEntry {
+
+ // Underlying object is System.Formats.Tar.TarEntry
+ private TarEntry _entry;
+
+ private IEntry _objectAsIEntry;
+
+ string IEntry.Name => _entry.Name;
+
+ bool IEntry.IsDirectory => _entry.EntryType == TarEntryType.Directory;
+
+ public TarArchiveEntry(TarEntry entry)
+ {
+ _entry = entry;
+ _objectAsIEntry = this;
+ }
+
+ void IEntry.ExpandTo(string destinationPath)
+ {
+ // If the parent directory does not exist, create it
+ string? parentDirectory = Path.GetDirectoryName(destinationPath);
+ if (parentDirectory is not null && !Directory.Exists(parentDirectory))
+ {
+ Directory.CreateDirectory(parentDirectory);
+ }
+
+ if (_objectAsIEntry.IsDirectory)
+ {
+ System.IO.Directory.CreateDirectory(destinationPath);
+ var lastWriteTime = _entry.ModificationTime;
+ System.IO.Directory.SetLastWriteTime(destinationPath, lastWriteTime.DateTime);
+ } else
+ {
+ _entry.ExtractToFile(destinationPath, overwrite: false);
+ }
+ }
+
+ private void SetFileAttributes(string destinationPath) {
+
+ }
+ }
}
}
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index eae5539..e2a20e7 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -30,8 +30,6 @@ internal class ZipArchive : IArchive
string IArchive.Path => _archivePath;
- int IArchive.NumberOfEntries => _zipArchive.Entries.Count;
-
public ZipArchive(string archivePath, ArchiveMode mode, FileStream archiveStream, CompressionLevel compressionLevel)
{
_disposedValue = false;
@@ -100,11 +98,6 @@ void IArchive.AddFileSystemEntry(ArchiveAddition addition)
}
}
- string[] IArchive.GetEntries()
- {
- throw new NotImplementedException();
- }
-
IEntry? IArchive.GetNextEntry()
{
if (_entryIndex < 0)
@@ -125,11 +118,6 @@ string[] IArchive.GetEntries()
return new ZipArchiveEntry(nextEntry);
}
- void IArchive.Expand(string destinationPath)
- {
- throw new NotImplementedException();
- }
-
private static System.IO.Compression.ZipArchiveMode ConvertToZipArchiveMode(ArchiveMode archiveMode)
{
switch (archiveMode)
@@ -196,6 +184,13 @@ internal class ZipArchiveEntry : IEntry
void IEntry.ExpandTo(string destinationPath)
{
+ // If the parent directory does not exist, create it
+ string? parentDirectory = Path.GetDirectoryName(destinationPath);
+ if (parentDirectory is not null && !Directory.Exists(parentDirectory))
+ {
+ Directory.CreateDirectory(parentDirectory);
+ }
+
// .NET APIs differentiate a file and directory by a terminating `/`
// If the entry name ends with `/`, it is a directory
if (_entry.FullName.EndsWith(System.IO.Path.AltDirectorySeparatorChar))
@@ -205,12 +200,6 @@ void IEntry.ExpandTo(string destinationPath)
System.IO.Directory.SetLastWriteTime(destinationPath, lastWriteTime.DateTime);
} else
{
- // If the parent directory does not exist, create it
- string? parentDirectory = Path.GetDirectoryName(destinationPath);
- if (parentDirectory is not null && !Directory.Exists(parentDirectory))
- {
- Directory.CreateDirectory(parentDirectory);
- }
_entry.ExtractToFile(destinationPath);
}
}
From cca70eda21a50454994fdc6c99d257b66cdf843a Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 9 Aug 2022 19:38:40 -0700
Subject: [PATCH 12/34] fixed bug where percent complete was not updating
---
src/CompressArchiveCommand.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/CompressArchiveCommand.cs b/src/CompressArchiveCommand.cs
index e76aea5..74f6f58 100644
--- a/src/CompressArchiveCommand.cs
+++ b/src/CompressArchiveCommand.cs
@@ -199,7 +199,8 @@ protected override void EndProcessing()
{
// Update progress
var percentComplete = numberOfAddedItems / (float)numberOfAdditions * 100f;
- progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, "{percentComplete:0.0}");
+ progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, $"{percentComplete:0.0}");
+ progressRecord.PercentComplete = (int)percentComplete;
WriteProgress(progressRecord);
if (ShouldProcess(target: entry.FileSystemInfo.FullName, action: Messages.Add))
@@ -221,6 +222,7 @@ protected override void EndProcessing()
// Once all items in the archive are processed, show progress as 100%
// This code is here and not in the loop because we want it to run even if there are no items to add to the archive
progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, "100.0");
+ progressRecord.PercentComplete = 100;
WriteProgress(progressRecord);
}
finally
From 80917c789fe5415729140927c44372e6e2ab3990 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 12:11:58 -0700
Subject: [PATCH 13/34] added progress info in Expand-Archive, addded changelog
---
CHANGELOG.md | 19 +++++++++++++++++++
src/CompressArchiveCommand.cs | 1 +
src/ExpandArchiveCommand.cs | 7 +++++++
src/Localized/Messages.resx | 6 ++++++
src/Microsoft.PowerShell.Archive.psd1 | 16 +++++++++++++++-
src/TarArchive.cs | 2 +-
6 files changed, 49 insertions(+), 2 deletions(-)
create mode 100644 CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..2decfb6
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,19 @@
+# Changelog
+
+## 2.0.1-preview2
+
+- Rewrite `Expand-Archive` cmdlet in C#
+- Added `-Format` parameter to `Expand-Archive`
+- Added `-WriteMode` parameter to `Expand-Archive`
+- Added support for zip64
+- Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
+
+## 2.0.1-preview1
+
+- Rewrite `Compress-Archive` cmdlet in C#
+- Added `-Format` parameter to `Compress-Archive`
+- Added `-WriteMode` parameter to `Compress-Archive`
+- Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
+- Added support for zip64
+- Fixed a bug where empty directories would not be compressed
+- Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
diff --git a/src/CompressArchiveCommand.cs b/src/CompressArchiveCommand.cs
index 74f6f58..9d040ed 100644
--- a/src/CompressArchiveCommand.cs
+++ b/src/CompressArchiveCommand.cs
@@ -199,6 +199,7 @@ protected override void EndProcessing()
{
// Update progress
var percentComplete = numberOfAddedItems / (float)numberOfAdditions * 100f;
+
progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, $"{percentComplete:0.0}");
progressRecord.PercentComplete = (int)percentComplete;
WriteProgress(progressRecord);
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 510bba6..9664030 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -115,10 +115,13 @@ protected override void EndProcessing()
System.IO.Directory.CreateDirectory(DestinationPath);
}
+ WriteObject(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
+
// Get the next entry in the archive and process it
var nextEntry = archive.GetNextEntry();
while (nextEntry != null)
{
+ // The process function will write the progress
ProcessArchiveEntry(nextEntry);
nextEntry = archive.GetNextEntry();
}
@@ -156,6 +159,10 @@ private void ProcessArchiveEntry(IEntry entry)
postExpandPath = postExpandPath.Remove(postExpandPath.Length - 1);
}
+ // Notify the user that we are expanding the entry
+ var expandingEntryMsg = string.Format(Messages.ExpandingEntryMessage, entry.Name, postExpandPath);
+ WriteObject(expandingEntryMsg);
+
// If the entry name is invalid, write a non-terminating error and stop processing the entry
if (IsPathInvalid(postExpandPath))
{
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index 7d28961..24471bc 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -194,4 +194,10 @@
The last write time of the path {0} is before 1980. Since zip does not support dates before January 1, 1980, the last write time will be set to January 1, 1980.
+
+ Expanding archive {0}
+
+
+ Expanding archive entry {0} to destination {1}
+
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1
index 6dd9f7d..c11f5b0 100644
--- a/src/Microsoft.PowerShell.Archive.psd1
+++ b/src/Microsoft.PowerShell.Archive.psd1
@@ -12,9 +12,23 @@ PrivateData = @{
PSData = @{
Tags = @('Archive', 'Zip', 'Compress')
ProjectUri = 'https://github.com/PowerShell/Microsoft.PowerShell.Archive'
+ LicenseUri = 'https://go.microsoft.com/fwlink/?linkid=2203619'
ReleaseNotes = @'
+ ## 2.0.1-preview2
+ - Rewrite `Expand-Archive` cmdlet in C#
+ - Added `-Format` parameter to `Expand-Archive`
+ - Added `-WriteMode` parameter to `Expand-Archive`
+ - Added support for zip64
+ - Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
+
## 2.0.1-preview1
- - Rewrote Compress-Archive cmdlet in C#
+ - Rewrite `Compress-Archive` cmdlet in C#
+ - Added `-Format` parameter to `Compress-Archive`
+ - Added `-WriteMode` parameter to `Compress-Archive`
+ - Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
+ - Added support for zip64
+ - Fixed a bug where empty directories would not be compressed
+ - Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
'@
Prerelease = 'preview1'
}
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index 9e7ef6a..9601591 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -83,7 +83,7 @@ private void CreateCopyStream() {
_tarReader = new TarReader(_fileStream, leaveOpen: false);
// Create a tar writer that will write the contents of the archive to the copy
- _tarWriter = new TarWriter(_)
+ //_tarWriter = new TarWriter(_)
}
protected virtual void Dispose(bool disposing)
From 0210c8d44adf392b35a48e4b7d9179eb97dbd7e0 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 12:20:33 -0700
Subject: [PATCH 14/34] fixed failing tests
---
src/ExpandArchiveCommand.cs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 9664030..5419ee4 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -115,7 +115,7 @@ protected override void EndProcessing()
System.IO.Directory.CreateDirectory(DestinationPath);
}
- WriteObject(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
+ //WriteObject(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
// Get the next entry in the archive and process it
var nextEntry = archive.GetNextEntry();
@@ -160,8 +160,8 @@ private void ProcessArchiveEntry(IEntry entry)
}
// Notify the user that we are expanding the entry
- var expandingEntryMsg = string.Format(Messages.ExpandingEntryMessage, entry.Name, postExpandPath);
- WriteObject(expandingEntryMsg);
+ //var expandingEntryMsg = string.Format(Messages.ExpandingEntryMessage, entry.Name, postExpandPath);
+ //WriteObject(expandingEntryMsg);
// If the entry name is invalid, write a non-terminating error and stop processing the entry
if (IsPathInvalid(postExpandPath))
From ab31d3e67f8752ff0bb1fc87f086cbed2f286bb8 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 12:44:09 -0700
Subject: [PATCH 15/34] fixed a bug where removing the extension on Unix
platforms would append a . to the path, added progress bar for zip archive
---
src/ExpandArchiveCommand.cs | 34 ++++++++++++++++++++++++++++++++--
src/ZipArchive.cs | 2 ++
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index 5419ee4..ec4f4c7 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -85,7 +85,7 @@ protected override void EndProcessing()
try
{
// Get an archive from source path -- this is where we will switch between different types of archives
- using IArchive? archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: _sourcePath, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
+ using IArchive archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: _sourcePath, archiveMode: ArchiveMode.Extract, compressionLevel: System.IO.Compression.CompressionLevel.NoCompression);
if (DestinationPath is null)
{
@@ -115,6 +115,16 @@ protected override void EndProcessing()
System.IO.Directory.CreateDirectory(DestinationPath);
}
+ long numberOfExpandedItems = 0;
+
+ // Show a progress bar
+ if (Format == ArchiveFormat.Zip && archive is ZipArchive) {
+ var statusDescription = string.Format(Messages.ProgressDisplay, "0.0");
+ var progressRecord = new ProgressRecord(1, "Expand-Archive", statusDescription);
+ progressRecord.PercentComplete = 0;
+ WriteProgress(progressRecord);
+ }
+
//WriteObject(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
// Get the next entry in the archive and process it
@@ -124,6 +134,25 @@ protected override void EndProcessing()
// The process function will write the progress
ProcessArchiveEntry(nextEntry);
nextEntry = archive.GetNextEntry();
+
+ // Update progress info
+ numberOfExpandedItems++;
+ if (Format == ArchiveFormat.Zip && archive is not null && archive is ZipArchive zipArchive) {
+ var percentComplete = numberOfExpandedItems / (float)zipArchive.NumberOfEntries * 100f;
+ var statusDescription = string.Format(Messages.ProgressDisplay, $"{percentComplete:0.0}");
+ var progressRecord = new ProgressRecord(1, "Expand-Archive", statusDescription);
+ progressRecord.PercentComplete = (int)percentComplete;
+ WriteProgress(progressRecord);
+ }
+ }
+
+ // Show progress as 100% complete
+ // Show a progress bar
+ if (Format == ArchiveFormat.Zip && archive is ZipArchive) {
+ var statusDescription = string.Format(Messages.ProgressDisplay, "100.0");
+ var progressRecord = new ProgressRecord(1, "Expand-Archive", statusDescription);
+ progressRecord.PercentComplete = 100;
+ WriteProgress(progressRecord);
}
@@ -315,7 +344,8 @@ private string DetermineDestinationPath(IArchive archive)
// If filename does have an exension, remove the extension and set the filename minus extension as destinationDirectory
if (System.IO.Path.GetExtension(filename) != string.Empty)
{
- destinationDirectory = System.IO.Path.ChangeExtension(path: filename, extension: string.Empty);
+ int indexOfLastPeriod = filename.LastIndexOf('.');
+ destinationDirectory = filename.Substring(0, indexOfLastPeriod);
}
}
diff --git a/src/ZipArchive.cs b/src/ZipArchive.cs
index e2a20e7..3954831 100644
--- a/src/ZipArchive.cs
+++ b/src/ZipArchive.cs
@@ -30,6 +30,8 @@ internal class ZipArchive : IArchive
string IArchive.Path => _archivePath;
+ internal int NumberOfEntries => _zipArchive.Entries.Count;
+
public ZipArchive(string archivePath, ArchiveMode mode, FileStream archiveStream, CompressionLevel compressionLevel)
{
_disposedValue = false;
From bb24f0e83e8bcd747138575802d8e352d4d29fdc Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 13:01:24 -0700
Subject: [PATCH 16/34] updated release build pipeline
---
.azdevops/ReleaseBuildPipeline.yml | 2 +-
src/ExpandArchiveCommand.cs | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.azdevops/ReleaseBuildPipeline.yml b/.azdevops/ReleaseBuildPipeline.yml
index cf05994..c8bf600 100644
--- a/.azdevops/ReleaseBuildPipeline.yml
+++ b/.azdevops/ReleaseBuildPipeline.yml
@@ -38,7 +38,7 @@ stages:
includePreviewVersions: true
- pwsh: |
- & $(Build.SourcesDirectory)/Microsoft.PowerShell.Archive/SimpleBuild.ps1
+ & $(Build.SourcesDirectory)/Microsoft.PowerShell.Archive/Build.ps1
displayName: Build Microsoft.PowerShell.Archive module
- pwsh: |
diff --git a/src/ExpandArchiveCommand.cs b/src/ExpandArchiveCommand.cs
index ec4f4c7..cc41587 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/ExpandArchiveCommand.cs
@@ -125,7 +125,8 @@ protected override void EndProcessing()
WriteProgress(progressRecord);
}
- //WriteObject(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
+ // Write a verbose message saying "Expanding archive ..."
+ WriteVerbose(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
// Get the next entry in the archive and process it
var nextEntry = archive.GetNextEntry();
From 28c4754f25cd7c87940e8b07556192d015b5afc3 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 13:26:30 -0700
Subject: [PATCH 17/34] added test for seeing if a file can be added to an
archive while it is in use, updated prelease version, added exception
handling when adding archive entries
---
CHANGELOG.md | 1 +
Tests/Compress-Archive.Tests.ps1 | 21 ++++++++++++++++
src/CompressArchiveCommand.cs | 36 +++++++++++++++++++++++----
src/ErrorMessages.cs | 4 ++-
src/Microsoft.PowerShell.Archive.psd1 | 3 ++-
5 files changed, 58 insertions(+), 7 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2decfb6..6590597 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
- Added `-WriteMode` parameter to `Expand-Archive`
- Added support for zip64
- Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
+- `Compress-Archive` skips writing an entry to an archive if an error occurs while doing so
## 2.0.1-preview1
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 5f1387b..844041e 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -776,4 +776,25 @@ BeforeDiscovery {
$output | Should -BeNullOrEmpty
}
}
+
+ Context "File permissions, attributes, etc. tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+ }
+
+
+ It "Skips archiving a file in use" {
+ $fileMode = [System.IO.FileMode]::Open
+ $fileAccess = [System.IO.FileAccess]::Write
+ $fileShare = [System.IO.FileShare]::None
+ $archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/file.txt",$fileMode,$fileAccess,$fileShare
+
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip
+ # Ensure it creates an empty zip archive
+ "TestDrive:/archive_in_use.zip" | Should -BeZipArchiveOnlyContaining @()
+
+ $archiveInUseStream.Dispose()
+ }
+ }
}
diff --git a/src/CompressArchiveCommand.cs b/src/CompressArchiveCommand.cs
index 9d040ed..dea909f 100644
--- a/src/CompressArchiveCommand.cs
+++ b/src/CompressArchiveCommand.cs
@@ -207,14 +207,40 @@ protected override void EndProcessing()
if (ShouldProcess(target: entry.FileSystemInfo.FullName, action: Messages.Add))
{
// Warn the user if the LastWriteTime of the file/directory is before 1980
- if (entry.FileSystemInfo.LastWriteTime.Year < 1980 && Format == ArchiveFormat.Zip) {
+ if (entry.FileSystemInfo.LastWriteTime.Year < 1980 && Format == ArchiveFormat.Zip)
+ {
WriteWarning(string.Format(Messages.LastWriteTimeBefore1980Warning, entry.FileSystemInfo.FullName));
}
- archive?.AddFileSystemEntry(entry);
- // Write a verbose message saying this item was added to the archive
- var addedItemMessage = string.Format(Messages.AddedItemToArchiveVerboseMessage, entry.FileSystemInfo.FullName);
- WriteVerbose(addedItemMessage);
+ // Use this to track of an exception that occurs when adding an entry to the archive
+ // so a non-terminating error can be reported
+ Exception? exception = null;
+ try
+ {
+ archive?.AddFileSystemEntry(entry);
+ // Write a verbose message saying this item was added to the archive
+ var addedItemMessage = string.Format(Messages.AddedItemToArchiveVerboseMessage, entry.FileSystemInfo.FullName);
+ WriteVerbose(addedItemMessage);
+ }
+ // This catches PathTooLongException as well
+ catch (IOException ioException)
+ {
+ exception = ioException;
+ }
+ catch (UnauthorizedAccessException unauthorizedAccessException)
+ {
+ exception = unauthorizedAccessException;
+ }
+ catch (System.NotSupportedException notSupportedException)
+ {
+ exception = notSupportedException;
+ }
+
+ if (exception is not null)
+ {
+ var errorRecord = new ErrorRecord(exception, nameof(ErrorCode.ExceptionOccuredWhileAddingEntry), ErrorCategory.InvalidOperation, entry.EntryName);
+ WriteError(errorRecord);
+ }
}
// Keep track of number of items added to the archive
numberOfAddedItems++;
diff --git a/src/ErrorMessages.cs b/src/ErrorMessages.cs
index 810141e..d79ab66 100644
--- a/src/ErrorMessages.cs
+++ b/src/ErrorMessages.cs
@@ -79,6 +79,8 @@ internal enum ErrorCode
// Expand-Archive: used when a path resolved to multiple paths when only one was needed
PathResolvedToMultiplePaths,
// Expand-Archive: used when the DestinationPath could not be determined
- CannotDetermineDestinationPath
+ CannotDetermineDestinationPath,
+ // Compress-Archive: Used when an exception occurs when adding an entry to an archive
+ ExceptionOccuredWhileAddingEntry
}
}
diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1
index c11f5b0..0861e0e 100644
--- a/src/Microsoft.PowerShell.Archive.psd1
+++ b/src/Microsoft.PowerShell.Archive.psd1
@@ -20,6 +20,7 @@ PrivateData = @{
- Added `-WriteMode` parameter to `Expand-Archive`
- Added support for zip64
- Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
+ - `Compress-Archive` skips writing an entry to an archive if an error occurs while doing so
## 2.0.1-preview1
- Rewrite `Compress-Archive` cmdlet in C#
@@ -30,6 +31,6 @@ PrivateData = @{
- Fixed a bug where empty directories would not be compressed
- Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
'@
- Prerelease = 'preview1'
+ Prerelease = 'preview2'
}
}
\ No newline at end of file
From 13e65509dfea6393fea116418937ede1abcc626e Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 14:45:59 -0700
Subject: [PATCH 18/34] updated sign and package script to use prelease version
from manifest
---
.azdevops/SignAndPackageModule.ps1 | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.azdevops/SignAndPackageModule.ps1 b/.azdevops/SignAndPackageModule.ps1
index 06427ac..a57be44 100644
--- a/.azdevops/SignAndPackageModule.ps1
+++ b/.azdevops/SignAndPackageModule.ps1
@@ -12,6 +12,7 @@ $BuildOutputDir = Join-Path $root "\src\bin\Release"
$ManifestPath = "${BuildOutputDir}\${Name}.psd1"
$ManifestData = Import-PowerShellDataFile -Path $ManifestPath
$Version = $ManifestData.ModuleVersion
+$Prelease = $ManifestPath.PrivateData.PSData.Prerelease
# this takes the files for the module and publishes them to a created, local repository
# so the nupkg can be used to publish to the PSGallery
@@ -31,7 +32,9 @@ function Export-Module
Publish-Module -Path $packageRoot -Repository $repoName
Unregister-PSRepository -Name $repoName
Get-ChildItem -Recurse -Name $packageRoot | Write-Verbose
- $nupkgName = "{0}.{1}-preview1.nupkg" -f ${Name},${Version}
+ $nupkgName = "{0}.{1}-{2}.nupkg" -f ${Name},${Version},${Prerelease}
+
+
$nupkgPath = Join-Path $packageRoot $nupkgName
if ($env:TF_BUILD) {
# In Azure DevOps
From 538bd726fb0bd106eafe907f8a1dee590b67c3fb Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 15:02:08 -0700
Subject: [PATCH 19/34] fixed typo in sign package script
---
.azdevops/SignAndPackageModule.ps1 | 2 +-
Tests/Compress-Archive.Tests.ps1 | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.azdevops/SignAndPackageModule.ps1 b/.azdevops/SignAndPackageModule.ps1
index a57be44..ed9561c 100644
--- a/.azdevops/SignAndPackageModule.ps1
+++ b/.azdevops/SignAndPackageModule.ps1
@@ -12,7 +12,7 @@ $BuildOutputDir = Join-Path $root "\src\bin\Release"
$ManifestPath = "${BuildOutputDir}\${Name}.psd1"
$ManifestData = Import-PowerShellDataFile -Path $ManifestPath
$Version = $ManifestData.ModuleVersion
-$Prelease = $ManifestPath.PrivateData.PSData.Prerelease
+$Prerelease = $ManifestPath.PrivateData.PSData.Prerelease
# this takes the files for the module and publishes them to a created, local repository
# so the nupkg can be used to publish to the PSGallery
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 844041e..1e4a62e 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -790,7 +790,7 @@ BeforeDiscovery {
$fileShare = [System.IO.FileShare]::None
$archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/file.txt",$fileMode,$fileAccess,$fileShare
- Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip -ErrorAction SilentlyContinue
# Ensure it creates an empty zip archive
"TestDrive:/archive_in_use.zip" | Should -BeZipArchiveOnlyContaining @()
From fea4d2e95dda443fb04ed634bd0ae4c035d5b109 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 15:20:10 -0700
Subject: [PATCH 20/34] added test, fixed an error in sign and package script
---
.azdevops/SignAndPackageModule.ps1 | 4 +--
Tests/Compress-Archive.Tests.ps1 | 50 ++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 2 deletions(-)
diff --git a/.azdevops/SignAndPackageModule.ps1 b/.azdevops/SignAndPackageModule.ps1
index ed9561c..5d0db53 100644
--- a/.azdevops/SignAndPackageModule.ps1
+++ b/.azdevops/SignAndPackageModule.ps1
@@ -12,7 +12,7 @@ $BuildOutputDir = Join-Path $root "\src\bin\Release"
$ManifestPath = "${BuildOutputDir}\${Name}.psd1"
$ManifestData = Import-PowerShellDataFile -Path $ManifestPath
$Version = $ManifestData.ModuleVersion
-$Prerelease = $ManifestPath.PrivateData.PSData.Prerelease
+#$Prerelease = $ManifestPath.PrivateData.PSData.Prerelease
# this takes the files for the module and publishes them to a created, local repository
# so the nupkg can be used to publish to the PSGallery
@@ -32,7 +32,7 @@ function Export-Module
Publish-Module -Path $packageRoot -Repository $repoName
Unregister-PSRepository -Name $repoName
Get-ChildItem -Recurse -Name $packageRoot | Write-Verbose
- $nupkgName = "{0}.{1}-{2}.nupkg" -f ${Name},${Version},${Prerelease}
+ $nupkgName = "{0}.{1}-preview2.nupkg" -f ${Name},${Version}
$nupkgPath = Join-Path $packageRoot $nupkgName
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 1e4a62e..8efdfb8 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -797,4 +797,54 @@ BeforeDiscovery {
$archiveInUseStream.Dispose()
}
}
+
+ Context "Long path tests" {
+ BeforeAll {
+ if ($IsWindows) {
+ $maxPathLength = 260
+ }
+ if ($IsLinux) {
+ $maxPathLength = 255
+ }
+ if ($IsMacOS) {
+ $maxPathLength = 1024
+ }
+
+ function Get-MaxLengthPath {
+ param (
+ [string] $character
+ )
+
+ $path = "${TestDrive}/"
+ while ($path.Length -le $maxPathLength + 2) {
+ $path += $character
+ }
+ return $path
+ }
+
+ New-Item -Path "TestDrive:/file.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "TestDrive:/file.txt"
+ }
+
+
+ It "Throws an error when -Path is too long" {
+
+ }
+
+ It "Throws an error when -LiteralPath is too long" {
+
+ }
+
+ It "Throws an error when -DestinationPath is too long" {
+ $path = "TestDrive:/file.txt"
+ # This will generate a path like TestDrive:/aaaaaa...aaaaaa
+ $destinationPath = Get-MaxLengthPath -character a
+ Write-Warning $destinationPath.Length
+ try {
+ Compress-Archive -Path $path -DestinationPath $destinationPath
+ } catch {
+ throw "${$_.Exception}"
+ }
+ }
+ }
}
From 2e977d1e35744d6a827b9b977c477736fbc80853 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 10 Aug 2022 17:28:09 -0700
Subject: [PATCH 21/34] worked on tar support and added test case and assertion
for tar
---
.../Should-BeArchiveOnlyContaining.psm1 | 34 ++++
.../Should-BeTarArchiveOnlyContaining.psm1 | 148 +++++++++++++++
Tests/Compress-Archive.Tests.ps1 | 171 +++++++++---------
src/ArchiveFactory.cs | 7 +-
src/ArchiveFormat.cs | 3 +-
src/TarArchive.cs | 66 +++++--
6 files changed, 324 insertions(+), 105 deletions(-)
create mode 100644 Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
create mode 100644 Tests/Assertions/Should-BeTarArchiveOnlyContaining.psm1
diff --git a/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1 b/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
new file mode 100644
index 0000000..6df4399
--- /dev/null
+++ b/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
@@ -0,0 +1,34 @@
+function Should-BeArchiveOnlyContaining {
+ <#
+ .SYNOPSIS
+ Checks if a zip archive contains the entries $ExpectedValue
+ .EXAMPLE
+ "C:\Users\\archive.zip" | Should -BeZipArchiveContaining @("file1.txt")
+
+ Checks if archive.zip only contains file1.txt
+ #>
+
+ [CmdletBinding()]
+ Param (
+ [string] $ActualValue,
+ [string[]] $ExpectedValue,
+ [switch] $Negate,
+ [string] $Because,
+ [switch] $LiteralPath,
+ $CallerSessionState,
+ [string] $Format
+ )
+
+ if ($Format -eq "Zip") {
+ return Should-BeZipArchiveOnlyContaining -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$Negate -Because $Because -LiteralPath:$LiteralPath -CallerSessionState $CallerSessionState
+ }
+ if ($Format -eq "Tar") {
+ return Should-BeTarArchiveOnlyContaining -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$Negate -Because $Because -LiteralPath:$LiteralPath -CallerSessionState $CallerSessionState
+ }
+ return return [pscustomobject]@{
+ Succeeded = $false
+ FailureMessage = "Format ${Format} is not supported."
+ }
+
+}
+Add-ShouldOperator -Name BeArchiveOnlyContaining -InternalName 'Should-BeArchiveOnlyContaining' -Test ${function:Should-BeArchiveOnlyContaining}
\ No newline at end of file
diff --git a/Tests/Assertions/Should-BeTarArchiveOnlyContaining.psm1 b/Tests/Assertions/Should-BeTarArchiveOnlyContaining.psm1
new file mode 100644
index 0000000..5c7fc91
--- /dev/null
+++ b/Tests/Assertions/Should-BeTarArchiveOnlyContaining.psm1
@@ -0,0 +1,148 @@
+function Should-BeTarArchiveOnlyContaining {
+ <#
+ .SYNOPSIS
+ Checks if a tar archive contains the entries $ExpectedValue
+ .EXAMPLE
+ "C:\Users\\archive.zip" | Should -BeZipArchiveContaining @("file1.txt")
+
+ Checks if archive.zip only contains file1.txt
+ #>
+
+ [CmdletBinding()]
+ Param (
+ [string] $ActualValue,
+ [string[]] $ExpectedValue,
+ [switch] $Negate,
+ [string] $Because,
+ [switch] $LiteralPath,
+ $CallerSessionState
+ )
+
+ # ActualValue is supposed to be a path to an archive
+ # It could be a path to a custom PSDrive, so it needes to be converted
+ if ($LiteralPath) {
+ $ActualValue = Convert-Path -LiteralPath $ActualValue
+ } else {
+ $ActualValue = Convert-Path -Path $ActualValue
+ }
+
+
+ # Ensure ActualValue is a valid path
+ if ($LiteralPath) {
+ $testPathResult = Test-Path -LiteralPath $ActualValue
+ } else {
+ $testPathResult = Test-Path -Path $ActualValue
+ }
+
+ # Don't continue processing if ActualValue is not an actual path
+ # Determine if the assertion succeeded or failed and then return
+ if (-not $testPathResult) {
+ $succeeded = $Negate
+ if (-not $succeeded) {
+ $failureMessage = "The path ${ActualValue} does not exist"
+ }
+ return [pscustomobject]@{
+ Succeeded = $succeeded
+ FailureMessage = $failureMessage
+ }
+ }
+
+ # Get 7-zip to list the contents of the archive
+ if ($IsWindows) {
+ $output = 7z.exe l $ActualValue -ba -ttar
+ } else {
+ $output = 7z l $ActualValue -ba -ttar
+ }
+
+ # Check if the output is null
+ if ($null -eq $output) {
+ if ($null -eq $ExpectedValue -or $ExpectedValue.Length -eq 0) {
+ $succeeded = -not $Negate
+ } else {
+ $succeeded = $Negate
+ }
+
+ if (-not $succeeded) {
+ $failureMessage = "Archive {0} contains nothing, but it was expected to contain something"
+ }
+
+ return [pscustomobject]@{
+ Succeeded = $succeeded
+ FailureMessage = $failureMessage
+ }
+ }
+
+ # Filter the output line by line
+ $lines = $output -split [System.Environment]::NewLine
+
+ # Stores the entry names
+ $entryNames = @()
+
+ # Go through each line and split it by whitespace
+ foreach ($line in $lines) {
+ $lineComponents = $line -split " +"
+
+ # Example of some lines:
+ #2022-08-05 15:54:04 D.... 0 0 SourceDir
+ #2022-08-05 15:54:04 ..... 11 11 SourceDir/Sample-1.txt
+
+ # First component is date
+ # 2nd component is time
+ # 3rd componnent is attributes
+ # 4th component is size
+ # 5th component is compressed size
+ # 6th component is entry name
+
+ $entryName = $lineComponents[$lineComponents.Length - 1]
+
+ # Since 7zip does not show trailing forwardslash for directories, we need to check the attributes to see if it starts with 'D'
+ # If so, it means the entry is a directory and we should append a forwardslash to the entry name
+
+ if ($lineComponents[2].StartsWith('D')) {
+ $entryName += '/'
+ }
+
+ # Replace backslashes to forwardslashes
+ $dirSeperatorChar = [System.IO.Path]::DirectorySeparatorChar
+ $entryName = $entryName.Replace($dirSeperatorChar, "/")
+
+ $entryNames += $entryName
+ }
+
+ $itemsNotInArchive = @()
+
+ # Go through each item in ExpectedValue and ensure it is in entryNames
+ foreach ($expectedItem in $ExpectedValue) {
+ if ($entryNames -notcontains $expectedItem) {
+ $itemsNotInArchive += $expectedItem
+ }
+ }
+
+ if ($itemsNotInArchive.Length -gt 0 -and -not $Negate) {
+ # Create a comma-seperated string from $itemsNotInEnryName
+ $commaSeperatedItemsNotInArchive = $itemsNotInArchive -join ","
+ $failureMessage = "'$ActualValue' does not contain $commaSeperatedItemsNotInArchive $(if($Because) { "because $Because"})."
+ $succeeded = $false
+ }
+
+ # Ensure the length of $entryNames is equal to that of $ExpectedValue
+ if ($null -eq $succeeded -and $entryNames.Length -ne $ExpectedValue.Length -and -not $Negate) {
+ $failureMessage = "${ActualValue} does not contain the same number of items as ${ExpectedValue -join ""} (expected ${ExpectedValue.Length} entries but found ${entryNames.Length}) $(if($Because) { "because $Because"})."
+ $succeeded = $false
+ }
+
+ if ($null -eq $succeeded) {
+ $succeeded = -not $Negate
+ if (-not $succeeded) {
+ $failureMessage = "Expected ${ActualValue} to not contain the entries ${ExpectedValue -join ""} only $(if($Because) { "because $Because"})."
+ }
+ }
+
+ $ObjProperties = @{
+ Succeeded = $succeeded
+ FailureMessage = $failureMessage
+ }
+ return New-Object PSObject -Property $ObjProperties
+}
+
+Add-ShouldOperator -Name BeTarArchiveOnlyContaining -InternalName 'Should-BeTarArchiveOnlyContaining' -Test ${function:Should-BeTarArchiveOnlyContaining}
\ No newline at end of file
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 8efdfb8..257b3ac 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -4,6 +4,8 @@
BeforeDiscovery {
# Loads and registers custom assertion. Ignores usage of unapproved verb with -DisableNameChecking
Import-Module "$PSScriptRoot/Assertions/Should-BeZipArchiveOnlyContaining.psm1" -DisableNameChecking
+ Import-Module "$PSScriptRoot/Assertions/Should-BeTarArchiveOnlyContaining.psm1" -DisableNameChecking
+ Import-Module "$PSScriptRoot/Assertions/Should-BeArchiveOnlyContaining.psm1" -DisableNameChecking
}
Describe("Microsoft.PowerShell.Archive tests") {
@@ -12,6 +14,21 @@ BeforeDiscovery {
$originalProgressPref = $ProgressPreference
$ProgressPreference = "SilentlyContinue"
$originalPSModulePath = $env:PSModulePath
+
+ function Add-FileExtensionBasedOnFormat {
+ Param (
+ [string] $Path,
+ [string] $Format
+ )
+
+ if ($Format -eq "Zip") {
+ return $Path += ".zip"
+ }
+ if ($Format -eq "Tar") {
+ return $Path += ".tar"
+ }
+ throw "Format type is not supported"
+ }
}
AfterAll {
@@ -272,7 +289,10 @@ BeforeDiscovery {
}
}
- Context "Basic functional tests" {
+ Context "Basic functional tests" -ForEach @(
+ @{Format = "Zip"},
+ @{Format = "Tar"}
+ ) {
BeforeAll {
New-Item TestDrive:/SourceDir -Type Directory | Out-Null
New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
@@ -301,93 +321,82 @@ BeforeDiscovery {
Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
}
- It "Compresses a single file" {
- $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt"
- $destinationPath = "$TestDrive$($DS)archive1.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @('Sample-2.txt')
+ It "Compresses a single file with format " {
+ $sourcePath = "TestDrive:/SourceDir/ChildDir-1/Sample-2.txt"
+ $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive1" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-2.txt') -Format $Format
}
- It "Compresses a non-empty directory" {
- $sourcePath = "$TestDrive$($DS)SourceDir$($DS)ChildDir-1"
- $destinationPath = "$TestDrive$($DS)archive2.zip"
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('ChildDir-1/', 'ChildDir-1/Sample-2.txt')
+ It "Compresses a non-empty directory with format " -Tag td1 {
+ $sourcePath = "TestDrive:/SourceDir/ChildDir-1"
+ $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive2" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('ChildDir-1/', 'ChildDir-1/Sample-2.txt') -Format $Format
}
- It "Compresses an empty directory" {
- $sourcePath = "$TestDrive$($DS)EmptyDir"
- $destinationPath = "$TestDrive$($DS)archive3.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @('EmptyDir/')
+ It "Compresses an empty directory with format " {
+ $sourcePath = "TestDrive:/EmptyDir"
+ $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive3" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('EmptyDir/') -Format $Format
}
- It "Compresses multiple files" {
- $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt")
- $destinationPath = "$TestDrive$($DS)archive4.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt')
+ It "Compresses multiple files with format " {
+ $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive4" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt') -Format $Format
}
- It "Compresses multiple files and a single empty directory" {
- $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
- "$TestDrive$($DS)SourceDir$($DS)ChildEmptyDir")
-
- $destinationPath = "$TestDrive$($DS)archive5.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildEmptyDir/')
+ It "Compresses multiple files and a single empty directory with format " {
+ $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildEmptyDir")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive5" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt', 'ChildEmptyDir/') -Format $Format
}
- It "Compresses multiple files and a single non-empty directory" {
- $sourcePath = @("$TestDrive$($DS)SourceDir$($DS)ChildDir-1$($DS)Sample-2.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
- "$TestDrive$($DS)SourceDir$($DS)ChildDir-2")
-
- $destinationPath = "$TestDrive$($DS)archive6.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-2/', 'ChildDir-2/Sample-3.txt')
+ It "Compresses multiple files and a single non-empty directory with format " {
+ $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildDir-2")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive6.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-2/', 'ChildDir-2/Sample-3.txt') -Format $Format
}
- It "Compresses multiple files and non-empty directories" {
- $sourcePath = @("$TestDrive$($DS)HelloWorld.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
- "$TestDrive$($DS)SourceDir$($DS)ChildDir-1", "$TestDrive$($DS)SourceDir$($DS)ChildDir-2")
-
- $destinationPath = "$TestDrive$($DS)archive7.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
- 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt')
+ It "Compresses multiple files and non-empty directories with format " {
+ $sourcePath = @("TestDrive:/HelloWorld.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildDir-1", "TestDrive:/SourceDir/ChildDir-2")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive7.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt') -Format $Format
}
- It "Compresses multiple files, non-empty directories, and an empty directory" {
- $sourcePath = @("$TestDrive$($DS)HelloWorld.txt", "$TestDrive$($DS)SourceDir$($DS)Sample-1.txt",
- "$TestDrive$($DS)SourceDir$($DS)ChildDir-1", "$TestDrive$($DS)SourceDir$($DS)ChildDir-2", "$TestDrive$($DS)SourceDir$($DS)ChildEmptyDir")
-
- $destinationPath = "$TestDrive$($DS)archive8.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
- 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "ChildEmptyDir/")
+ It "Compresses multiple files, non-empty directories, and an empty directory with format " {
+ $sourcePath = @("TestDrive:/HelloWorld.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildDir-1", "TestDrive:/SourceDir/ChildDir-2", "TestDrive:/SourceDir/ChildEmptyDir")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive8.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "ChildEmptyDir/") -Format $Format
}
- It "Compresses a directory containing files, non-empty directories, and an empty directory can be compressed" -Tag td4 {
- $sourcePath = "$TestDrive$($DS)SourceDir"
- $destinationPath = "$TestDrive$($DS)archive9.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt', 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt')
+ It "Compresses a directory containing files, non-empty directories, and an empty directory can be compressed with format " {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive9.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $contents = @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt',
+ 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt')
+ $destinationPath | Should -BeArchiveOnlyContaining $contents -Format $Format
}
- It "Compresses a zero-byte file" {
- $sourcePath = "$TestDrive$($DS)EmptyFile"
- $destinationPath = "$TestDrive$($DS)archive10.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- $contents = @('EmptyFile')
- Test-ZipArchive $destinationPath $contents
+ It "Compresses a zero-byte file with format " {
+ $sourcePath = "TestDrive:/EmptyFile"
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive10.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('EmptyFile') -Format $Format
}
It "Compresses a file whose last write time is before 1980" {
@@ -423,7 +432,7 @@ BeforeDiscovery {
$archiveStream.Dispose()
}
- It "Compresses a directory whose last write time is before 1980" {
+ It "Compresses a directory whose last write time is before 1980 with format " {
$sourcePath = "TestDrive:/olddirectory"
$destinationPath = "${TestDrive}/archive12.zip"
@@ -452,7 +461,7 @@ BeforeDiscovery {
$archiveStream.Dispose()
}
- It "Writes a warning when compressing a file whose last write time is before 1980" {
+ It "Writes a warning when compressing a file whose last write time is before 1980 with format " {
$sourcePath = "TestDrive:/OldFile.txt"
$destinationPath = "${TestDrive}/archive13.zip"
@@ -464,7 +473,7 @@ BeforeDiscovery {
$warnings.Length | Should -Be 1
}
- It "Writes a warning when compresing a directory whose last write time is before 1980" {
+ It "Writes a warning when compresing a directory whose last write time is before 1980 with format " {
$sourcePath = "TestDrive:/olddirectory"
$destinationPath = "${TestDrive}/archive14.zip"
@@ -477,10 +486,6 @@ BeforeDiscovery {
}
}
- Context "Update tests" -Skip {
-
- }
-
Context "DestinationPath and -WriteMode Overwrite tests" {
BeforeAll {
New-Item TestDrive:/SourceDir -Type Directory | Out-Null
@@ -682,7 +687,7 @@ BeforeDiscovery {
}
# From 596
- It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" -Tag this3 {
+ It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" {
$sourcePath = "TestDrive:/SourceDir"
$destinationPath = "./RelativePathForDestinationPathParameter.zip"
try
@@ -798,10 +803,11 @@ BeforeDiscovery {
}
}
- Context "Long path tests" {
+ # This can be difficult to test
+ Context "Long path tests" -Skip {
BeforeAll {
if ($IsWindows) {
- $maxPathLength = 260
+ $maxPathLength = 300
}
if ($IsLinux) {
$maxPathLength = 255
@@ -816,7 +822,7 @@ BeforeDiscovery {
)
$path = "${TestDrive}/"
- while ($path.Length -le $maxPathLength + 2) {
+ while ($path.Length -le $maxPathLength + 10) {
$path += $character
}
return $path
@@ -841,10 +847,11 @@ BeforeDiscovery {
$destinationPath = Get-MaxLengthPath -character a
Write-Warning $destinationPath.Length
try {
- Compress-Archive -Path $path -DestinationPath $destinationPath
+ Compress-Archive -Path $path -DestinationPath $destinationPath -ErrorVariable err
} catch {
throw "${$_.Exception}"
}
+ $destinationPath | Should -Not -Exist
}
}
}
diff --git a/src/ArchiveFactory.cs b/src/ArchiveFactory.cs
index fd0e636..dacf713 100644
--- a/src/ArchiveFactory.cs
+++ b/src/ArchiveFactory.cs
@@ -21,7 +21,7 @@ internal static IArchive GetArchive(ArchiveFormat format, string archivePath, Ar
return format switch
{
ArchiveFormat.Zip => new ZipArchive(archivePath, archiveMode, archiveFileStream, compressionLevel),
- //ArchiveFormat.tar => new TarArchive(archivePath, archiveMode, archiveFileStream),
+ ArchiveFormat.Tar => new TarArchive(archivePath, archiveMode, archiveFileStream),
// TODO: Add Tar.gz here
_ => throw new ArgumentOutOfRangeException(nameof(archiveMode))
};
@@ -32,9 +32,8 @@ internal static bool TryGetArchiveFormatFromExtension(string path, out ArchiveFo
archiveFormat = Path.GetExtension(path).ToLowerInvariant() switch
{
".zip" => ArchiveFormat.Zip,
- /* Disable support for tar and tar.gz for preview1 release
- ".gz" => path.EndsWith(".tar.gz) ? ArchiveFormat.Tgz : null,
- */
+ ".tar" => ArchiveFormat.Tar,
+ ".gz" => path.EndsWith(".tar.gz") ? ArchiveFormat.Tgz : null,
_ => null
};
return archiveFormat is not null;
diff --git a/src/ArchiveFormat.cs b/src/ArchiveFormat.cs
index 5ee6fef..f8032a9 100644
--- a/src/ArchiveFormat.cs
+++ b/src/ArchiveFormat.cs
@@ -10,8 +10,7 @@ namespace Microsoft.PowerShell.Archive
public enum ArchiveFormat
{
Zip,
- /* Removing these formats for preview relase
Tar,
- Tgz*/
+ Tgz
}
}
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index 9601591..56828dc 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -5,7 +5,7 @@
using System.Collections.Generic;
using System.Formats.Tar;
using System.IO;
-using System.Text;
+using System.Diagnostics;
namespace Microsoft.PowerShell.Archive
{
@@ -17,15 +17,15 @@ internal class TarArchive : IArchive
private readonly string _path;
- private TarWriter _tarWriter;
+ private TarWriter? _tarWriter;
private TarReader? _tarReader;
private readonly FileStream _fileStream;
- private FileStream _copyStream;
+ private FileStream? _copyStream;
- private string _copyPath;
+ private string? _copyPath;
ArchiveMode IArchive.Mode => _mode;
@@ -35,34 +35,43 @@ public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
{
_mode = mode;
_path = path;
- _tarWriter = new TarWriter(archiveStream: fileStream, format: TarEntryFormat.Pax, leaveOpen: false);
_fileStream = fileStream;
}
void IArchive.AddFileSystemEntry(ArchiveAddition entry)
{
- if (_mode == ArchiveMode.Extract) {
+ if (_mode == ArchiveMode.Extract)
+ {
throw new ArgumentException("Adding entries to the archive is not supported on Extract mode.");
}
// If the archive is in Update mode, we want to update the archive by copying it to a new archive
// and then adding the entries to that archive
- if (_mode == ArchiveMode.Update) {
-
- } else {
- // If the archive mode is Create, no copy
- _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entry.EntryName);
- }
+ if (_mode == ArchiveMode.Update)
+ {
+ if (_copyStream is null)
+ {
+ CreateCopyStream();
+ }
+ }
+ else
+ {
+ _tarWriter = new TarWriter(_fileStream, TarEntryFormat.Pax, true);
+ }
+ Debug.Assert(_tarWriter is not null);
+ _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entry.EntryName);
}
IEntry? IArchive.GetNextEntry()
{
// If _tarReader is null, create it
- if (_tarReader is null) {
+ if (_tarReader is null)
+ {
_tarReader = new TarReader(archiveStream: _fileStream, leaveOpen: true);
}
var entry = _tarReader.GetNextEntry();
- if (entry is null) {
+ if (entry is null)
+ {
return null;
}
// Create and return a TarArchiveEntry, which is a wrapper around entry
@@ -74,7 +83,8 @@ private void CreateCopyStream() {
string copyName = Path.GetRandomFileName();
// Directory of the copy will be the same as the directory of the archive
- string directory = Path.GetDirectoryName(_path);
+ string? directory = Path.GetDirectoryName(_path);
+ Debug.Assert(directory is not null);
_copyPath = Path.Combine(directory, copyName);
_copyStream = new FileStream(_copyPath, FileMode.Create, FileAccess.ReadWrite, FileShare.None);
@@ -83,7 +93,22 @@ private void CreateCopyStream() {
_tarReader = new TarReader(_fileStream, leaveOpen: false);
// Create a tar writer that will write the contents of the archive to the copy
- //_tarWriter = new TarWriter(_)
+ _tarWriter = new TarWriter(_copyStream, TarEntryFormat.Pax, true);
+
+ var entry = _tarReader.GetNextEntry();
+ while (entry is not null)
+ {
+ _tarWriter.WriteEntry(entry);
+ entry = _tarReader.GetNextEntry();
+ }
+ }
+
+ private void ReplaceArchiveWithCopy() {
+ Debug.Assert(_copyPath is not null);
+ // Delete the archive
+ File.Delete(_path);
+ // Move copy to archive path
+ File.Move(_copyPath, _path);
}
protected virtual void Dispose(bool disposing)
@@ -93,8 +118,15 @@ protected virtual void Dispose(bool disposing)
if (disposing)
{
// TODO: dispose managed state (managed objects)
- _tarWriter.Dispose();
+ _tarWriter?.Dispose();
+ _copyStream?.Dispose();
+ _tarReader?.Dispose();
_fileStream.Dispose();
+
+ if (_mode == ArchiveMode.Update)
+ {
+ ReplaceArchiveWithCopy();
+ }
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
From a7f0ee23de7be7a6132a2c794cd806dd985674e8 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Thu, 11 Aug 2022 12:10:22 -0700
Subject: [PATCH 22/34] worked on tar support, added support for determining
whether an archive has a top-level directory, added tests for tar
---
Tests/Compress-Archive.Tests.ps1 | 24 +++++++++++-
Tests/Expand-Archive.Tests.ps1 | 67 +++++++++++++++++++++-----------
src/TarArchive.cs | 42 +++++++++++++++++---
3 files changed, 104 insertions(+), 29 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 257b3ac..807c6a4 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -310,7 +310,7 @@ BeforeDiscovery {
"Hello, World!" | Out-File -FilePath $TestDrive$($DS)HelloWorld.txt
# Create a zero-byte file
- New-Item $TestDrive$($DS)EmptyFile -Type File | Out-Null
+ New-Item TestDrive:/EmptyFile -Type File | Out-Null
# Create a file whose last write time is before 1980
$content | Out-File -FilePath TestDrive:/OldFile.txt
@@ -398,6 +398,18 @@ BeforeDiscovery {
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
$destinationPath | Should -BeArchiveOnlyContaining @('EmptyFile') -Format $Format
}
+ }
+
+ Context "Zip-specific tests" {
+ BeforeAll {
+ # Create a file whose last write time is before 1980
+ $content | Out-File -FilePath TestDrive:/OldFile.txt
+ Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
+
+ # Create a directory whose last write time is before 1980
+ New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
+ }
It "Compresses a file whose last write time is before 1980" {
$sourcePath = "$TestDrive$($DS)OldFile.txt"
@@ -786,6 +798,10 @@ BeforeDiscovery {
BeforeAll {
New-Item TestDrive:/file.txt -ItemType File
"Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+ # Create a read-only file
+ New-Item TestDrive:/readonly.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/readonly.txt
}
@@ -801,6 +817,12 @@ BeforeDiscovery {
$archiveInUseStream.Dispose()
}
+
+ It "Compresses a read-only file" {
+ $destinationPath = "TestDrive:/archive_with_readonly_file.zip"
+ Compress-Archive -Path TestDrive:/readonly.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("readonly.txt") -Format Zip
+ }
}
# This can be difficult to test
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index fc8a636..5d4d65a 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -7,6 +7,21 @@ Describe("Expand-Archive Tests") {
# Progress perference
$originalProgressPref = $ProgressPreference
$ProgressPreference = "SilentlyContinue"
+
+ function Add-FileExtensionBasedOnFormat {
+ Param (
+ [string] $Path,
+ [string] $Format
+ )
+
+ if ($Format -eq "Zip") {
+ return $Path += ".zip"
+ }
+ if ($Format -eq "Tar") {
+ return $Path += ".tar"
+ }
+ throw "Format type is not supported"
+ }
}
AfterAll {
@@ -335,7 +350,10 @@ Describe("Expand-Archive Tests") {
}
}
- Context "Basic functionality tests" {
+ Context "Basic functionality tests" -ForEach @(
+ @{Format = "Zip"},
+ @{Format = "Tar"}
+ ) {
# extract to a directory works
# extract to working directory works when DestinationPath is specified
# expand archive works when -DestinationPath is not specified (and a single top level item which is a directory)
@@ -344,7 +362,7 @@ Describe("Expand-Archive Tests") {
BeforeAll {
New-Item -Path "TestDrive:/file1.txt" -ItemType File
"Hello, World!" | Out-File -FilePath "TestDrive:/file1.txt"
- Compress-Archive -Path "TestDrive:/file1.txt" -DestinationPath "TestDrive:/archive1.zip"
+ Compress-Archive -Path "TestDrive:/file1.txt" -DestinationPath (Add-FileExtensionBasedOnFormat "TestDrive:/archive1" -Format $Format)
New-Item -Path "TestDrive:/directory2" -ItemType Directory
New-Item -Path "TestDrive:/directory3" -ItemType Directory
@@ -353,25 +371,25 @@ Describe("Expand-Archive Tests") {
New-Item -Path "TestDrive:/directory6" -ItemType Directory
New-Item -Path "TestDrive:/DirectoryToArchive" -ItemType Directory
- Compress-Archive -Path "TestDrive:/DirectoryToArchive" -DestinationPath "TestDrive:/archive2.zip"
+ Compress-Archive -Path "TestDrive:/DirectoryToArchive" -DestinationPath (Add-FileExtensionBasedOnFormat "TestDrive:/archive2" -Format $Format)
# Create an archive containing a file and an empty folder
- Compress-Archive -Path "TestDrive:/file1.txt","TestDrive:/DirectoryToArchive" -DestinationPath "TestDrive:/archive3.zip"
+ Compress-Archive -Path "TestDrive:/file1.txt","TestDrive:/DirectoryToArchive" -DestinationPath (Add-FileExtensionBasedOnFormat "TestDrive:/archive3" -Format $Format)
}
- It "Expands an archive when a non-existent directory is specified as -DestinationPath" {
- $sourcePath = "TestDrive:/archive1.zip"
+ It "Expands an archive when a non-existent directory is specified as -DestinationPath with format " {
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive1" -Format $Format
$destinationPath = "TestDrive:/directory1"
- Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
$itemsInDestinationPath = Get-ChildItem $destinationPath -Recurse
$itemsInDestinationPath.Count | Should -Be 1
$itemsInDestinationPath[0].Name | Should -Be "file1.txt"
}
- It "Expands an archive when DestinationPath is an existing directory" -Tag debug3 {
- $sourcePath = "TestDrive:/archive1.zip"
+ It "Expands an archive when DestinationPath is an existing directory" {
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive1" -Format $Format
$destinationPath = "TestDrive:/directory2"
try {
@@ -382,7 +400,7 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive to the working directory when it is specified as -DestinationPath" {
- $sourcePath = "TestDrive:/archive1.zip"
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive1" -Format $Format
$destinationPath = "TestDrive:/directory3"
Push-Location $destinationPath
@@ -397,7 +415,7 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" {
- $sourcePath = "TestDrive:/archive2.zip"
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive2" -Format $Format
$destinationPath = "TestDrive:/directory4"
Push-Location $destinationPath
@@ -412,7 +430,7 @@ Describe("Expand-Archive Tests") {
}
It "Expands an archive containing multiple top-level items to a directory with that archive's name when -DestinationPath is not specified" {
- $sourcePath = "TestDrive:/archive3.zip"
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive3" -Format $Format
$destinationPath = "TestDrive:/directory5"
Push-Location $destinationPath
@@ -448,12 +466,13 @@ Describe("Expand-Archive Tests") {
$archive4Paths = @("TestDrive:/file2.txt", "TestDrive:/file3.txt", "TestDrive:/emptydirectory1", "TestDrive:/emptydirectory2", "TestDrive:/nonemptydirectory1", "TestDrive:/nonemptydirectory2")
- Compress-Archive -Path $archive4Paths -DestinationPath "TestDrive:/archive4.zip"
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive4" -Format $Format
+ Compress-Archive -Path $archive4Paths -DestinationPath $sourcePath -Format $Format
- $sourcePath = "TestDrive:/archive4.zip"
+
$destinationPath = "TestDrive:/directory6"
- Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
$expandedItems = Get-ChildItem $destinationPath -Recurse -Name
@@ -468,11 +487,12 @@ Describe("Expand-Archive Tests") {
It "Expands an archive containing a file whose LastWriteTime is in the past" {
New-Item -Path "TestDrive:/oldfile.txt" -ItemType File
Set-ItemProperty -Path "TestDrive:/oldfile.txt" -Name "LastWriteTime" -Value '2003-01-16 14:44'
- Compress-Archive -Path "TestDrive:/oldfile.txt" -DestinationPath "TestDrive:/archive_oldfile.zip"
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive_oldfile" -Format $Format
+ Compress-Archive -Path "TestDrive:/oldfile.txt" -DestinationPath $sourcePath -Format $Format
- $sourcePath = "TestDrive:/archive_oldfile.zip"
+
$destinationPath = "TestDrive:/destination7"
- Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
$lastWriteTime = Get-ItemPropertyValue -Path (Join-Path $destinationPath "oldfile.txt") -Name "LastWriteTime"
@@ -488,11 +508,13 @@ Describe("Expand-Archive Tests") {
It "Expands an archive containing a directory whose LastWriteTime is in the past" {
New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '2003-01-16 14:44'
- Compress-Archive -Path "TestDrive:/olddirectory" -DestinationPath "TestDrive:/archive_olddirectory.zip"
- $sourcePath = "TestDrive:/archive_olddirectory.zip"
+ $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive_olddirectory" -Format $Format
+ Compress-Archive -Path "TestDrive:/olddirectory" -DestinationPath $sourcePath -Format $Format
+
+
$destinationPath = "TestDrive:/destination_olddirectory"
- Expand-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Expand-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
$lastWriteTime = Get-ItemPropertyValue -Path "TestDrive:/destination_olddirectory/olddirectory" -Name "LastWriteTime"
@@ -572,7 +594,7 @@ Describe("Expand-Archive Tests") {
}
}
- Context "File permssions, attributes, etc tests" {
+ Context "File permssions, attributes, etc tests" -Tag td2 {
BeforeAll {
New-Item TestDrive:/file.txt -ItemType File
"Hello, World!" | Out-File -Path TestDrive:/file.txt
@@ -588,6 +610,7 @@ Describe("Expand-Archive Tests") {
$fileShare = [System.IO.FileShare]::Read
$archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/archive_in_use.zip",$fileMode,$fileAccess,$fileShare
+ [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
# Create an archive containing an entry with non-latin characters
New-Item TestDrive:/ملف -ItemType File
"Hello, World!" | Out-File -Path TestDrive:/ملف
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index 56828dc..1968df1 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Archive
{
internal class TarArchive : IArchive
{
- private bool disposedValue;
+ private bool _disposedValue;
private readonly ArchiveMode _mode;
@@ -54,12 +54,16 @@ void IArchive.AddFileSystemEntry(ArchiveAddition entry)
CreateCopyStream();
}
}
- else
+ else if (_tarWriter is null)
{
_tarWriter = new TarWriter(_fileStream, TarEntryFormat.Pax, true);
}
+
+ // Replace '\' with '/'
+ var entryName = entry.EntryName.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
+
Debug.Assert(_tarWriter is not null);
- _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entry.EntryName);
+ _tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entryName);
}
IEntry? IArchive.GetNextEntry()
@@ -67,6 +71,7 @@ void IArchive.AddFileSystemEntry(ArchiveAddition entry)
// If _tarReader is null, create it
if (_tarReader is null)
{
+ _fileStream.Position = 0;
_tarReader = new TarReader(archiveStream: _fileStream, leaveOpen: true);
}
var entry = _tarReader.GetNextEntry();
@@ -113,7 +118,7 @@ private void ReplaceArchiveWithCopy() {
protected virtual void Dispose(bool disposing)
{
- if (!disposedValue)
+ if (!_disposedValue)
{
if (disposing)
{
@@ -131,7 +136,7 @@ protected virtual void Dispose(bool disposing)
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
- disposedValue = true;
+ _disposedValue = true;
}
}
@@ -144,7 +149,32 @@ public void Dispose()
bool IArchive.HasTopLevelDirectory()
{
- throw new NotImplementedException();
+ // Go through each entry and see if it is a top-level entry
+ _tarReader = new TarReader(_fileStream, leaveOpen: true);
+
+ int topLevelDirectoriesCount = 0;
+ var entry = _tarReader.GetNextEntry();
+ while (entry is not null) {
+
+ if (entry.EntryType == TarEntryType.Directory)
+ {
+ topLevelDirectoriesCount++;
+ if (topLevelDirectoriesCount > 1)
+ {
+ break;
+ }
+ } else
+ {
+ _tarReader.Dispose();
+ _tarReader = null;
+ return false;
+ }
+ entry = _tarReader.GetNextEntry();
+ }
+
+ _tarReader.Dispose();
+ _tarReader = null;
+ return topLevelDirectoriesCount == 1;
}
internal class TarArchiveEntry : IEntry {
From 4f9265cb0073f2b380b35a6b2b68c8ef5b4dea62 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Thu, 11 Aug 2022 14:37:09 -0700
Subject: [PATCH 23/34] updated changelog and release notes
---
CHANGELOG.md | 10 +++++-----
src/Microsoft.PowerShell.Archive.psd1 | 10 +++++-----
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6590597..a1ee678 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,19 +2,19 @@
## 2.0.1-preview2
-- Rewrite `Expand-Archive` cmdlet in C#
+- Rewrote `Expand-Archive` cmdlet in C#
- Added `-Format` parameter to `Expand-Archive`
- Added `-WriteMode` parameter to `Expand-Archive`
-- Added support for zip64
+- Added support for zip64 to `Expand-Archive`
- Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
-- `Compress-Archive` skips writing an entry to an archive if an error occurs while doing so
+- `Compress-Archive` by default skips writing an entry to an archive if an error occurs while doing so
## 2.0.1-preview1
-- Rewrite `Compress-Archive` cmdlet in C#
+- Rewrote `Compress-Archive` cmdlet in C#
- Added `-Format` parameter to `Compress-Archive`
- Added `-WriteMode` parameter to `Compress-Archive`
- Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
-- Added support for zip64
+- Added support for zip64 to `Compress-Archive`
- Fixed a bug where empty directories would not be compressed
- Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1
index 0861e0e..da72f6b 100644
--- a/src/Microsoft.PowerShell.Archive.psd1
+++ b/src/Microsoft.PowerShell.Archive.psd1
@@ -15,19 +15,19 @@ PrivateData = @{
LicenseUri = 'https://go.microsoft.com/fwlink/?linkid=2203619'
ReleaseNotes = @'
## 2.0.1-preview2
- - Rewrite `Expand-Archive` cmdlet in C#
+ - Rewrote `Expand-Archive` cmdlet in C#
- Added `-Format` parameter to `Expand-Archive`
- Added `-WriteMode` parameter to `Expand-Archive`
- - Added support for zip64
+ - Added support for zip64 to `Expand-Archive`
- Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
- - `Compress-Archive` skips writing an entry to an archive if an error occurs while doing so
+ - `Compress-Archive` by default skips writing an entry to an archive if an error occurs while doing so
## 2.0.1-preview1
- - Rewrite `Compress-Archive` cmdlet in C#
+ - Rewrote `Compress-Archive` cmdlet in C#
- Added `-Format` parameter to `Compress-Archive`
- Added `-WriteMode` parameter to `Compress-Archive`
- Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
- - Added support for zip64
+ - Added support for zip64 to `Compress-Archive`
- Fixed a bug where empty directories would not be compressed
- Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
'@
From 1d8a21ccfa09e4bb5563483f3c448d004201e20a Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Thu, 11 Aug 2022 14:39:30 -0700
Subject: [PATCH 24/34] fixed a typo in the changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a1ee678..58c5e7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,7 +14,7 @@
- Rewrote `Compress-Archive` cmdlet in C#
- Added `-Format` parameter to `Compress-Archive`
- Added `-WriteMode` parameter to `Compress-Archive`
-- Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
+- Added support for relative path structure preservation when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
- Added support for zip64 to `Compress-Archive`
- Fixed a bug where empty directories would not be compressed
- Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
From 4e7515f127c8c3059abf23f1cd1746f742b19811 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Thu, 11 Aug 2022 17:35:59 -0700
Subject: [PATCH 25/34] added tests for path structure preservation, added gzip
support, worked on tar.gz support
---
.../Should-BeZipArchiveOnlyContaining.psm1 | 4 +-
Tests/Compress-Archive.Tests.ps1 | 60 +++++++++
src/GzipArchive.cs | 125 ++++++++++++++++++
src/TarArchive.cs | 7 +-
src/TarGzArchive.cs | 108 +++++++++++++++
5 files changed, 299 insertions(+), 5 deletions(-)
create mode 100644 src/GzipArchive.cs
create mode 100644 src/TarGzArchive.cs
diff --git a/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1 b/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1
index 833fc6c..52e448e 100644
--- a/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1
+++ b/Tests/Assertions/Should-BeZipArchiveOnlyContaining.psm1
@@ -49,9 +49,9 @@ function Should-BeZipArchiveOnlyContaining {
# Get 7-zip to list the contents of the archive
if ($IsWindows) {
- $output = 7z.exe l $ActualValue -ba
+ $output = 7z.exe l $ActualValue -ba -tzip
} else {
- $output = 7z l $ActualValue -ba
+ $output = 7z l $ActualValue -ba -tzip
}
# Check if the output is null
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 807c6a4..67f9711 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -876,4 +876,64 @@ BeforeDiscovery {
$destinationPath | Should -Not -Exist
}
}
+
+ Context "CompressionLevel tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+ }
+
+ It "Throws an error when an invalid value is supplied to CompressionLevel" {
+ try {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1.zip -CompressionLevel fakelevel
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "InvalidArgument, ${CmdletClassName}"
+ }
+ }
+ }
+
+ Context "Path Structure Preservation Tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+
+ New-Item -Path TestDrive:/directory1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/subdir1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/subdir1/file.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file.txt
+ }
+
+ It "Creates an archive containing only a file when the path to that file is not relative to the working directory" {
+ $destinationPath = "TestDrive:/archive1.zip"
+
+ Push-Location TestDrive:/directory1
+
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt")
+
+ Pop-Location
+ }
+
+ It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
+ $destinationPath = "TestDrive:/archive2.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1/subdir1/file.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt")
+
+ Pop-Location
+ }
+
+ It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
+ $destinationPath = "TestDrive:/archive3.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1 -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt")
+
+ Pop-Location
+ }
+ }
}
diff --git a/src/GzipArchive.cs b/src/GzipArchive.cs
new file mode 100644
index 0000000..a695015
--- /dev/null
+++ b/src/GzipArchive.cs
@@ -0,0 +1,125 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Formats.Tar;
+using System.IO;
+using System.IO.Compression;
+using System.Diagnostics;
+
+namespace Microsoft.PowerShell.Archive
+{
+ internal class GzipArchive : IArchive
+ {
+ private bool _disposedValue;
+
+ private readonly ArchiveMode _mode;
+
+ private readonly string _path;
+
+ private readonly FileStream _fileStream;
+
+ private readonly CompressionLevel _compressionLevel;
+
+ private bool _addedFile;
+
+ private bool _didCallGetNextEntry;
+
+ ArchiveMode IArchive.Mode => _mode;
+
+ string IArchive.Path => _path;
+
+ public GzipArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel)
+ {
+ _mode = mode;
+ _path = path;
+ _fileStream = fileStream;
+ _compressionLevel = compressionLevel;
+ }
+
+ void IArchive.AddFileSystemEntry(ArchiveAddition entry)
+ {
+ if (_mode == ArchiveMode.Extract)
+ {
+ throw new ArgumentException("Adding entries to the archive is not supported on Extract mode.");
+ }
+ if (_mode == ArchiveMode.Update)
+ {
+ throw new ArgumentException("Updating a Gzip file in not supported.");
+ }
+ if (_addedFile)
+ {
+ throw new ArgumentException("Adding a Gzip file in not supported.");
+ }
+ if (entry.FileSystemInfo.Attributes.HasFlag(FileAttributes.Directory)) {
+ throw new ArgumentException("Compressing directories is not supported");
+ }
+ using var gzipCompressor = new GZipStream(_fileStream, _compressionLevel, leaveOpen: true);
+ using var fileToCopy = new FileStream(entry.FileSystemInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
+ fileToCopy.CopyTo(gzipCompressor);
+ _addedFile = true;
+ }
+
+ IEntry? IArchive.GetNextEntry()
+ {
+ // Gzip has no concept of entries
+ if (!_didCallGetNextEntry) {
+ _didCallGetNextEntry = true;
+ return new GzipArchiveEntry(this);
+ }
+ return null;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _fileStream.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ bool IArchive.HasTopLevelDirectory()
+ {
+ throw new NotSupportedException();
+ }
+
+ internal class GzipArchiveEntry : IEntry {
+
+ private GzipArchive _gzipArchive;
+
+ // Gzip has no concept of entries, so getting the entry name is not supported
+ string IEntry.Name => throw new NotSupportedException();
+
+ // Gzip does not compress directories, so this is always false
+ bool IEntry.IsDirectory => false;
+
+ public GzipArchiveEntry(GzipArchive gzipArchive)
+ {
+ _gzipArchive = gzipArchive;
+ }
+
+ void IEntry.ExpandTo(string destinationPath)
+ {
+ using var destinationFileStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ using var gzipDecompressor = new GZipStream(_gzipArchive._fileStream, CompressionMode.Decompress);
+ gzipDecompressor.CopyTo(destinationFileStream);
+ }
+ }
+ }
+}
diff --git a/src/TarArchive.cs b/src/TarArchive.cs
index 1968df1..6cc266e 100644
--- a/src/TarArchive.cs
+++ b/src/TarArchive.cs
@@ -203,14 +203,15 @@ void IEntry.ExpandTo(string destinationPath)
Directory.CreateDirectory(parentDirectory);
}
+ var lastWriteTime = _entry.ModificationTime.LocalDateTime;
if (_objectAsIEntry.IsDirectory)
{
- System.IO.Directory.CreateDirectory(destinationPath);
- var lastWriteTime = _entry.ModificationTime;
- System.IO.Directory.SetLastWriteTime(destinationPath, lastWriteTime.DateTime);
+ Directory.CreateDirectory(destinationPath);
+ Directory.SetLastWriteTime(destinationPath, lastWriteTime);
} else
{
_entry.ExtractToFile(destinationPath, overwrite: false);
+ File.SetLastWriteTime(destinationPath, lastWriteTime);
}
}
diff --git a/src/TarGzArchive.cs b/src/TarGzArchive.cs
new file mode 100644
index 0000000..e3cb4b0
--- /dev/null
+++ b/src/TarGzArchive.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Formats.Tar;
+using System.IO;
+using System.IO.Compression;
+using System.Diagnostics;
+
+namespace Microsoft.PowerShell.Archive
+{
+ internal class TarGzArchive : IArchive
+ {
+ private bool _disposedValue;
+
+ private readonly ArchiveMode _mode;
+
+ private readonly string _path;
+
+ private readonly FileStream _fileStream;
+
+ private readonly CompressionLevel _compressionLevel;
+
+ // Use a tar archive because .tar.gz file is a compressed tar file
+ private TarArchive _tarArchive;
+
+ ArchiveMode IArchive.Mode => _mode;
+
+ string IArchive.Path => _path;
+
+ public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel)
+ {
+ _mode = mode;
+ _path = path;
+ _fileStream = fileStream;
+ _compressionLevel = compressionLevel;
+ }
+
+ void IArchive.AddFileSystemEntry(ArchiveAddition entry)
+ {
+ if (_mode == ArchiveMode.Create) {
+
+ }
+ }
+
+ IEntry? IArchive.GetNextEntry()
+ {
+ // Gzip has no concept of entries
+ if (!_didCallGetNextEntry) {
+ _didCallGetNextEntry = true;
+ return new TarGzArchiveEntry(this);
+ }
+ return null;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _fileStream.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ _disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ bool IArchive.HasTopLevelDirectory()
+ {
+ throw new NotSupportedException();
+ }
+
+ internal class TarGzArchiveEntry : IEntry {
+
+ private TarGzArchive _gzipArchive;
+
+ // Gzip has no concept of entries, so getting the entry name is not supported
+ string IEntry.Name => throw new NotSupportedException();
+
+ // Gzip does not compress directories, so this is always false
+ bool IEntry.IsDirectory => false;
+
+ public TarGzArchiveEntry(TarGzArchive gzipArchive)
+ {
+ _gzipArchive = gzipArchive;
+ }
+
+ void IEntry.ExpandTo(string destinationPath)
+ {
+ using var destinationFileStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ using var gzipDecompressor = new GZipStream(_gzipArchive._fileStream, CompressionMode.Decompress);
+ gzipDecompressor.CopyTo(destinationFileStream);
+ }
+ }
+ }
+}
From 0a5ca6b794dfa327223b72ba37f37a8d530265b4 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Fri, 12 Aug 2022 17:25:16 -0700
Subject: [PATCH 26/34] worked on tar.gz support
---
src/ArchiveFactory.cs | 2 +-
src/TarGzArchive.cs | 28 ++++++++++++++++++++++------
2 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/src/ArchiveFactory.cs b/src/ArchiveFactory.cs
index dacf713..bb91531 100644
--- a/src/ArchiveFactory.cs
+++ b/src/ArchiveFactory.cs
@@ -22,7 +22,7 @@ internal static IArchive GetArchive(ArchiveFormat format, string archivePath, Ar
{
ArchiveFormat.Zip => new ZipArchive(archivePath, archiveMode, archiveFileStream, compressionLevel),
ArchiveFormat.Tar => new TarArchive(archivePath, archiveMode, archiveFileStream),
- // TODO: Add Tar.gz here
+ ArchiveFormat.Tgz => new TarGzArchive(archivePath, archiveMode, archiveFileStream, compressionLevel),
_ => throw new ArgumentOutOfRangeException(nameof(archiveMode))
};
}
diff --git a/src/TarGzArchive.cs b/src/TarGzArchive.cs
index e3cb4b0..ec1f2cf 100644
--- a/src/TarGzArchive.cs
+++ b/src/TarGzArchive.cs
@@ -23,7 +23,9 @@ internal class TarGzArchive : IArchive
private readonly CompressionLevel _compressionLevel;
// Use a tar archive because .tar.gz file is a compressed tar file
- private TarArchive _tarArchive;
+ private TarArchive? _tarArchive;
+
+ private string? _tarFilePath;
ArchiveMode IArchive.Mode => _mode;
@@ -39,17 +41,22 @@ public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, Compre
void IArchive.AddFileSystemEntry(ArchiveAddition entry)
{
+ if (_mode == ArchiveMode.Extract) {
+ throw new ArgumentException("Adding entries to the archive is not supported in extract mode");
+ }
+
if (_mode == ArchiveMode.Create) {
-
+ if (_tarArchive is null) {
+ _tarArchive = new TarArchive(_path, ArchiveMode.Create, _fileStream);
+ }
+ (_tarArchive as IArchive).AddFileSystemEntry(entry);
}
}
IEntry? IArchive.GetNextEntry()
{
- // Gzip has no concept of entries
- if (!_didCallGetNextEntry) {
- _didCallGetNextEntry = true;
- return new TarGzArchiveEntry(this);
+ if (_mode == ArchiveMode.Create || _mode == ArchiveMode.Update) {
+ throw new ArgumentException("Getting the entries in an archive is not supported in Create or Update mode");
}
return null;
}
@@ -62,6 +69,7 @@ protected virtual void Dispose(bool disposing)
{
// TODO: dispose managed state (managed objects)
_fileStream.Dispose();
+ CompressArchive();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
@@ -82,6 +90,14 @@ bool IArchive.HasTopLevelDirectory()
throw new NotSupportedException();
}
+ // Performs gzip compression on _path
+ private void CompressArchive() {
+ //using var destinationFileStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ _fileStream.Position = 0;
+ using var gzipDecompressor = new GZipStream(_fileStream, _compressionLevel, true);
+ _fileStream.CopyTo(gzipDecompressor);
+ }
+
internal class TarGzArchiveEntry : IEntry {
private TarGzArchive _gzipArchive;
From 86b7204fa8eaf671fa72d1932277100e0f3ebe45 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Sat, 13 Aug 2022 20:09:04 -0700
Subject: [PATCH 27/34] organized files, worked on tar.gz support, filter and
flatten support
---
src/{ => Cmdlets}/CompressArchiveCommand.cs | 19 ++-
src/{ => Cmdlets}/ExpandArchiveCommand.cs | 8 --
src/{ErrorMessages.cs => Error.cs} | 0
src/{ => Formats}/GzipArchive.cs | 14 +--
src/{ => Formats}/TarArchive.cs | 2 +-
src/Formats/TarGzArchive.cs | 70 +++++++++++
src/{ => Formats}/ZipArchive.cs | 0
src/IArchive.cs | 12 +-
src/PathHelper.cs | 83 ++++++++++---
src/TarGzArchive.cs | 124 --------------------
10 files changed, 162 insertions(+), 170 deletions(-)
rename src/{ => Cmdlets}/CompressArchiveCommand.cs (98%)
rename src/{ => Cmdlets}/ExpandArchiveCommand.cs (97%)
rename src/{ErrorMessages.cs => Error.cs} (100%)
rename src/{ => Formats}/GzipArchive.cs (91%)
rename src/{ => Formats}/TarArchive.cs (99%)
create mode 100644 src/Formats/TarGzArchive.cs
rename src/{ => Formats}/ZipArchive.cs (100%)
delete mode 100644 src/TarGzArchive.cs
diff --git a/src/CompressArchiveCommand.cs b/src/Cmdlets/CompressArchiveCommand.cs
similarity index 98%
rename from src/CompressArchiveCommand.cs
rename to src/Cmdlets/CompressArchiveCommand.cs
index dea909f..76679fa 100644
--- a/src/CompressArchiveCommand.cs
+++ b/src/Cmdlets/CompressArchiveCommand.cs
@@ -50,18 +50,25 @@ private enum ParameterSet
[NotNull]
public string? DestinationPath { get; set; }
- [Parameter()]
+ [Parameter]
public WriteMode WriteMode { get; set; }
- [Parameter()]
+ [Parameter]
public SwitchParameter PassThru { get; set; }
- [Parameter()]
+ [Parameter]
[ValidateNotNullOrEmpty]
public CompressionLevel CompressionLevel { get; set; }
- [Parameter()]
- public ArchiveFormat? Format { get; set; } = null;
+ [Parameter]
+ public ArchiveFormat? Format { get; set; }
+
+ [Parameter]
+ [ValidateNotNullOrEmpty]
+ public string? Filter { get; set; }
+
+ [Parameter]
+ public SwitchParameter Flatten { get; set; }
private readonly PathHelper _pathHelper;
@@ -156,6 +163,8 @@ protected override void EndProcessing()
// Get archive entries
// If a path causes an exception (e.g., SecurityException), _pathHelper should handle it
Debug.Assert(_paths is not null);
+ _pathHelper.Flatten = Flatten;
+ _pathHelper.Filter = Filter;
List archiveAdditions = _pathHelper.GetArchiveAdditions(_paths);
// Remove references to _paths, Path, and LiteralPath to free up memory
diff --git a/src/ExpandArchiveCommand.cs b/src/Cmdlets/ExpandArchiveCommand.cs
similarity index 97%
rename from src/ExpandArchiveCommand.cs
rename to src/Cmdlets/ExpandArchiveCommand.cs
index cc41587..7c3d4ec 100644
--- a/src/ExpandArchiveCommand.cs
+++ b/src/Cmdlets/ExpandArchiveCommand.cs
@@ -46,8 +46,6 @@ private enum ParameterSet {
private PathHelper _pathHelper;
- private System.IO.FileSystemInfo? _destinationPathInfo;
-
private bool _didCreateOutput;
private string? _sourcePath;
@@ -56,9 +54,7 @@ private enum ParameterSet {
public ExpandArchiveCommand()
{
- _didCreateOutput = false;
_pathHelper = new PathHelper(cmdlet: this);
- _destinationPathInfo = null;
}
protected override void BeginProcessing()
@@ -189,10 +185,6 @@ private void ProcessArchiveEntry(IEntry entry)
postExpandPath = postExpandPath.Remove(postExpandPath.Length - 1);
}
- // Notify the user that we are expanding the entry
- //var expandingEntryMsg = string.Format(Messages.ExpandingEntryMessage, entry.Name, postExpandPath);
- //WriteObject(expandingEntryMsg);
-
// If the entry name is invalid, write a non-terminating error and stop processing the entry
if (IsPathInvalid(postExpandPath))
{
diff --git a/src/ErrorMessages.cs b/src/Error.cs
similarity index 100%
rename from src/ErrorMessages.cs
rename to src/Error.cs
diff --git a/src/GzipArchive.cs b/src/Formats/GzipArchive.cs
similarity index 91%
rename from src/GzipArchive.cs
rename to src/Formats/GzipArchive.cs
index a695015..a9c0000 100644
--- a/src/GzipArchive.cs
+++ b/src/Formats/GzipArchive.cs
@@ -12,19 +12,19 @@ namespace Microsoft.PowerShell.Archive
{
internal class GzipArchive : IArchive
{
- private bool _disposedValue;
+ protected bool _disposedValue;
- private readonly ArchiveMode _mode;
+ protected readonly ArchiveMode _mode;
- private readonly string _path;
+ protected readonly string _path;
- private readonly FileStream _fileStream;
+ protected readonly FileStream _fileStream;
- private readonly CompressionLevel _compressionLevel;
+ protected readonly CompressionLevel _compressionLevel;
private bool _addedFile;
- private bool _didCallGetNextEntry;
+ protected bool _didCallGetNextEntry;
ArchiveMode IArchive.Mode => _mode;
@@ -38,7 +38,7 @@ public GzipArchive(string path, ArchiveMode mode, FileStream fileStream, Compres
_compressionLevel = compressionLevel;
}
- void IArchive.AddFileSystemEntry(ArchiveAddition entry)
+ public virtual void AddFileSystemEntry(ArchiveAddition entry)
{
if (_mode == ArchiveMode.Extract)
{
diff --git a/src/TarArchive.cs b/src/Formats/TarArchive.cs
similarity index 99%
rename from src/TarArchive.cs
rename to src/Formats/TarArchive.cs
index 6cc266e..4188676 100644
--- a/src/TarArchive.cs
+++ b/src/Formats/TarArchive.cs
@@ -38,7 +38,7 @@ public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
_fileStream = fileStream;
}
- void IArchive.AddFileSystemEntry(ArchiveAddition entry)
+ public void AddFileSystemEntry(ArchiveAddition entry)
{
if (_mode == ArchiveMode.Extract)
{
diff --git a/src/Formats/TarGzArchive.cs b/src/Formats/TarGzArchive.cs
new file mode 100644
index 0000000..acb22dd
--- /dev/null
+++ b/src/Formats/TarGzArchive.cs
@@ -0,0 +1,70 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Formats.Tar;
+using System.IO;
+using System.IO.Compression;
+using System.Diagnostics;
+
+namespace Microsoft.PowerShell.Archive
+{
+ internal class TarGzArchive : GzipArchive
+ {
+
+ // Use a tar archive because .tar.gz file is a compressed tar file
+ private TarArchive? _tarArchive;
+
+ private string? _tarFilePath;
+
+ private FileStream? _tarFileStream;
+
+ public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel) : base(path, mode, fileStream, compressionLevel)
+ {
+ }
+
+ public override void AddFileSystemEntry(ArchiveAddition entry)
+ {
+ if (_mode == ArchiveMode.Extract || _mode == ArchiveMode.Update) {
+ throw new ArgumentException("Adding entries to the archive is not supported in extract or update mode");
+ }
+
+ if (_tarArchive is null)
+ {
+ var outputDirectory = Path.GetDirectoryName(_path);
+ var tarFilename = Path.GetRandomFileName();
+ _tarFilePath = Path.Combine(outputDirectory, tarFilename);
+ _tarFileStream = new FileStream(_tarFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
+ _tarArchive = new TarArchive(_tarFilePath, ArchiveMode.Create, _tarFileStream);
+
+ }
+ _tarArchive.AddFileSystemEntry(entry);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ _fileStream.Dispose();
+ CompressArchive();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ _disposedValue = true;
+ }
+ }
+
+ // Performs gzip compression on _path
+ private void CompressArchive() {
+ Debug.Assert(_tarFileStream is not null);
+ _tarFileStream.Position = 0;
+ using var gzipCompressor = new GZipStream(_fileStream, _compressionLevel, true);
+ _tarFileStream.CopyTo(gzipCompressor);
+ }
+ }
+}
diff --git a/src/ZipArchive.cs b/src/Formats/ZipArchive.cs
similarity index 100%
rename from src/ZipArchive.cs
rename to src/Formats/ZipArchive.cs
diff --git a/src/IArchive.cs b/src/IArchive.cs
index c0e3b77..4d284b2 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -7,22 +7,22 @@
namespace Microsoft.PowerShell.Archive
{
- internal interface IArchive: IDisposable
+ interface IArchive: IDisposable
{
// Get what mode the archive is in
- internal ArchiveMode Mode { get; }
+ public ArchiveMode Mode { get; }
// Get the fully qualified path of the archive
- internal string Path { get; }
+ public string Path { get; }
// Add a file or folder to the archive. The entry name of the added item in the
// will be ArchiveEntry.Name.
// Throws an exception if the archive is in read mode.
- internal void AddFileSystemEntry(ArchiveAddition entry);
+ public void AddFileSystemEntry(ArchiveAddition entry);
- internal IEntry? GetNextEntry();
+ public IEntry? GetNextEntry();
// Does the archive have only a top-level directory?
- internal bool HasTopLevelDirectory();
+ public bool HasTopLevelDirectory();
}
}
diff --git a/src/PathHelper.cs b/src/PathHelper.cs
index 9dbb9af..87dd646 100644
--- a/src/PathHelper.cs
+++ b/src/PathHelper.cs
@@ -16,6 +16,12 @@ internal class PathHelper
private const string FileSystemProviderName = "FileSystem";
+ internal bool Flatten { get; set; }
+
+ internal string? Filter { get; set; }
+
+ internal WildcardPattern? _wildCardPattern;
+
internal PathHelper(PSCmdlet cmdlet)
{
_cmdlet = cmdlet;
@@ -23,13 +29,16 @@ internal PathHelper(PSCmdlet cmdlet)
internal List GetArchiveAdditions(HashSet fullyQualifiedPaths)
{
+ if (Filter is not null) {
+ _wildCardPattern = new WildcardPattern(Filter);
+ }
List archiveAdditions = new List(fullyQualifiedPaths.Count);
foreach (var path in fullyQualifiedPaths)
{
// Assume each path is valid, fully qualified, and existing
Debug.Assert(Path.Exists(path));
Debug.Assert(Path.IsPathFullyQualified(path));
- AddAdditionForFullyQualifiedPath(path, archiveAdditions);
+ AddAdditionForFullyQualifiedPath(path, archiveAdditions, entryName: null, parentMatchesFilter: false);
}
return archiveAdditions;
}
@@ -40,7 +49,7 @@ internal List GetArchiveAdditions(HashSet fullyQualifie
/// The fully qualified path
/// The list where to add the ArchiveAddition object for the path
/// If true, relative path structure will be preserved. If false, relative path structure will NOT be preserved.
- private void AddAdditionForFullyQualifiedPath(string path, List additions)
+ private void AddAdditionForFullyQualifiedPath(string path, List additions, string? entryName, bool parentMatchesFilter)
{
Debug.Assert(Path.Exists(path));
FileSystemInfo fileSystemInfo;
@@ -60,15 +69,38 @@ private void AddAdditionForFullyQualifiedPath(string path, List
fileSystemInfo = new FileInfo(path);
}
- // Get the entry name of the file or directory in the archive
- // The cmdlet will preserve the directory structure as long as the path is relative to the working directory
- var entryName = GetEntryName(fileSystemInfo, out bool doesPreservePathStructure);
- additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
+ bool doesMatchFilter = true;
+ if (!parentMatchesFilter && _wildCardPattern is not null) {
+ doesMatchFilter = _wildCardPattern.IsMatch(fileSystemInfo.Name);
+ }
+
+ // if entryName, then set it as the entry name of the file or directory in the archive
+ // The entry name will preserve the directory structure as long as the path is relative to the working directory
+ if (entryName is null) {
+ entryName = GetEntryName(fileSystemInfo, out bool doesPreservePathStructure);
+ }
+
+
+ // Number of elements in additions before adding this item and its descendents if it is a directory
+ int initialAdditions = additions.Count;
// Recurse through the child items and add them to additions
- if (fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory) && fileSystemInfo is DirectoryInfo directoryInfo) {
- AddDescendentEntries(directoryInfo: directoryInfo, additions: additions, shouldPreservePathStructure: doesPreservePathStructure);
+ if (fileSystemInfo.Attributes.HasFlag(FileAttributes.Directory) && fileSystemInfo is DirectoryInfo directoryInfo)
+ {
+ AddDescendentEntries(directoryInfo, additions, doesMatchFilter);
}
+
+ // Number of elements in additions after adding this item's descendents (if directory)
+ int finalAdditions = additions.Count;
+
+ // If the item being added is a file, finalAdditions - initialAdditions = 0
+ // If the item being added is a directory and does not have any descendent files that match the filter, finalAdditions - initialAdditions = 0
+ // If the item being added is a directory and has descendent files that match the filter, finalAdditions > initialAdditions
+
+ if (doesMatchFilter || (!doesMatchFilter && finalAdditions - initialAdditions > 0)) {
+ additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
+ }
+
}
///
@@ -77,28 +109,41 @@ private void AddAdditionForFullyQualifiedPath(string path, List
/// A fully qualifed path referring to a directory
/// Where the ArchiveAddtion object for each child item of the directory will be added
/// See above
- private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List additions, bool shouldPreservePathStructure)
+ private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List additions, bool parentMatchesFilter)
{
try
{
// pathPrefix is used to construct the entry names of the descendents of the directory
var pathPrefix = GetPrefixForPath(directoryInfo: directoryInfo);
- foreach (var childFileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
+ // If the parent directory matches the filter, then we don't have to check if each individual descendent of the directory
+ // matches the filter.
+ // This reduces the total number of method calls
+ SearchOption searchOption = parentMatchesFilter ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+ foreach (var childFileSystemInfo in directoryInfo.EnumerateFileSystemInfos("*", searchOption))
{
string entryName;
- // If the cmdlet should preserve the path structure, then use the relative path
- if (shouldPreservePathStructure)
+ if (Flatten)
{
- entryName = GetEntryName(childFileSystemInfo, out bool doesPreservePathStructure);
- Debug.Assert(doesPreservePathStructure);
+ entryName = childFileSystemInfo.Name;
+ } else
+ {
+ entryName = GetEntryNameUsingPrefix(path: childFileSystemInfo.FullName, prefix: pathPrefix);
}
- // Otherwise, get the entry name using the prefix
+
+
+ // Add an entry for each descendent of the directory
+ if (parentMatchesFilter)
+ {
+ // If the parent directory matches the filter, all its contents are included in the archive
+ // Just add the entry for each child without needing to check whether the child matches the filter
+ additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: childFileSystemInfo));
+ }
else
{
- entryName = GetEntryNameUsingPrefix(path: childFileSystemInfo.FullName, prefix: pathPrefix);
+ // If the parent directory does not match the filter, we want to call this function
+ // because this function will check if the name of the child matches the filter and if so, will add it
+ AddAdditionForFullyQualifiedPath(childFileSystemInfo.FullName, additions, entryName, parentMatchesFilter: false);
}
- // Add an entry for each descendent of the directory
- additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: childFileSystemInfo));
}
}
// Write a non-terminating error if a securityException occurs
@@ -121,7 +166,7 @@ private string GetEntryName(FileSystemInfo fileSystemInfo, out bool doesPreserve
string entryName;
doesPreservePathStructure = false;
// If the path is relative to the current working directory, return the relative path as name
- if (TryGetPathRelativeToCurrentWorkingDirectory(path: fileSystemInfo.FullName, out var relativePath))
+ if (!Flatten && TryGetPathRelativeToCurrentWorkingDirectory(path: fileSystemInfo.FullName, out var relativePath))
{
Debug.Assert(relativePath is not null);
doesPreservePathStructure = true;
diff --git a/src/TarGzArchive.cs b/src/TarGzArchive.cs
deleted file mode 100644
index ec1f2cf..0000000
--- a/src/TarGzArchive.cs
+++ /dev/null
@@ -1,124 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System;
-using System.Collections.Generic;
-using System.Formats.Tar;
-using System.IO;
-using System.IO.Compression;
-using System.Diagnostics;
-
-namespace Microsoft.PowerShell.Archive
-{
- internal class TarGzArchive : IArchive
- {
- private bool _disposedValue;
-
- private readonly ArchiveMode _mode;
-
- private readonly string _path;
-
- private readonly FileStream _fileStream;
-
- private readonly CompressionLevel _compressionLevel;
-
- // Use a tar archive because .tar.gz file is a compressed tar file
- private TarArchive? _tarArchive;
-
- private string? _tarFilePath;
-
- ArchiveMode IArchive.Mode => _mode;
-
- string IArchive.Path => _path;
-
- public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel)
- {
- _mode = mode;
- _path = path;
- _fileStream = fileStream;
- _compressionLevel = compressionLevel;
- }
-
- void IArchive.AddFileSystemEntry(ArchiveAddition entry)
- {
- if (_mode == ArchiveMode.Extract) {
- throw new ArgumentException("Adding entries to the archive is not supported in extract mode");
- }
-
- if (_mode == ArchiveMode.Create) {
- if (_tarArchive is null) {
- _tarArchive = new TarArchive(_path, ArchiveMode.Create, _fileStream);
- }
- (_tarArchive as IArchive).AddFileSystemEntry(entry);
- }
- }
-
- IEntry? IArchive.GetNextEntry()
- {
- if (_mode == ArchiveMode.Create || _mode == ArchiveMode.Update) {
- throw new ArgumentException("Getting the entries in an archive is not supported in Create or Update mode");
- }
- return null;
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposedValue)
- {
- if (disposing)
- {
- // TODO: dispose managed state (managed objects)
- _fileStream.Dispose();
- CompressArchive();
- }
-
- // TODO: free unmanaged resources (unmanaged objects) and override finalizer
- // TODO: set large fields to null
- _disposedValue = true;
- }
- }
-
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
- }
-
- bool IArchive.HasTopLevelDirectory()
- {
- throw new NotSupportedException();
- }
-
- // Performs gzip compression on _path
- private void CompressArchive() {
- //using var destinationFileStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
- _fileStream.Position = 0;
- using var gzipDecompressor = new GZipStream(_fileStream, _compressionLevel, true);
- _fileStream.CopyTo(gzipDecompressor);
- }
-
- internal class TarGzArchiveEntry : IEntry {
-
- private TarGzArchive _gzipArchive;
-
- // Gzip has no concept of entries, so getting the entry name is not supported
- string IEntry.Name => throw new NotSupportedException();
-
- // Gzip does not compress directories, so this is always false
- bool IEntry.IsDirectory => false;
-
- public TarGzArchiveEntry(TarGzArchive gzipArchive)
- {
- _gzipArchive = gzipArchive;
- }
-
- void IEntry.ExpandTo(string destinationPath)
- {
- using var destinationFileStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
- using var gzipDecompressor = new GZipStream(_gzipArchive._fileStream, CompressionMode.Decompress);
- gzipDecompressor.CopyTo(destinationFileStream);
- }
- }
- }
-}
From 296864452f599b14834238c89bcde1a8b97f515f Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 16 Aug 2022 10:04:12 -0700
Subject: [PATCH 28/34] added support for expanding tar.gz archives
---
src/ArchiveFactory.cs | 1 +
src/Cmdlets/ExpandArchiveCommand.cs | 21 ++----
src/Formats/GzipArchive.cs | 25 +++----
src/Formats/TarArchive.cs | 33 +--------
src/Formats/TarGzArchive.cs | 104 ++++++++++++++++++++++------
src/Formats/ZipArchive.cs | 22 ------
src/IArchive.cs | 3 -
7 files changed, 100 insertions(+), 109 deletions(-)
diff --git a/src/ArchiveFactory.cs b/src/ArchiveFactory.cs
index bb91531..6502506 100644
--- a/src/ArchiveFactory.cs
+++ b/src/ArchiveFactory.cs
@@ -34,6 +34,7 @@ internal static bool TryGetArchiveFormatFromExtension(string path, out ArchiveFo
".zip" => ArchiveFormat.Zip,
".tar" => ArchiveFormat.Tar,
".gz" => path.EndsWith(".tar.gz") ? ArchiveFormat.Tgz : null,
+ ".tgz" => ArchiveFormat.Tgz,
_ => null
};
return archiveFormat is not null;
diff --git a/src/Cmdlets/ExpandArchiveCommand.cs b/src/Cmdlets/ExpandArchiveCommand.cs
index 7c3d4ec..e59e9d9 100644
--- a/src/Cmdlets/ExpandArchiveCommand.cs
+++ b/src/Cmdlets/ExpandArchiveCommand.cs
@@ -324,23 +324,14 @@ private string DetermineDestinationPath(IArchive archive)
var workingDirectory = SessionState.Path.CurrentFileSystemLocation.ProviderPath;
string? destinationDirectory = null;
- // If the archive has a single top-level directory only, the destination will be: "working directory"
- // This makes it easier for the cmdlet to expand the directory without needing addition checks
- if (archive.HasTopLevelDirectory())
+ var filename = System.IO.Path.GetFileName(archive.Path);
+ // If filename does have an exension, remove the extension and set the filename minus extension as destinationDirectory
+ if (System.IO.Path.GetExtension(filename) != string.Empty)
{
- destinationDirectory = workingDirectory;
- }
- // Otherwise, the destination path will be: "working directory/archive file name"
- else
- {
- var filename = System.IO.Path.GetFileName(archive.Path);
- // If filename does have an exension, remove the extension and set the filename minus extension as destinationDirectory
- if (System.IO.Path.GetExtension(filename) != string.Empty)
- {
- int indexOfLastPeriod = filename.LastIndexOf('.');
- destinationDirectory = filename.Substring(0, indexOfLastPeriod);
- }
+ int indexOfLastPeriod = filename.LastIndexOf('.');
+ destinationDirectory = filename.Substring(0, indexOfLastPeriod);
}
+
if (destinationDirectory is null)
{
diff --git a/src/Formats/GzipArchive.cs b/src/Formats/GzipArchive.cs
index a9c0000..d2c08d2 100644
--- a/src/Formats/GzipArchive.cs
+++ b/src/Formats/GzipArchive.cs
@@ -12,19 +12,19 @@ namespace Microsoft.PowerShell.Archive
{
internal class GzipArchive : IArchive
{
- protected bool _disposedValue;
+ private bool _disposedValue;
- protected readonly ArchiveMode _mode;
+ private readonly ArchiveMode _mode;
- protected readonly string _path;
+ private readonly string _path;
- protected readonly FileStream _fileStream;
+ private readonly FileStream _fileStream;
- protected readonly CompressionLevel _compressionLevel;
+ private readonly CompressionLevel _compressionLevel;
private bool _addedFile;
- protected bool _didCallGetNextEntry;
+ private bool _didCallGetNextEntry;
ArchiveMode IArchive.Mode => _mode;
@@ -38,7 +38,7 @@ public GzipArchive(string path, ArchiveMode mode, FileStream fileStream, Compres
_compressionLevel = compressionLevel;
}
- public virtual void AddFileSystemEntry(ArchiveAddition entry)
+ public void AddFileSystemEntry(ArchiveAddition entry)
{
if (_mode == ArchiveMode.Extract)
{
@@ -61,7 +61,7 @@ public virtual void AddFileSystemEntry(ArchiveAddition entry)
_addedFile = true;
}
- IEntry? IArchive.GetNextEntry()
+ public IEntry? GetNextEntry()
{
// Gzip has no concept of entries
if (!_didCallGetNextEntry) {
@@ -71,7 +71,7 @@ public virtual void AddFileSystemEntry(ArchiveAddition entry)
return null;
}
- protected virtual void Dispose(bool disposing)
+ private void Dispose(bool disposing)
{
if (!_disposedValue)
{
@@ -94,11 +94,6 @@ public void Dispose()
GC.SuppressFinalize(this);
}
- bool IArchive.HasTopLevelDirectory()
- {
- throw new NotSupportedException();
- }
-
internal class GzipArchiveEntry : IEntry {
private GzipArchive _gzipArchive;
@@ -116,7 +111,7 @@ public GzipArchiveEntry(GzipArchive gzipArchive)
void IEntry.ExpandTo(string destinationPath)
{
- using var destinationFileStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ using var destinationFileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None);
using var gzipDecompressor = new GZipStream(_gzipArchive._fileStream, CompressionMode.Decompress);
gzipDecompressor.CopyTo(destinationFileStream);
}
diff --git a/src/Formats/TarArchive.cs b/src/Formats/TarArchive.cs
index 4188676..09b2938 100644
--- a/src/Formats/TarArchive.cs
+++ b/src/Formats/TarArchive.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
using System.Formats.Tar;
using System.IO;
using System.Diagnostics;
@@ -66,7 +65,7 @@ public void AddFileSystemEntry(ArchiveAddition entry)
_tarWriter.WriteEntry(fileName: entry.FileSystemInfo.FullName, entryName: entryName);
}
- IEntry? IArchive.GetNextEntry()
+ public IEntry? GetNextEntry()
{
// If _tarReader is null, create it
if (_tarReader is null)
@@ -147,36 +146,6 @@ public void Dispose()
GC.SuppressFinalize(this);
}
- bool IArchive.HasTopLevelDirectory()
- {
- // Go through each entry and see if it is a top-level entry
- _tarReader = new TarReader(_fileStream, leaveOpen: true);
-
- int topLevelDirectoriesCount = 0;
- var entry = _tarReader.GetNextEntry();
- while (entry is not null) {
-
- if (entry.EntryType == TarEntryType.Directory)
- {
- topLevelDirectoriesCount++;
- if (topLevelDirectoriesCount > 1)
- {
- break;
- }
- } else
- {
- _tarReader.Dispose();
- _tarReader = null;
- return false;
- }
- entry = _tarReader.GetNextEntry();
- }
-
- _tarReader.Dispose();
- _tarReader = null;
- return topLevelDirectoriesCount == 1;
- }
-
internal class TarArchiveEntry : IEntry {
// Underlying object is System.Formats.Tar.TarEntry
diff --git a/src/Formats/TarGzArchive.cs b/src/Formats/TarGzArchive.cs
index acb22dd..54d908f 100644
--- a/src/Formats/TarGzArchive.cs
+++ b/src/Formats/TarGzArchive.cs
@@ -2,55 +2,123 @@
// Licensed under the MIT License.
using System;
-using System.Collections.Generic;
-using System.Formats.Tar;
using System.IO;
using System.IO.Compression;
using System.Diagnostics;
namespace Microsoft.PowerShell.Archive
{
- internal class TarGzArchive : GzipArchive
+ internal class TarGzArchive : IArchive
{
// Use a tar archive because .tar.gz file is a compressed tar file
private TarArchive? _tarArchive;
+ private FileStream? _tarFileStream;
+
private string? _tarFilePath;
- private FileStream? _tarFileStream;
+ private string _path;
+
+ private bool _disposedValue;
+
+ private bool _didCallGetNextEntry;
+
+ private readonly ArchiveMode _mode;
+
+ private readonly FileStream _fileStream;
+
+ private readonly CompressionLevel _compressionLevel;
+
+ public string Path => _path;
- public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel) : base(path, mode, fileStream, compressionLevel)
+ ArchiveMode IArchive.Mode => _mode;
+
+ string IArchive.Path => _path;
+
+ public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel)
{
+ _path = path;
+ _mode = mode;
+ _fileStream = fileStream;
+ _compressionLevel = compressionLevel;
}
- public override void AddFileSystemEntry(ArchiveAddition entry)
+ public void AddFileSystemEntry(ArchiveAddition entry)
{
- if (_mode == ArchiveMode.Extract || _mode == ArchiveMode.Update) {
+ if (_mode == ArchiveMode.Extract || _mode == ArchiveMode.Update)
+ {
throw new ArgumentException("Adding entries to the archive is not supported in extract or update mode");
}
if (_tarArchive is null)
{
- var outputDirectory = Path.GetDirectoryName(_path);
- var tarFilename = Path.GetRandomFileName();
- _tarFilePath = Path.Combine(outputDirectory, tarFilename);
- _tarFileStream = new FileStream(_tarFilePath, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None);
+ // This will create a temp file and return the path
+ _tarFilePath = System.IO.Path.GetTempFileName();
+ // When creating the stream, the file already exists
+ _tarFileStream = new FileStream(_tarFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
_tarArchive = new TarArchive(_tarFilePath, ArchiveMode.Create, _tarFileStream);
}
_tarArchive.AddFileSystemEntry(entry);
}
- protected override void Dispose(bool disposing)
+ public IEntry? GetNextEntry()
+ {
+ if (_mode == ArchiveMode.Create)
+ {
+ throw new ArgumentException("Getting next entry is not supported when the archive is in Create mode");
+ }
+
+ if (_tarArchive is null)
+ {
+ // Create a Gzip archive
+ using var gzipArchive = new GzipArchive(_path, _mode, _fileStream, _compressionLevel);
+ // Where to put the tar file when expanding the tar.gz archive
+ _tarFilePath = System.IO.Path.GetTempFileName();
+ // Expand the gzip portion
+ var entry = gzipArchive.GetNextEntry();
+ Debug.Assert(entry is not null);
+ entry.ExpandTo(_tarFilePath);
+ // Create a TarArchive pointing to the newly expanded out tar file from the tar.gz file
+ FileStream tarFileStream = new FileStream(_tarFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
+ _tarArchive = new TarArchive(_tarFilePath, ArchiveMode.Extract, tarFileStream);
+ }
+ return _tarArchive?.GetNextEntry();
+ }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ // Performs gzip compression on _path
+ private void CompressArchive() {
+ Debug.Assert(_tarFilePath is not null);
+ _tarFileStream = new FileStream(_tarFilePath, FileMode.Open, FileAccess.Read);
+ using var gzipCompressor = new GZipStream(_fileStream, _compressionLevel, true);
+ _tarFileStream.CopyTo(gzipCompressor);
+ _tarFileStream.Dispose();
+ }
+
+ private void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
- // TODO: dispose managed state (managed objects)
+ // Do this before compression because disposing a tar archive will add necessary EOF markers
+ _tarArchive?.Dispose();
+ if (_mode == ArchiveMode.Create) {
+ CompressArchive();
+ }
_fileStream.Dispose();
- CompressArchive();
+ if (_tarFilePath is not null) {
+ // Delete the tar file created in the process of created the tar.gz file
+ File.Delete(_tarFilePath);
+ }
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
@@ -58,13 +126,5 @@ protected override void Dispose(bool disposing)
_disposedValue = true;
}
}
-
- // Performs gzip compression on _path
- private void CompressArchive() {
- Debug.Assert(_tarFileStream is not null);
- _tarFileStream.Position = 0;
- using var gzipCompressor = new GZipStream(_fileStream, _compressionLevel, true);
- _tarFileStream.CopyTo(gzipCompressor);
- }
}
}
diff --git a/src/Formats/ZipArchive.cs b/src/Formats/ZipArchive.cs
index 3954831..5e2a41e 100644
--- a/src/Formats/ZipArchive.cs
+++ b/src/Formats/ZipArchive.cs
@@ -152,28 +152,6 @@ public void Dispose()
GC.SuppressFinalize(this);
}
- bool IArchive.HasTopLevelDirectory()
- {
- int topLevelDirectoriesCount = 0;
- foreach (var entry in _zipArchive.Entries)
- {
- if (entry.FullName.EndsWith(ZipArchiveDirectoryPathTerminator) &&
- entry.FullName.LastIndexOf(ZipArchiveDirectoryPathTerminator, entry.FullName.Length - 2) == -1)
- {
- topLevelDirectoriesCount++;
- if (topLevelDirectoriesCount > 1)
- {
- break;
- }
- } else
- {
- return false;
- }
- }
-
- return topLevelDirectoriesCount == 1;
- }
-
internal class ZipArchiveEntry : IEntry
{
// Underlying object is System.IO.Compression.ZipArchiveEntry
diff --git a/src/IArchive.cs b/src/IArchive.cs
index 4d284b2..18cfc75 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -21,8 +21,5 @@ interface IArchive: IDisposable
public void AddFileSystemEntry(ArchiveAddition entry);
public IEntry? GetNextEntry();
-
- // Does the archive have only a top-level directory?
- public bool HasTopLevelDirectory();
}
}
From 964406b336148cfdeecad1bb5c0a3622a3ee0ab9 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 16 Aug 2022 12:09:50 -0700
Subject: [PATCH 29/34] merged with preview1 branch
---
.../Should-BeArchiveOnlyContaining.psm1 | 2 +-
Tests/Compress-Archive.Tests.ps1 | 1857 ++++++++---------
src/Cmdlets/CompressArchiveCommand.cs | 8 +-
src/Cmdlets/ExpandArchiveCommand.cs | 2 +-
src/Localized/Messages.resx | 1 +
src/Microsoft.PowerShell.Archive.psd1 | 64 +-
6 files changed, 963 insertions(+), 971 deletions(-)
diff --git a/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1 b/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
index 6df4399..0ef8d23 100644
--- a/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
+++ b/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
@@ -25,7 +25,7 @@ function Should-BeArchiveOnlyContaining {
if ($Format -eq "Tar") {
return Should-BeTarArchiveOnlyContaining -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$Negate -Because $Because -LiteralPath:$LiteralPath -CallerSessionState $CallerSessionState
}
- return return [pscustomobject]@{
+ return [pscustomobject]@{
Succeeded = $false
FailureMessage = "Format ${Format} is not supported."
}
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 67f9711..5f56178 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -2,938 +2,929 @@
# Licensed under the MIT License.
BeforeDiscovery {
- # Loads and registers custom assertion. Ignores usage of unapproved verb with -DisableNameChecking
- Import-Module "$PSScriptRoot/Assertions/Should-BeZipArchiveOnlyContaining.psm1" -DisableNameChecking
- Import-Module "$PSScriptRoot/Assertions/Should-BeTarArchiveOnlyContaining.psm1" -DisableNameChecking
- Import-Module "$PSScriptRoot/Assertions/Should-BeArchiveOnlyContaining.psm1" -DisableNameChecking
+ # Loads and registers custom assertion. Ignores usage of unapproved verb with -DisableNameChecking
+ Import-Module "$PSScriptRoot/Assertions/Should-BeZipArchiveOnlyContaining.psm1" -DisableNameChecking
+ Import-Module "$PSScriptRoot/Assertions/Should-BeTarArchiveOnlyContaining.psm1" -DisableNameChecking
+ Import-Module "$PSScriptRoot/Assertions/Should-BeArchiveOnlyContaining.psm1" -DisableNameChecking
}
- Describe("Microsoft.PowerShell.Archive tests") {
- BeforeAll {
-
- $originalProgressPref = $ProgressPreference
- $ProgressPreference = "SilentlyContinue"
- $originalPSModulePath = $env:PSModulePath
-
- function Add-FileExtensionBasedOnFormat {
- Param (
- [string] $Path,
- [string] $Format
- )
-
- if ($Format -eq "Zip") {
- return $Path += ".zip"
- }
- if ($Format -eq "Tar") {
- return $Path += ".tar"
- }
- throw "Format type is not supported"
- }
- }
-
- AfterAll {
- $global:ProgressPreference = $originalProgressPref
- $env:PSModulePath = $originalPSModulePath
- }
-
- Context "Parameter set validation tests" {
- BeforeAll {
- # Set up files for tests
- New-Item TestDrive:/SourceDir -Type Directory
- "Some Data" | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- New-Item TestDrive:/EmptyDirectory -Type Directory | Out-Null
- }
-
-
- It "Validate errors from Compress-Archive with null and empty values for Path, LiteralPath, and DestinationPath parameters" -ForEach @(
- @{ Path = $null; DestinationPath = "TestDrive:/archive1.zip" }
- @{ Path = "TestDrive:/SourceDir"; DestinationPath = $null }
- @{ Path = $null; DestinationPath = $null }
- @{ Path = ""; DestinationPath = "TestDrive:/archive1.zip" }
- @{ Path = "TestDrive:/SourceDir"; DestinationPath = "" }
- @{ Path = ""; DestinationPath = "" }
- ) {
- try
- {
- Compress-Archive -Path $Path -DestinationPath $DestinationPath
- throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
-
- try
- {
- Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath
- throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Validate errors from Compress-Archive when invalid path is supplied for Path or LiteralPath parameters" -ForEach @(
- @{ Path = "Variable:/PWD" }
- @{ Path = @("TestDrive:/", "Variable:/PWD") }
- ) {
- $DestinationPath = "TestDrive:/archive2.zip"
-
- Compress-Archive -Path $Path -DestinationPath $DestinationPath -ErrorAction SilentlyContinue -ErrorVariable error
- $error.Count | Should -Be 1
- $error[0].FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- Remove-Item -Path $DestinationPath
-
- Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath -ErrorAction SilentlyContinue -ErrorVariable error
- $error.Count | Should -Be 1
- $error[0].FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- Remove-Item -Path $DestinationPath
- }
-
- It "Throws terminating error when non-existing path is supplied for Path or LiteralPath parameters" -ForEach @(
- @{ Path = "TestDrive:/DoesNotExist" }
- @{ Path = @("TestDrive:/", "TestDrive:/DoesNotExist") }
- ) -Tag this2 {
- $DestinationPath = "TestDrive:/archive3.zip"
-
- try
- {
- Compress-Archive -Path $Path -DestinationPath $DestinationPath
- throw "Failed to validate that an invalid Path was supplied as input to Compress-Archive cmdlet."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "PathNotFound,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
-
- try
- {
- Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath
- throw "Failed to validate that an invalid LiteralPath was supplied as input to Compress-Archive cmdlet."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "PathNotFound,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Validate error from Compress-Archive when duplicate paths are supplied as input to Path parameter" {
- $sourcePath = @(
- "TestDrive:/SourceDir/Sample-1.txt",
- "TestDrive:/SourceDir/Sample-1.txt")
- $destinationPath = "TestDrive:/DuplicatePaths.zip"
-
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect that duplicate Path $sourcePath is supplied as input to Path parameter."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "DuplicatePaths,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Validate error from Compress-Archive when duplicate paths are supplied as input to LiteralPath parameter" {
- $sourcePath = @(
- "TestDrive:/SourceDir/Sample-1.txt",
- "TestDrive:/SourceDir/Sample-1.txt")
- $destinationPath = "TestDrive:/DuplicatePaths.zip"
-
- try
- {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect that duplicate Path $sourcePath is supplied as input to LiteralPath parameter."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "DuplicatePaths,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- ## From 504
- It "Validate that Source Path can be at SystemDrive location" -Skip {
- $sourcePath = "$env:SystemDrive/SourceDir"
- $destinationPath = "TestDrive:/SampleFromSystemDrive.zip"
- New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux
- "Some Data" | Out-File -FilePath $sourcePath/SampleSourceFileForArchive.txt
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Remove-Item "$sourcePath" -Force -Recurse -ErrorAction SilentlyContinue
- }
- }
-
- # This cannot happen in -WriteMode Create because another error will be throw before
- It "Throws an error when Path and DestinationPath are the same" -Skip {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = $sourcePath
-
- try {
- # Note the cmdlet performs validation on $destinationPath
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect an error when Path and DestinationPath are the same"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws an error when Path and DestinationPath are the same and -Update is specified" {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = $sourcePath
-
- try {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
- throw "Failed to detect an error when Path and DestinationPath are the same and -Update is specified"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws an error when Path and DestinationPath are the same and -Overwrite is specified" {
- $sourcePath = "TestDrive:/EmptyDirectory"
- $destinationPath = $sourcePath
-
- try {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
- throw "Failed to detect an error when Path and DestinationPath are the same and -Overwrite is specified"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws an error when LiteralPath and DestinationPath are the same" -Skip {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = $sourcePath
-
- try {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect an error when LiteralPath and DestinationPath are the same"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws an error when LiteralPath and DestinationPath are the same and -Update is specified" {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = $sourcePath
-
- try {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Update
- throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Update is specified"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws an error when LiteralPath and DestinationPath are the same and -Overwrite is specified" {
- $sourcePath = "TestDrive:/EmptyDirectory"
- $destinationPath = $sourcePath
-
- try {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
- throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Overwrite is specified"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws an error when an invalid path is supplied to DestinationPath" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "Variable:/PWD"
-
- try {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect an error when an invalid path is supplied to DestinationPath"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
- }
-
- Context "WriteMode tests" {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- }
-
- It "Throws a terminating error when an incorrect value is supplied to -WriteMode" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive1.zip"
-
- try {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode mode
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "-WriteMode Create works" -Tag td1 {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive1.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Verbose
- if ($IsWindows) {
- $t = Convert-Path $destinationPath
- 7z l "${t}" | Write-Verbose -Verbose
- }
- $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/Sample-1.txt')
-
-
- }
- }
-
- Context "Basic functional tests" -ForEach @(
- @{Format = "Zip"},
- @{Format = "Tar"}
- ) {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory | Out-Null
- New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
- New-Item TestDrive:/SourceDir/ChildDir-2 -Type Directory | Out-Null
- New-Item TestDrive:/SourceDir/ChildEmptyDir -Type Directory | Out-Null
-
- # create an empty directory
- New-Item TestDrive:/EmptyDir -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
- $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-2/Sample-3.txt
-
- "Hello, World!" | Out-File -FilePath $TestDrive$($DS)HelloWorld.txt
-
- # Create a zero-byte file
- New-Item TestDrive:/EmptyFile -Type File | Out-Null
-
- # Create a file whose last write time is before 1980
- $content | Out-File -FilePath TestDrive:/OldFile.txt
- Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
-
- # Create a directory whose last write time is before 1980
- New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
- Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
- }
-
- It "Compresses a single file with format " {
- $sourcePath = "TestDrive:/SourceDir/ChildDir-1/Sample-2.txt"
- $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive1" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('Sample-2.txt') -Format $Format
- }
-
- It "Compresses a non-empty directory with format " -Tag td1 {
- $sourcePath = "TestDrive:/SourceDir/ChildDir-1"
- $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive2" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('ChildDir-1/', 'ChildDir-1/Sample-2.txt') -Format $Format
- }
-
- It "Compresses an empty directory with format " {
- $sourcePath = "TestDrive:/EmptyDir"
- $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive3" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('EmptyDir/') -Format $Format
- }
-
- It "Compresses multiple files with format " {
- $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt")
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive4" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt') -Format $Format
- }
-
- It "Compresses multiple files and a single empty directory with format " {
- $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt",
- "TestDrive:/SourceDir/ChildEmptyDir")
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive5" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt', 'ChildEmptyDir/') -Format $Format
- }
-
- It "Compresses multiple files and a single non-empty directory with format " {
- $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt",
- "TestDrive:/SourceDir/ChildDir-2")
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive6.zip" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-2/', 'ChildDir-2/Sample-3.txt') -Format $Format
- }
-
- It "Compresses multiple files and non-empty directories with format " {
- $sourcePath = @("TestDrive:/HelloWorld.txt", "TestDrive:/SourceDir/Sample-1.txt",
- "TestDrive:/SourceDir/ChildDir-1", "TestDrive:/SourceDir/ChildDir-2")
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive7.zip" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
- 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt') -Format $Format
- }
-
- It "Compresses multiple files, non-empty directories, and an empty directory with format " {
- $sourcePath = @("TestDrive:/HelloWorld.txt", "TestDrive:/SourceDir/Sample-1.txt",
- "TestDrive:/SourceDir/ChildDir-1", "TestDrive:/SourceDir/ChildDir-2", "TestDrive:/SourceDir/ChildEmptyDir")
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive8.zip" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
- 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "ChildEmptyDir/") -Format $Format
- }
-
- It "Compresses a directory containing files, non-empty directories, and an empty directory can be compressed with format " {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive9.zip" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $contents = @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt',
- 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt')
- $destinationPath | Should -BeArchiveOnlyContaining $contents -Format $Format
- }
-
- It "Compresses a zero-byte file with format " {
- $sourcePath = "TestDrive:/EmptyFile"
- $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive10.zip" -Format $Format
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
- $destinationPath | Should -BeArchiveOnlyContaining @('EmptyFile') -Format $Format
- }
- }
-
- Context "Zip-specific tests" {
- BeforeAll {
- # Create a file whose last write time is before 1980
- $content | Out-File -FilePath TestDrive:/OldFile.txt
- Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
-
- # Create a directory whose last write time is before 1980
- New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
- Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
- }
-
- It "Compresses a file whose last write time is before 1980" {
- $sourcePath = "$TestDrive$($DS)OldFile.txt"
- $destinationPath = "$TestDrive$($DS)archive11.zip"
-
- # Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
- $dateProperty.Year | Should -BeLessThan 1980
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('OldFile.txt')
-
- # Get the archive
- $fileMode = [System.IO.FileMode]::Open
- $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
- $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
- $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
- $entry = $archive.GetEntry("OldFile.txt")
- $entry | Should -Not -BeNullOrEmpty
-
- $entry.LastWriteTime.Year | Should -BeExactly 1980
- $entry.LastWriteTime.Month| Should -BeExactly 1
- $entry.LastWriteTime.Day | Should -BeExactly 1
- $entry.LastWriteTime.Hour | Should -BeExactly 0
- $entry.LastWriteTime.Minute | Should -BeExactly 0
- $entry.LastWriteTime.Second | Should -BeExactly 0
- $entry.LastWriteTime.Millisecond | Should -BeExactly 0
-
-
- $archive.Dispose()
- $archiveStream.Dispose()
- }
-
- It "Compresses a directory whose last write time is before 1980 with format " {
- $sourcePath = "TestDrive:/olddirectory"
- $destinationPath = "${TestDrive}/archive12.zip"
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- Test-ZipArchive $destinationPath @('olddirectory/')
-
- # Get the archive
- $fileMode = [System.IO.FileMode]::Open
- $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
- $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
- $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
- $entry = $archive.GetEntry("olddirectory/")
- $entry | Should -Not -BeNullOrEmpty
-
- $entry.LastWriteTime.Year | Should -BeExactly 1980
- $entry.LastWriteTime.Month| Should -BeExactly 1
- $entry.LastWriteTime.Day | Should -BeExactly 1
- $entry.LastWriteTime.Hour | Should -BeExactly 0
- $entry.LastWriteTime.Minute | Should -BeExactly 0
- $entry.LastWriteTime.Second | Should -BeExactly 0
- $entry.LastWriteTime.Millisecond | Should -BeExactly 0
-
-
- $archive.Dispose()
- $archiveStream.Dispose()
- }
-
- It "Writes a warning when compressing a file whose last write time is before 1980 with format " {
- $sourcePath = "TestDrive:/OldFile.txt"
- $destinationPath = "${TestDrive}/archive13.zip"
-
- # Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
- $dateProperty.Year | Should -BeLessThan 1980
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
- $warnings.Length | Should -Be 1
- }
-
- It "Writes a warning when compresing a directory whose last write time is before 1980 with format " {
- $sourcePath = "TestDrive:/olddirectory"
- $destinationPath = "${TestDrive}/archive14.zip"
-
- # Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
- $dateProperty.Year | Should -BeLessThan 1980
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
- $warnings.Length | Should -Be 1
- }
- }
-
- Context "DestinationPath and -WriteMode Overwrite tests" {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
-
- New-Item TestDrive:/archive3.zip -Type Directory | Out-Null
-
- New-Item TestDrive:/EmptyDirectory -Type Directory | Out-Null
-
- # Create a read-only archive
- $readOnlyArchivePath = "TestDrive:/readonly.zip"
- Compress-Archive -Path TestDrive:/SourceDir/Sample-1.txt -DestinationPath $readOnlyArchivePath
- Set-ItemProperty -Path $readOnlyArchivePath -Name IsReadOnly -Value $true
-
- # Create TestDrive:/archive.zip
- Compress-Archive -Path TestDrive:/SourceDir/Sample-1.txt -DestinationPath "TestDrive:/archive.zip"
-
- # Create Sample-2.txt
- $content | Out-File -FilePath TestDrive:/Sample-2.txt
- }
-
- It "Throws an error when archive file already exists and -Update and -Overwrite parameters are not specified" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive1.zip"
-
- try
- {
- "Some Data" > $destinationPath
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- throw "Failed to validate that an archive file format $destinationPath already exists and -Update switch parameter is not specified."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "DestinationExists,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws a terminating error when archive file exists and -Update is specified but the archive is read-only" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/readonly.zip"
-
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
- throw "Failed to detect an that an error was thrown when archive $destinationPath already exists but it is read-only and -WriteMode Update is specified."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "ArchiveReadOnly,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws a terminating error when archive already exists as a directory and -Update and -Overwrite parameters are not specified" {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = "TestDrive:/SourceDir"
-
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect an error was thrown when archive $destinationPath exists as a directory and -WriteMode Update or -WriteMode Overwrite is not specified."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "DestinationExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws a terminating error when DestinationPath is a directory and -Update is specified" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive3.zip"
-
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
- throw "Failed to validate that a directory $destinationPath exists and -Update switch parameter is specified."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "DestinationExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws a terminating error when DestinationPath is a folder containing at least 1 item and Overwrite is specified" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:"
-
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
- throw "Failed to detect an error when $destinationPath is an existing directory containing at least 1 item and -Overwrite switch parameter is specified."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "DestinationIsNonEmptyDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- It "Throws a terminating error when archive does not exist and -Update mode is specified" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive2.zip"
-
- try
- {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
- throw "Failed to validate that an archive file format $destinationPath does not exist and -Update switch parameter is specified."
- }
- catch
- {
- $_.FullyQualifiedErrorId | Should -Be "ArchiveDoesNotExist,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
- ## Overwrite tests
- It "Throws an error when trying to overwrite an empty directory, which is the working directory" {
- $sourcePath = "TestDrive:/Sample-2.txt"
- $destinationPath = "TestDrive:/EmptyDirectory"
-
- Push-Location $destinationPath
-
- try {
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "CannotOverwriteWorkingDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
-
- Pop-Location
- }
-
- It "Overwrites a directory containing no items when -Overwrite is specified" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/EmptyDirectory"
-
- # Ensure $destinationPath is a directory
- Test-Path $destinationPath -PathType Container | Should -Be $true
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
-
- # Ensure $destinationPath is now a file
- Test-Path $destinationPath -PathType Leaf | Should -Be $true
- }
-
- It "Overwrites an archive that already exists" {
- $destinationPath = "TestDrive:/archive.zip"
-
- # Ensure the original archive contains Sample-1.txt
- $destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-1.txt")
-
- # Overwrite the archive
- $sourcePath = "TestDrive:/Sample-2.txt"
- Compress-Archive -Path $sourcePath -DestinationPath "TestDrive:/archive.zip" -WriteMode Overwrite
-
- # Ensure the original entries and different than the new entries
- $destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-2.txt")
- }
- }
-
- Context "Relative Path tests" {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory | Out-Null
- New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
- }
-
- # From 568
- It "Validate that relative path can be specified as Path parameter of Compress-Archive cmdlet" {
- $sourcePath = "./SourceDir"
- $destinationPath = "RelativePathForPathParameter.zip"
- try
- {
- Push-Location TestDrive:/
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Pop-Location
- }
- }
-
- # From 582
- It "Validate that relative path can be specified as LiteralPath parameter of Compress-Archive cmdlet" {
- $sourcePath = "./SourceDir"
- $destinationPath = "RelativePathForLiteralPathParameter.zip"
- try
- {
- Push-Location TestDrive:/
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Pop-Location
- }
- }
-
- # From 596
- It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "./RelativePathForDestinationPathParameter.zip"
- try
- {
- Push-Location TestDrive:/
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Pop-Location
- }
- }
- }
-
- Context "Special and Wildcard Characters Tests" {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- New-Item -LiteralPath "$TestDrive$($DS)Source[]Dir" -Type Directory | Out-Null
- $content | Out-File -FilePath $TestDrive$($DS)SourceDir$($DS)file1[].txt
- }
-
- It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = "TestDrive:/Sample[]SingleFile.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path -LiteralPath $destinationPath | Should -Be $true
- Remove-Item -LiteralPath $destinationPath
- }
-
- It "Accepts DestinationPath parameter with [ but no matching ]" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive[2.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
- Remove-Item -LiteralPath $destinationPath
- }
-
- It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
- $sourcePath = "$TestDrive$($DS)Source[]Dir"
- "Some Random Content" | Out-File -LiteralPath "$sourcePath$($DS)Sample[]File.txt"
- $destinationPath = "$TestDrive$($DS)archive3.zip"
- try
- {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- }
- finally
- {
- Remove-Item -LiteralPath $sourcePath -Force -Recurse
- }
- }
-
- It "Accepts LiteralPath parameter for a file with wildcards in the filename" {
- $sourcePath = "$TestDrive$($DS)file1[].txt"
- $destinationPath = "$TestDrive$($DS)archive4.zip"
- try
- {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- }
- finally
- {
- Remove-Item -LiteralPath $sourcePath -Force -Recurse
- }
- }
- }
-
- Context "PassThru tests" {
- BeforeAll {
- New-Item -Path TestDrive:/file.txt -ItemType File
- }
-
- It "Returns an object of type System.IO.FileInfo when PassThru is specified" {
- $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip -PassThru
- $output | Should -BeOfType System.IO.FileInfo
- $destinationPath = Join-Path $TestDrive "archive1.zip"
- $output.FullName | Should -Be $destinationPath
- }
-
- It "Does not return an object when PassThru is not specified" {
- $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
- $output | Should -BeNullOrEmpty
- }
-
- It "Does not return an object when PassThru is false" {
- $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive3.zip -PassThru:$false
- $output | Should -BeNullOrEmpty
- }
- }
-
- Context "File permissions, attributes, etc. tests" {
- BeforeAll {
- New-Item TestDrive:/file.txt -ItemType File
- "Hello, World!" | Out-File -Path TestDrive:/file.txt
-
- # Create a read-only file
- New-Item TestDrive:/readonly.txt -ItemType File
- "Hello, World!" | Out-File -Path TestDrive:/readonly.txt
- }
-
-
- It "Skips archiving a file in use" {
- $fileMode = [System.IO.FileMode]::Open
- $fileAccess = [System.IO.FileAccess]::Write
- $fileShare = [System.IO.FileShare]::None
- $archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/file.txt",$fileMode,$fileAccess,$fileShare
-
- Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip -ErrorAction SilentlyContinue
- # Ensure it creates an empty zip archive
- "TestDrive:/archive_in_use.zip" | Should -BeZipArchiveOnlyContaining @()
-
- $archiveInUseStream.Dispose()
- }
-
- It "Compresses a read-only file" {
- $destinationPath = "TestDrive:/archive_with_readonly_file.zip"
- Compress-Archive -Path TestDrive:/readonly.txt -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("readonly.txt") -Format Zip
- }
- }
-
- # This can be difficult to test
- Context "Long path tests" -Skip {
- BeforeAll {
- if ($IsWindows) {
- $maxPathLength = 300
- }
- if ($IsLinux) {
- $maxPathLength = 255
- }
- if ($IsMacOS) {
- $maxPathLength = 1024
- }
-
- function Get-MaxLengthPath {
- param (
- [string] $character
- )
-
- $path = "${TestDrive}/"
- while ($path.Length -le $maxPathLength + 10) {
- $path += $character
- }
- return $path
- }
-
- New-Item -Path "TestDrive:/file.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "TestDrive:/file.txt"
- }
-
-
- It "Throws an error when -Path is too long" {
-
- }
-
- It "Throws an error when -LiteralPath is too long" {
-
- }
-
- It "Throws an error when -DestinationPath is too long" {
- $path = "TestDrive:/file.txt"
- # This will generate a path like TestDrive:/aaaaaa...aaaaaa
- $destinationPath = Get-MaxLengthPath -character a
- Write-Warning $destinationPath.Length
- try {
- Compress-Archive -Path $path -DestinationPath $destinationPath -ErrorVariable err
- } catch {
- throw "${$_.Exception}"
- }
- $destinationPath | Should -Not -Exist
- }
- }
-
- Context "CompressionLevel tests" {
- BeforeAll {
- New-Item -Path TestDrive:/file1.txt -ItemType File
- "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
- }
-
- It "Throws an error when an invalid value is supplied to CompressionLevel" {
- try {
- Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1.zip -CompressionLevel fakelevel
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "InvalidArgument, ${CmdletClassName}"
- }
- }
- }
-
- Context "Path Structure Preservation Tests" {
- BeforeAll {
- New-Item -Path TestDrive:/file1.txt -ItemType File
- "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
-
- New-Item -Path TestDrive:/directory1 -ItemType Directory
- New-Item -Path TestDrive:/directory1/subdir1 -ItemType Directory
- New-Item -Path TestDrive:/directory1/subdir1/file.txt -ItemType File
- "Hello, World!" | Out-File -FilePath TestDrive:/file.txt
- }
-
- It "Creates an archive containing only a file when the path to that file is not relative to the working directory" {
- $destinationPath = "TestDrive:/archive1.zip"
-
- Push-Location TestDrive:/directory1
-
- Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt")
-
- Pop-Location
- }
-
- It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
- $destinationPath = "TestDrive:/archive2.zip"
-
- Push-Location TestDrive:/
-
- Compress-Archive -Path directory1/subdir1/file.txt -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt")
-
- Pop-Location
- }
-
- It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
- $destinationPath = "TestDrive:/archive3.zip"
-
- Push-Location TestDrive:/
-
- Compress-Archive -Path directory1 -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt")
-
- Pop-Location
- }
- }
-}
+Describe("Microsoft.PowerShell.Archive tests") {
+ BeforeAll {
+
+ $originalProgressPref = $ProgressPreference
+ $ProgressPreference = "SilentlyContinue"
+ $originalPSModulePath = $env:PSModulePath
+
+ function Add-FileExtensionBasedOnFormat {
+ Param (
+ [string] $Path,
+ [string] $Format
+ )
+
+ if ($Format -eq "Zip") {
+ return $Path += ".zip"
+ }
+ if ($Format -eq "Tar") {
+ return $Path += ".tar"
+ }
+ throw "Format type is not supported"
+ }
+ }
+
+ AfterAll {
+ $global:ProgressPreference = $originalProgressPref
+ $env:PSModulePath = $originalPSModulePath
+ }
+
+ Context "Parameter set validation tests" {
+ BeforeAll {
+ # Set up files for tests
+ New-Item TestDrive:/SourceDir -Type Directory
+ "Some Data" | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ New-Item TestDrive:/EmptyDirectory -Type Directory | Out-Null
+ }
+
+
+ It "Validate errors from Compress-Archive with null and empty values for Path, LiteralPath, and DestinationPath parameters" -ForEach @(
+ @{ Path = $null; DestinationPath = "TestDrive:/archive1.zip" }
+ @{ Path = "TestDrive:/SourceDir"; DestinationPath = $null }
+ @{ Path = $null; DestinationPath = $null }
+ @{ Path = ""; DestinationPath = "TestDrive:/archive1.zip" }
+ @{ Path = "TestDrive:/SourceDir"; DestinationPath = "" }
+ @{ Path = ""; DestinationPath = "" }
+ ) {
+ try
+ {
+ Compress-Archive -Path $Path -DestinationPath $DestinationPath
+ throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+
+ try
+ {
+ Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath
+ throw "ValidateNotNullOrEmpty attribute is missing on one of parameters belonging to LiteralPath parameterset."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ParameterArgumentValidationError,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Validate errors from Compress-Archive when invalid path is supplied for Path or LiteralPath parameters" -ForEach @(
+ @{ Path = "Variable:/PWD" }
+ @{ Path = @("TestDrive:/", "Variable:/PWD") }
+ ) {
+ $DestinationPath = "TestDrive:/archive2.zip"
+
+ Compress-Archive -Path $Path -DestinationPath $DestinationPath -ErrorAction SilentlyContinue -ErrorVariable error
+ $error.Count | Should -Be 1
+ $error[0].FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ Remove-Item -Path $DestinationPath
+
+ Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath -ErrorAction SilentlyContinue -ErrorVariable error
+ $error.Count | Should -Be 1
+ $error[0].FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ Remove-Item -Path $DestinationPath
+ }
+
+ It "Throws terminating error when non-existing path is supplied for Path or LiteralPath parameters" -ForEach @(
+ @{ Path = "TestDrive:/DoesNotExist" }
+ @{ Path = @("TestDrive:/", "TestDrive:/DoesNotExist") }
+ ) -Tag this2 {
+ $DestinationPath = "TestDrive:/archive3.zip"
+
+ try
+ {
+ Compress-Archive -Path $Path -DestinationPath $DestinationPath
+ throw "Failed to validate that an invalid Path was supplied as input to Compress-Archive cmdlet."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "PathNotFound,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+
+ try
+ {
+ Compress-Archive -LiteralPath $Path -DestinationPath $DestinationPath
+ throw "Failed to validate that an invalid LiteralPath was supplied as input to Compress-Archive cmdlet."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "PathNotFound,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Validate error from Compress-Archive when duplicate paths are supplied as input to Path parameter" {
+ $sourcePath = @(
+ "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/Sample-1.txt")
+ $destinationPath = "TestDrive:/DuplicatePaths.zip"
+
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect that duplicate Path $sourcePath is supplied as input to Path parameter."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "DuplicatePaths,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Validate error from Compress-Archive when duplicate paths are supplied as input to LiteralPath parameter" {
+ $sourcePath = @(
+ "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/Sample-1.txt")
+ $destinationPath = "TestDrive:/DuplicatePaths.zip"
+
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect that duplicate Path $sourcePath is supplied as input to LiteralPath parameter."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "DuplicatePaths,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ ## From 504
+ It "Validate that Source Path can be at SystemDrive location" -Skip {
+ $sourcePath = "$env:SystemDrive/SourceDir"
+ $destinationPath = "TestDrive:/SampleFromSystemDrive.zip"
+ New-Item $sourcePath -Type Directory | Out-Null # not enough permissions to write to drive root on Linux
+ "Some Data" | Out-File -FilePath $sourcePath/SampleSourceFileForArchive.txt
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Remove-Item "$sourcePath" -Force -Recurse -ErrorAction SilentlyContinue
+ }
+ }
+
+ # This cannot happen in -WriteMode Create because another error will be throw before
+ It "Throws an error when Path and DestinationPath are the same" -Skip {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = $sourcePath
+
+ try {
+ # Note the cmdlet performs validation on $destinationPath
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect an error when Path and DestinationPath are the same"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws an error when Path and DestinationPath are the same and -Update is specified" {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = $sourcePath
+
+ try {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
+ throw "Failed to detect an error when Path and DestinationPath are the same and -Update is specified"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws an error when Path and DestinationPath are the same and -Overwrite is specified" {
+ $sourcePath = "TestDrive:/EmptyDirectory"
+ $destinationPath = $sourcePath
+
+ try {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+ throw "Failed to detect an error when Path and DestinationPath are the same and -Overwrite is specified"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws an error when LiteralPath and DestinationPath are the same" -Skip {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = $sourcePath
+
+ try {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect an error when LiteralPath and DestinationPath are the same"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws an error when LiteralPath and DestinationPath are the same and -Update is specified" {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = $sourcePath
+
+ try {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Update
+ throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Update is specified"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws an error when LiteralPath and DestinationPath are the same and -Overwrite is specified" {
+ $sourcePath = "TestDrive:/EmptyDirectory"
+ $destinationPath = $sourcePath
+
+ try {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+ throw "Failed to detect an error when LiteralPath and DestinationPath are the same and -Overwrite is specified"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws an error when an invalid path is supplied to DestinationPath" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "Variable:/PWD"
+
+ try {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect an error when an invalid path is supplied to DestinationPath"
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "InvalidPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+ }
+
+ Context "WriteMode tests" {
+ BeforeAll {
+ New-Item TestDrive:/SourceDir -Type Directory | Out-Null
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ }
+
+ It "Throws a terminating error when an incorrect value is supplied to -WriteMode" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive1.zip"
+
+ try {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode mode
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "-WriteMode Create works" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive1.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @('SourceDir/', 'SourceDir/Sample-1.txt')
+
+
+ }
+ }
+
+ Context "Basic functional tests" -ForEach @(
+ @{Format = "Zip"},
+ @{Format = "Tar"}
+ ) {
+ BeforeAll {
+ New-Item TestDrive:/SourceDir -Type Directory | Out-Null
+ New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
+ New-Item TestDrive:/SourceDir/ChildDir-2 -Type Directory | Out-Null
+ New-Item TestDrive:/SourceDir/ChildEmptyDir -Type Directory | Out-Null
+
+ # create an empty directory
+ New-Item TestDrive:/EmptyDir -Type Directory | Out-Null
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
+ $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-2/Sample-3.txt
+
+ "Hello, World!" | Out-File -FilePath TestDrive:/HelloWorld.txt
+
+ # Create a zero-byte file
+ New-Item TestDrive:/EmptyFile -Type File | Out-Null
+ }
+
+ It "Compresses a single file with format " {
+ $sourcePath = "TestDrive:/SourceDir/ChildDir-1/Sample-2.txt"
+ $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive1" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-2.txt') -Format $Format
+ }
+
+ It "Compresses a non-empty directory with format " -Tag td1 {
+ $sourcePath = "TestDrive:/SourceDir/ChildDir-1"
+ $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive2" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('ChildDir-1/', 'ChildDir-1/Sample-2.txt') -Format $Format
+ }
+
+ It "Compresses an empty directory with format " {
+ $sourcePath = "TestDrive:/EmptyDir"
+ $destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive3" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('EmptyDir/') -Format $Format
+ }
+
+ It "Compresses multiple files with format " {
+ $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive4" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt') -Format $Format
+ }
+
+ It "Compresses multiple files and a single empty directory with format " {
+ $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildEmptyDir")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive5" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt', 'ChildEmptyDir/') -Format $Format
+ }
+
+ It "Compresses multiple files and a single non-empty directory with format " {
+ $sourcePath = @("TestDrive:/SourceDir/ChildDir-1/Sample-2.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildDir-2")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive6.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'Sample-2.txt', 'ChildDir-2/', 'ChildDir-2/Sample-3.txt') -Format $Format
+ }
+
+ It "Compresses multiple files and non-empty directories with format " {
+ $sourcePath = @("TestDrive:/HelloWorld.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildDir-1", "TestDrive:/SourceDir/ChildDir-2")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive7.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt') -Format $Format
+ }
+
+ It "Compresses multiple files, non-empty directories, and an empty directory with format " {
+ $sourcePath = @("TestDrive:/HelloWorld.txt", "TestDrive:/SourceDir/Sample-1.txt",
+ "TestDrive:/SourceDir/ChildDir-1", "TestDrive:/SourceDir/ChildDir-2", "TestDrive:/SourceDir/ChildEmptyDir")
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive8.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('Sample-1.txt', 'HelloWorld.txt', 'ChildDir-1/', 'ChildDir-2/',
+ 'ChildDir-1/Sample-2.txt', 'ChildDir-2/Sample-3.txt', "ChildEmptyDir/") -Format $Format
+ }
+
+ It "Compresses a directory containing files, non-empty directories, and an empty directory can be compressed with format " {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive9.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $contents = @('SourceDir/', 'SourceDir/ChildDir-1/', 'SourceDir/ChildDir-2/', 'SourceDir/ChildEmptyDir/', 'SourceDir/Sample-1.txt',
+ 'SourceDir/ChildDir-1/Sample-2.txt', 'SourceDir/ChildDir-2/Sample-3.txt')
+ $destinationPath | Should -BeArchiveOnlyContaining $contents -Format $Format
+ }
+
+ It "Compresses a zero-byte file with format " {
+ $sourcePath = "TestDrive:/EmptyFile"
+ $destinationPath = Add-FileExtensionBasedOnFormat "TestDrive:/archive10.zip" -Format $Format
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
+ $destinationPath | Should -BeArchiveOnlyContaining @('EmptyFile') -Format $Format
+ }
+ }
+
+ Context "Zip-specific tests" {
+ BeforeAll {
+ # Create a file whose last write time is before 1980
+ $content | Out-File -FilePath TestDrive:/OldFile.txt
+ Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
+
+ # Create a directory whose last write time is before 1980
+ New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
+ }
+
+ It "Compresses a file whose last write time is before 1980" {
+ $sourcePath = "TestDrive:/OldFile.txt"
+ $destinationPath = "${TestDrive}/archive11.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @('OldFile.txt')
+
+ # Get the archive
+ $fileMode = [System.IO.FileMode]::Open
+ $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
+ $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
+ $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
+ $entry = $archive.GetEntry("OldFile.txt")
+ $entry | Should -Not -BeNullOrEmpty
+
+ $entry.LastWriteTime.Year | Should -BeExactly 1980
+ $entry.LastWriteTime.Month| Should -BeExactly 1
+ $entry.LastWriteTime.Day | Should -BeExactly 1
+ $entry.LastWriteTime.Hour | Should -BeExactly 0
+ $entry.LastWriteTime.Minute | Should -BeExactly 0
+ $entry.LastWriteTime.Second | Should -BeExactly 0
+ $entry.LastWriteTime.Millisecond | Should -BeExactly 0
+
+
+ $archive.Dispose()
+ $archiveStream.Dispose()
+ }
+
+ It "Compresses a directory whose last write time is before 1980 with format " {
+ $sourcePath = "TestDrive:/olddirectory"
+ $destinationPath = "${TestDrive}/archive12.zip"
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @('olddirectory/')
+
+ # Get the archive
+ $fileMode = [System.IO.FileMode]::Open
+ $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
+ $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
+ $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
+ $entry = $archive.GetEntry("olddirectory/")
+ $entry | Should -Not -BeNullOrEmpty
+
+ $entry.LastWriteTime.Year | Should -BeExactly 1980
+ $entry.LastWriteTime.Month| Should -BeExactly 1
+ $entry.LastWriteTime.Day | Should -BeExactly 1
+ $entry.LastWriteTime.Hour | Should -BeExactly 0
+ $entry.LastWriteTime.Minute | Should -BeExactly 0
+ $entry.LastWriteTime.Second | Should -BeExactly 0
+ $entry.LastWriteTime.Millisecond | Should -BeExactly 0
+
+
+ $archive.Dispose()
+ $archiveStream.Dispose()
+ }
+
+ It "Writes a warning when compressing a file whose last write time is before 1980 with format " {
+ $sourcePath = "TestDrive:/OldFile.txt"
+ $destinationPath = "${TestDrive}/archive13.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
+ $warnings.Length | Should -Be 1
+ }
+
+ It "Writes a warning when compresing a directory whose last write time is before 1980 with format " {
+ $sourcePath = "TestDrive:/olddirectory"
+ $destinationPath = "${TestDrive}/archive14.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
+ $warnings.Length | Should -Be 1
+ }
+ }
+
+ Context "DestinationPath and -WriteMode Overwrite tests" {
+ BeforeAll {
+ New-Item TestDrive:/SourceDir -Type Directory | Out-Null
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+
+ New-Item TestDrive:/archive3.zip -Type Directory | Out-Null
+
+ New-Item TestDrive:/EmptyDirectory -Type Directory | Out-Null
+
+ # Create a read-only archive
+ $readOnlyArchivePath = "TestDrive:/readonly.zip"
+ Compress-Archive -Path TestDrive:/SourceDir/Sample-1.txt -DestinationPath $readOnlyArchivePath
+ Set-ItemProperty -Path $readOnlyArchivePath -Name IsReadOnly -Value $true
+
+ # Create TestDrive:/archive.zip
+ Compress-Archive -Path TestDrive:/SourceDir/Sample-1.txt -DestinationPath "TestDrive:/archive.zip"
+
+ # Create Sample-2.txt
+ $content | Out-File -FilePath TestDrive:/Sample-2.txt
+ }
+
+ It "Throws an error when archive file already exists and -Update and -Overwrite parameters are not specified" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive1.zip"
+
+ try
+ {
+ "Some Data" > $destinationPath
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to validate that an archive file format $destinationPath already exists and -Update switch parameter is not specified."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExists,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws a terminating error when archive file exists and -Update is specified but the archive is read-only" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/readonly.zip"
+
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
+ throw "Failed to detect an that an error was thrown when archive $destinationPath already exists but it is read-only and -WriteMode Update is specified."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ArchiveReadOnly,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws a terminating error when archive already exists as a directory and -Update and -Overwrite parameters are not specified" {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = "TestDrive:/SourceDir"
+
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ throw "Failed to detect an error was thrown when archive $destinationPath exists as a directory and -WriteMode Update or -WriteMode Overwrite is not specified."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws a terminating error when DestinationPath is a directory and -Update is specified" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive3.zip"
+
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
+ throw "Failed to validate that a directory $destinationPath exists and -Update switch parameter is specified."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "DestinationExistsAsDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws a terminating error when DestinationPath is a folder containing at least 1 item and Overwrite is specified" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:"
+
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+ throw "Failed to detect an error when $destinationPath is an existing directory containing at least 1 item and -Overwrite switch parameter is specified."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "DestinationIsNonEmptyDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Throws a terminating error when archive does not exist and -Update mode is specified" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive2.zip"
+
+ try
+ {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Update
+ throw "Failed to validate that an archive file format $destinationPath does not exist and -Update switch parameter is specified."
+ }
+ catch
+ {
+ $_.FullyQualifiedErrorId | Should -Be "ArchiveDoesNotExist,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ ## Overwrite tests
+ It "Throws an error when trying to overwrite an empty directory, which is the working directory" {
+ $sourcePath = "TestDrive:/Sample-2.txt"
+ $destinationPath = "TestDrive:/EmptyDirectory"
+
+ Push-Location $destinationPath
+
+ try {
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotOverwriteWorkingDirectory,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+
+ Pop-Location
+ }
+
+ It "Overwrites a directory containing no items when -Overwrite is specified" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/EmptyDirectory"
+
+ # Ensure $destinationPath is a directory
+ Test-Path $destinationPath -PathType Container | Should -Be $true
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WriteMode Overwrite
+
+ # Ensure $destinationPath is now a file
+ Test-Path $destinationPath -PathType Leaf | Should -Be $true
+ }
+
+ It "Overwrites an archive that already exists" {
+ $destinationPath = "TestDrive:/archive.zip"
+
+ # Ensure the original archive contains Sample-1.txt
+ $destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-1.txt")
+
+ # Overwrite the archive
+ $sourcePath = "TestDrive:/Sample-2.txt"
+ Compress-Archive -Path $sourcePath -DestinationPath "TestDrive:/archive.zip" -WriteMode Overwrite
+
+ # Ensure the original entries and different than the new entries
+ $destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-2.txt")
+ }
+ }
+
+ Context "Relative Path tests" {
+ BeforeAll {
+ New-Item TestDrive:/SourceDir -Type Directory | Out-Null
+ New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
+ }
+
+ # From 568
+ It "Validate that relative path can be specified as Path parameter of Compress-Archive cmdlet" {
+ $sourcePath = "./SourceDir"
+ $destinationPath = "RelativePathForPathParameter.zip"
+ try
+ {
+ Push-Location TestDrive:/
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Pop-Location
+ }
+ }
+
+ # From 582
+ It "Validate that relative path can be specified as LiteralPath parameter of Compress-Archive cmdlet" {
+ $sourcePath = "./SourceDir"
+ $destinationPath = "RelativePathForLiteralPathParameter.zip"
+ try
+ {
+ Push-Location TestDrive:/
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Pop-Location
+ }
+ }
+
+ # From 596
+ It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "./RelativePathForDestinationPathParameter.zip"
+ try
+ {
+ Push-Location TestDrive:/
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Pop-Location
+ }
+ }
+ }
+
+ Context "Special and Wildcard Characters Tests" {
+ BeforeAll {
+ New-Item TestDrive:/SourceDir -Type Directory
+
+ New-Item -Path "TestDrive:/Source`[`]Dir" -Type Directory
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ $content | Out-File -LiteralPath TestDrive:/file1[].txt
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ }
+
+ It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = "TestDrive:/Sample[]SingleFile.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path -LiteralPath $destinationPath | Should -Be $true
+ Remove-Item -LiteralPath $destinationPath
+ }
+
+ It "Accepts DestinationPath parameter with [ but no matching ]" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive[2.zip"
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
+ Remove-Item -LiteralPath $destinationPath -Force
+ }
+
+ It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
+ $sourcePath = "TestDrive:/Source[]Dir"
+ "Some Random Content" | Out-File -LiteralPath "$sourcePath/Sample[]File.txt"
+ $destinationPath = "TestDrive:/archive3.zip"
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ }
+ finally
+ {
+ Remove-Item -LiteralPath $sourcePath -Force -Recurse
+ }
+ }
+
+ It "Accepts LiteralPath parameter for a file with wildcards in the filename" {
+ $sourcePath = "TestDrive:/file1[].txt"
+ $destinationPath = "TestDrive:/archive4.zip"
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @("file1[].txt")
+ }
+ finally
+ {
+ Remove-Item -LiteralPath $sourcePath -Force -Recurse
+ }
+ }
+ }
+
+ Context "PassThru tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file.txt -ItemType File
+ }
+
+ It "Returns an object of type System.IO.FileInfo when PassThru is specified" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip -PassThru
+ $output | Should -BeOfType System.IO.FileInfo
+ $destinationPath = Join-Path $TestDrive "archive1.zip"
+ $output.FullName | Should -Be $destinationPath
+ }
+
+ It "Does not return an object when PassThru is not specified" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
+ $output | Should -BeNullOrEmpty
+ }
+
+ It "Does not return an object when PassThru is false" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive3.zip -PassThru:$false
+ $output | Should -BeNullOrEmpty
+ }
+ }
+
+ Context "File permissions, attributes, etc. tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+ # Create a read-only file
+ New-Item TestDrive:/readonly.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/readonly.txt
+ }
+
+
+ It "Skips archiving a file in use" {
+ $fileMode = [System.IO.FileMode]::Open
+ $fileAccess = [System.IO.FileAccess]::Write
+ $fileShare = [System.IO.FileShare]::None
+ $archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/file.txt",$fileMode,$fileAccess,$fileShare
+
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip -ErrorAction SilentlyContinue
+ # Ensure it creates an empty zip archive
+ "TestDrive:/archive_in_use.zip" | Should -BeZipArchiveOnlyContaining @()
+
+ $archiveInUseStream.Dispose()
+ }
+
+ It "Compresses a read-only file" {
+ $destinationPath = "TestDrive:/archive_with_readonly_file.zip"
+ Compress-Archive -Path TestDrive:/readonly.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("readonly.txt") -Format Zip
+ }
+ }
+
+ # This can be difficult to test
+ Context "Long path tests" -Skip {
+ BeforeAll {
+ if ($IsWindows) {
+ $maxPathLength = 300
+ }
+ if ($IsLinux) {
+ $maxPathLength = 255
+ }
+ if ($IsMacOS) {
+ $maxPathLength = 1024
+ }
+
+ function Get-MaxLengthPath {
+ param (
+ [string] $character
+ )
+
+ $path = "${TestDrive}/"
+ while ($path.Length -le $maxPathLength + 10) {
+ $path += $character
+ }
+ return $path
+ }
+
+ New-Item -Path "TestDrive:/file.txt" -ItemType File
+ "Hello, World!" | Out-File -FilePath "TestDrive:/file.txt"
+ }
+
+
+ It "Throws an error when -Path is too long" {
+
+ }
+
+ It "Throws an error when -LiteralPath is too long" {
+
+ }
+
+ It "Throws an error when -DestinationPath is too long" {
+ $path = "TestDrive:/file.txt"
+ # This will generate a path like TestDrive:/aaaaaa...aaaaaa
+ $destinationPath = Get-MaxLengthPath -character a
+ Write-Warning $destinationPath.Length
+ try {
+ Compress-Archive -Path $path -DestinationPath $destinationPath -ErrorVariable err
+ } catch {
+ throw "${$_.Exception}"
+ }
+ $destinationPath | Should -Not -Exist
+ }
+ }
+
+ Context "CompressionLevel tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+ }
+
+ It "Throws an error when an invalid value is supplied to CompressionLevel" {
+ try {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1.zip -CompressionLevel fakelevel
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+ }
+
+ Context "Path Structure Preservation Tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+
+ New-Item -Path TestDrive:/directory1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/subdir1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/subdir1/file.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file.txt
+ }
+
+ It "Creates an archive containing only a file when the path to that file is not relative to the working directory" {
+ $destinationPath = "TestDrive:/archive1.zip"
+
+ Push-Location TestDrive:/directory1
+
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+
+ Pop-Location
+ }
+
+ It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
+ $destinationPath = "TestDrive:/archive2.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1/subdir1/file.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
+
+ Pop-Location
+ }
+
+ It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
+ $destinationPath = "TestDrive:/archive3.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1 -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
+
+ Pop-Location
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Cmdlets/CompressArchiveCommand.cs b/src/Cmdlets/CompressArchiveCommand.cs
index 76679fa..6aac79c 100644
--- a/src/Cmdlets/CompressArchiveCommand.cs
+++ b/src/Cmdlets/CompressArchiveCommand.cs
@@ -297,12 +297,12 @@ private void ValidateDestinationPath()
// Throw an error if DestinationPath exists and the cmdlet is not in Update mode or Overwrite is not specified
if (WriteMode == WriteMode.Create)
{
- errorCode = ErrorCode.ArchiveExistsAsDirectory;
+ errorCode = ErrorCode.DestinationExistsAsDirectory;
}
// Throw an error if the DestinationPath is a directory and the cmdlet is in Update mode
else if (WriteMode == WriteMode.Update)
{
- errorCode = ErrorCode.ArchiveExistsAsDirectory;
+ errorCode = ErrorCode.DestinationExistsAsDirectory;
}
// Throw an error if the DestinationPath is the current working directory and the cmdlet is in Overwrite mode
else if (WriteMode == WriteMode.Overwrite && DestinationPath == SessionState.Path.CurrentFileSystemLocation.ProviderPath)
@@ -312,7 +312,7 @@ private void ValidateDestinationPath()
// Throw an error if the DestinationPath is a directory with at 1 least item and the cmdlet is in Overwrite mode
else if (WriteMode == WriteMode.Overwrite && Directory.GetFileSystemEntries(DestinationPath).Length > 0)
{
- errorCode = ErrorCode.ArchiveIsNonEmptyDirectory;
+ errorCode = ErrorCode.DestinationIsNonEmptyDirectory;
}
}
// If DestinationPath is an existing file
@@ -321,7 +321,7 @@ private void ValidateDestinationPath()
// Throw an error if DestinationPath exists and the cmdlet is not in Update mode or Overwrite is not specified
if (WriteMode == WriteMode.Create)
{
- errorCode = ErrorCode.ArchiveExists;
+ errorCode = ErrorCode.DestinationExists;
}
// Throw an error if the cmdlet is in Update mode but the archive is read only
else if (WriteMode == WriteMode.Update && File.GetAttributes(DestinationPath).HasFlag(FileAttributes.ReadOnly))
diff --git a/src/Cmdlets/ExpandArchiveCommand.cs b/src/Cmdlets/ExpandArchiveCommand.cs
index e59e9d9..3405d0a 100644
--- a/src/Cmdlets/ExpandArchiveCommand.cs
+++ b/src/Cmdlets/ExpandArchiveCommand.cs
@@ -177,7 +177,7 @@ private void ProcessArchiveEntry(IEntry entry)
Debug.Assert(DestinationPath is not null);
// The location of the entry post-expanding of the archive
- string postExpandPath = GetPostExpansionPath(entryName: entry.Name, destinationPath: _destinationPathInfo.FullName);
+ string postExpandPath = GetPostExpansionPath(entryName: entry.Name, destinationPath: DestinationPath);
// If postExpandPath has a terminating `/`, remove it (there is case where overwriting a file may fail because of this)
if (postExpandPath.EndsWith(System.IO.Path.DirectorySeparatorChar))
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index 24471bc..2fd8d20 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -143,6 +143,7 @@
Create
+
The destination path {0} is a directory.
diff --git a/src/Microsoft.PowerShell.Archive.psd1 b/src/Microsoft.PowerShell.Archive.psd1
index da72f6b..2d79fbc 100644
--- a/src/Microsoft.PowerShell.Archive.psd1
+++ b/src/Microsoft.PowerShell.Archive.psd1
@@ -1,36 +1,36 @@
@{
-ModuleVersion = '2.0.1'
-GUID = '06a335eb-dd10-4d25-b753-4f6a80163516'
-Author = 'Microsoft'
-CompanyName = 'Microsoft'
-Copyright = '(c) Microsoft. All rights reserved.'
-Description = 'PowerShell module for creating and expanding archives.'
-PowerShellVersion = '7.2.5'
-NestedModules = @('Microsoft.PowerShell.Archive.dll')
-CmdletsToExport = @('Compress-Archive', 'Expand-Archive')
-PrivateData = @{
- PSData = @{
- Tags = @('Archive', 'Zip', 'Compress')
- ProjectUri = 'https://github.com/PowerShell/Microsoft.PowerShell.Archive'
- LicenseUri = 'https://go.microsoft.com/fwlink/?linkid=2203619'
- ReleaseNotes = @'
- ## 2.0.1-preview2
- - Rewrote `Expand-Archive` cmdlet in C#
- - Added `-Format` parameter to `Expand-Archive`
- - Added `-WriteMode` parameter to `Expand-Archive`
- - Added support for zip64 to `Expand-Archive`
- - Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
- - `Compress-Archive` by default skips writing an entry to an archive if an error occurs while doing so
-
- ## 2.0.1-preview1
- - Rewrote `Compress-Archive` cmdlet in C#
- - Added `-Format` parameter to `Compress-Archive`
- - Added `-WriteMode` parameter to `Compress-Archive`
- - Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
- - Added support for zip64 to `Compress-Archive`
- - Fixed a bug where empty directories would not be compressed
- - Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
+ ModuleVersion = '2.0.1'
+ GUID = '06a335eb-dd10-4d25-b753-4f6a80163516'
+ Author = 'Microsoft'
+ CompanyName = 'Microsoft'
+ Copyright = '(c) Microsoft. All rights reserved.'
+ Description = 'PowerShell module for creating and expanding archives.'
+ PowerShellVersion = '7.3.0'
+ NestedModules = @('Microsoft.PowerShell.Archive.dll')
+ CmdletsToExport = @('Compress-Archive', 'Expand-Archive')
+ PrivateData = @{
+ PSData = @{
+ Tags = @('Archive', 'Zip', 'Compress')
+ ProjectUri = 'https://github.com/PowerShell/Microsoft.PowerShell.Archive'
+ LicenseUri = 'https://go.microsoft.com/fwlink/?linkid=2203619'
+ ReleaseNotes = @'
+ ## 2.0.1-preview2
+ - Rewrote `Expand-Archive` cmdlet in C#
+ - Added `-Format` parameter to `Expand-Archive`
+ - Added `-WriteMode` parameter to `Expand-Archive`
+ - Added support for zip64 to `Expand-Archive`
+ - Fixed a bug where the entry names of files in a directory would not be correct when compressing an archive
+ - `Compress-Archive` by default skips writing an entry to an archive if an error occurs while doing so
+ ## 2.0.1-preview1
+ - Rewrote `Compress-Archive` cmdlet in C#
+ - Added `-Format` parameter to `Compress-Archive`
+ - Added `-WriteMode` parameter to `Compress-Archive`
+ - Added support for relative path structure preservating when paths relative to the working directory are specified to `-Path` or `-LiteralPath` in `Compress-Archive`
+ - Added support for zip64 to `Compress-Archive`
+ - Fixed a bug where empty directories would not be compressed
+ - Fixed a bug where an abrupt stop when compressing empty directories would not delete the newly created archive
'@
- Prerelease = 'preview2'
+ Prerelease = 'preview2'
+ }
}
}
\ No newline at end of file
From 6bc0787aa955ec97b2279b2e2bb5971dda2ac0af Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 16 Aug 2022 16:22:58 -0700
Subject: [PATCH 30/34] added tests for Compress-Archive
---
Tests/Compress-Archive.Tests.ps1 | 697 ++++++++++++++++++-------------
Tests/Expand-Archive.Tests.ps1 | 41 +-
src/PathHelper.cs | 17 +-
3 files changed, 453 insertions(+), 302 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 5f56178..f04a720 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -643,288 +643,425 @@ Describe("Microsoft.PowerShell.Archive tests") {
}
Context "Relative Path tests" {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory | Out-Null
- New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
-
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
- }
-
- # From 568
- It "Validate that relative path can be specified as Path parameter of Compress-Archive cmdlet" {
- $sourcePath = "./SourceDir"
- $destinationPath = "RelativePathForPathParameter.zip"
- try
- {
- Push-Location TestDrive:/
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Pop-Location
- }
- }
-
- # From 582
- It "Validate that relative path can be specified as LiteralPath parameter of Compress-Archive cmdlet" {
- $sourcePath = "./SourceDir"
- $destinationPath = "RelativePathForLiteralPathParameter.zip"
- try
- {
- Push-Location TestDrive:/
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Pop-Location
- }
- }
-
- # From 596
- It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "./RelativePathForDestinationPathParameter.zip"
- try
- {
- Push-Location TestDrive:/
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path $destinationPath | Should -Be $true
- }
- finally
- {
- Pop-Location
- }
- }
+
}
- Context "Special and Wildcard Characters Tests" {
- BeforeAll {
- New-Item TestDrive:/SourceDir -Type Directory
-
- New-Item -Path "TestDrive:/Source`[`]Dir" -Type Directory
+ Context "Special and Wildcard Characters Tests" {
+ BeforeAll {
+ New-Item TestDrive:/SourceDir -Type Directory
+
+ New-Item -Path "TestDrive:/Source`[`]Dir" -Type Directory
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ $content | Out-File -LiteralPath TestDrive:/file1[].txt
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ }
+
+ It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
+ $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
+ $destinationPath = "TestDrive:/Sample[]SingleFile.zip"
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path -LiteralPath $destinationPath | Should -Be $true
+ Remove-Item -LiteralPath $destinationPath
+ }
+
+ It "Accepts DestinationPath parameter with [ but no matching ]" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "TestDrive:/archive[2.zip"
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
+ Remove-Item -LiteralPath $destinationPath -Force
+ }
+
+ It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
+ $sourcePath = "TestDrive:/Source[]Dir"
+ "Some Random Content" | Out-File -LiteralPath "$sourcePath/Sample[]File.txt"
+ $destinationPath = "TestDrive:/archive3.zip"
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -Exist
+ }
+ finally
+ {
+ Remove-Item -LiteralPath $sourcePath -Force -Recurse
+ }
+ }
+
+ It "Accepts LiteralPath parameter for a file with wildcards in the filename" {
+ $sourcePath = "TestDrive:/file1[].txt"
+ $destinationPath = "TestDrive:/archive4.zip"
+ try
+ {
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @("file1[].txt")
+ }
+ finally
+ {
+ Remove-Item -LiteralPath $sourcePath -Force -Recurse
+ }
+ }
+ }
+
+ Context "PassThru tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file.txt -ItemType File
+ }
+
+ It "Returns an object of type System.IO.FileInfo when PassThru is specified" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip -PassThru
+ $output | Should -BeOfType System.IO.FileInfo
+ $destinationPath = Join-Path $TestDrive "archive1.zip"
+ $output.FullName | Should -Be $destinationPath
+ }
+
+ It "Does not return an object when PassThru is not specified" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
+ $output | Should -BeNullOrEmpty
+ }
+
+ It "Does not return an object when PassThru is false" {
+ $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive3.zip -PassThru:$false
+ $output | Should -BeNullOrEmpty
+ }
+ }
+
+ Context "File permissions, attributes, etc. tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+ # Create a read-only file
+ New-Item TestDrive:/readonly.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/readonly.txt
+
+ # Create a hidden file
+ New-Item TestDrive:/.hiddenfile -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/.hiddenfile
+
+ # Create a hidden directory
+ New-Item TestDrive:/.hiddendirectory -ItemType Directory
+ New-Item TestDrive:/.hiddendirectory/file.txt -ItemType File -Force
+ "Hello, World!" | Out-File -Path TestDrive:/.hiddendirectory/file.txt
+
+ # Create a directory containing a hidden file and directory
+ New-Item "TestDrive:/directory_with_hidden_items" -ItemType Directory
+ New-Item "TestDrive:/directory_with_hidden_items/.hiddenfile" -ItemType File
+ New-Item "TestDrive:/directory_with_hidden_items/.hiddendirectory" -ItemType Directory
+ if ($IsWindows) {
+ (Get-Item "TestDrive:/directory_with_hidden_items/.hiddenfile").Attributes += 'Hidden'
+ (Get-Item "TestDrive:/directory_with_hidden_items/.hiddendirectory").Attributes += 'Hidden'
+ }
+ }
+
+
+ It "Skips archiving a file in use" {
+ $fileMode = [System.IO.FileMode]::Open
+ $fileAccess = [System.IO.FileAccess]::Write
+ $fileShare = [System.IO.FileShare]::None
+ $archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/file.txt",$fileMode,$fileAccess,$fileShare
+
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip -ErrorAction SilentlyContinue
+ # Ensure it creates an empty zip archive
+ "TestDrive:/archive_in_use.zip" | Should -BeZipArchiveOnlyContaining @()
+
+ $archiveInUseStream.Dispose()
+ }
+
+ It "Compresses a read-only file" {
+ $destinationPath = "TestDrive:/archive_with_readonly_file.zip"
+ Compress-Archive -Path TestDrive:/readonly.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("readonly.txt") -Format Zip
+ }
+
+ It "Compresses a hidden file" {
+ $path = "TestDrive:/.hiddenfile"
+ if ($IsWindows) {
+ (Get-Item $path).Attributes += 'Hidden'
+ }
+ $destinationPath = "TestDrive:/archive3.zip"
+ Compress-Archive -Path $path -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @(".hiddenfile") -Format Zip
+ }
+
+ It "Compresses a hidden directory" {
+ $path = "TestDrive:/.hiddendirectory"
+ if ($IsWindows) {
+ (Get-Item $path).Attributes += 'Hidden'
+ }
+ $destinationPath = "TestDrive:/archive4.zip"
+ Compress-Archive -Path $path -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @(".hiddendirectory/", ".hiddendirectory/file.txt") -Format Zip
+ }
+
+ It "Compresses a directory containing a hidden file and directory" {
+ $path = "TestDrive:/directory_with_hidden_items"
+ $destinationPath = "TestDrive:/archive5.zip"
+ Compress-Archive -Path $path -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory_with_hidden_items/", "directory_with_hidden_items/.hiddendirectory/", "directory_with_hidden_items/.hiddenfile") -Format Zip
+ }
+ }
+
+ Context "CompressionLevel tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+ }
+
+ It "Throws an error when an invalid value is supplied to CompressionLevel" {
+ try {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1.zip -CompressionLevel fakelevel
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+ }
+
+ Context "Path Structure Preservation Tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+
+ New-Item -Path TestDrive:/directory1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/subdir1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/subdir1/file.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file.txt
+
+ New-Item TestDrive:/SourceDir -Type Directory | Out-Null
+ New-Item TestDrive:/SourceDir/ChildDir-1 -Type Directory | Out-Null
+
+ $content = "Some Data"
+ $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
+ $content | Out-File -FilePath TestDrive:/SourceDir/ChildDir-1/Sample-2.txt
+ }
+
+ It "Creates an archive containing only a file when the path to that file is not relative to the working directory" {
+ $destinationPath = "TestDrive:/archive1.zip"
+
+ Push-Location TestDrive:/directory1
+
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+
+ Pop-Location
+ }
+
+ It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
+ $destinationPath = "TestDrive:/archive2.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1/subdir1/file.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
+
+ Pop-Location
+ }
+
+ It "Compressing a relative path containing .. preserves the relative path structure" {
+ $destinationPath = "TestDrive:/archive3.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1/../directory1/subdir1/file.txt -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
+
+ Pop-Location
+ }
+
+ It "Compressing a relative path containing ~ works when the home directory is relative to the working directory" {
+ $destinationPath = "TestDrive:/archive4.zip"
+ $path = "~/file.txt"
+ New-Item -Path $path -ItemType File
+
+ $homeDirectory = New-Object -TypeName System.IO.DirectoryInfo -ArgumentList $HOME
+ $homeDirectoryName = $homeDirectory.Name
+
+ Push-Location "~/.."
+
+ Compress-Archive -Path $path -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("${homeDirectoryName}/file.txt") -Format Zip
+
+ Remove-Item $path
+ Pop-Location
+
+ }
+
+ it "Compressing a relative path containing wildcard characters preserves the relative directory structure" {
+ $destinationPath = "TestDrive:/archive5.zip"
+
+ Push-Location TestDrive:/
+
+ Compress-Archive -Path directory1/subdir1/* -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
+
+ Pop-Location
+ }
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- $content | Out-File -LiteralPath TestDrive:/file1[].txt
+ It "Validate that relative path can be specified as Path parameter of Compress-Archive cmdlet" {
+ $sourcePath = "./SourceDir"
+ $destinationPath = "RelativePathForPathParameter.zip"
+ try
+ {
+ Push-Location TestDrive:/
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Pop-Location
+ }
+ }
- $content = "Some Data"
- $content | Out-File -FilePath TestDrive:/SourceDir/Sample-1.txt
- }
-
- It "Accepts DestinationPath parameter with wildcard characters that resolves to one path" {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = "TestDrive:/Sample[]SingleFile.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- Test-Path -LiteralPath $destinationPath | Should -Be $true
- Remove-Item -LiteralPath $destinationPath
- }
-
- It "Accepts DestinationPath parameter with [ but no matching ]" {
- $sourcePath = "TestDrive:/SourceDir"
- $destinationPath = "TestDrive:/archive[2.zip"
-
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @("SourceDir/", "SourceDir/Sample-1.txt") -LiteralPath
- Remove-Item -LiteralPath $destinationPath -Force
- }
-
- It "Accepts LiteralPath parameter for a directory with special characters in the directory name" -skip:(($PSVersionTable.psversion.Major -lt 5) -and ($PSVersionTable.psversion.Minor -lt 0)) {
- $sourcePath = "TestDrive:/Source[]Dir"
- "Some Random Content" | Out-File -LiteralPath "$sourcePath/Sample[]File.txt"
- $destinationPath = "TestDrive:/archive3.zip"
- try
- {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -Exist
- }
- finally
- {
- Remove-Item -LiteralPath $sourcePath -Force -Recurse
- }
- }
-
- It "Accepts LiteralPath parameter for a file with wildcards in the filename" {
- $sourcePath = "TestDrive:/file1[].txt"
- $destinationPath = "TestDrive:/archive4.zip"
- try
- {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @("file1[].txt")
- }
- finally
- {
- Remove-Item -LiteralPath $sourcePath -Force -Recurse
- }
- }
- }
-
- Context "PassThru tests" {
- BeforeAll {
- New-Item -Path TestDrive:/file.txt -ItemType File
- }
-
- It "Returns an object of type System.IO.FileInfo when PassThru is specified" {
- $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip -PassThru
- $output | Should -BeOfType System.IO.FileInfo
- $destinationPath = Join-Path $TestDrive "archive1.zip"
- $output.FullName | Should -Be $destinationPath
- }
-
- It "Does not return an object when PassThru is not specified" {
- $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
- $output | Should -BeNullOrEmpty
- }
-
- It "Does not return an object when PassThru is false" {
- $output = Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive3.zip -PassThru:$false
- $output | Should -BeNullOrEmpty
- }
- }
-
- Context "File permissions, attributes, etc. tests" {
- BeforeAll {
- New-Item TestDrive:/file.txt -ItemType File
- "Hello, World!" | Out-File -Path TestDrive:/file.txt
-
- # Create a read-only file
- New-Item TestDrive:/readonly.txt -ItemType File
- "Hello, World!" | Out-File -Path TestDrive:/readonly.txt
- }
-
-
- It "Skips archiving a file in use" {
- $fileMode = [System.IO.FileMode]::Open
- $fileAccess = [System.IO.FileAccess]::Write
- $fileShare = [System.IO.FileShare]::None
- $archiveInUseStream = New-Object -TypeName "System.IO.FileStream" -ArgumentList "${TestDrive}/file.txt",$fileMode,$fileAccess,$fileShare
-
- Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive_in_use.zip -ErrorAction SilentlyContinue
- # Ensure it creates an empty zip archive
- "TestDrive:/archive_in_use.zip" | Should -BeZipArchiveOnlyContaining @()
-
- $archiveInUseStream.Dispose()
- }
-
- It "Compresses a read-only file" {
- $destinationPath = "TestDrive:/archive_with_readonly_file.zip"
- Compress-Archive -Path TestDrive:/readonly.txt -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("readonly.txt") -Format Zip
- }
- }
-
- # This can be difficult to test
- Context "Long path tests" -Skip {
- BeforeAll {
- if ($IsWindows) {
- $maxPathLength = 300
- }
- if ($IsLinux) {
- $maxPathLength = 255
- }
- if ($IsMacOS) {
- $maxPathLength = 1024
- }
-
- function Get-MaxLengthPath {
- param (
- [string] $character
- )
-
- $path = "${TestDrive}/"
- while ($path.Length -le $maxPathLength + 10) {
- $path += $character
- }
- return $path
- }
-
- New-Item -Path "TestDrive:/file.txt" -ItemType File
- "Hello, World!" | Out-File -FilePath "TestDrive:/file.txt"
- }
-
-
- It "Throws an error when -Path is too long" {
-
- }
-
- It "Throws an error when -LiteralPath is too long" {
-
- }
-
- It "Throws an error when -DestinationPath is too long" {
- $path = "TestDrive:/file.txt"
- # This will generate a path like TestDrive:/aaaaaa...aaaaaa
- $destinationPath = Get-MaxLengthPath -character a
- Write-Warning $destinationPath.Length
- try {
- Compress-Archive -Path $path -DestinationPath $destinationPath -ErrorVariable err
- } catch {
- throw "${$_.Exception}"
- }
- $destinationPath | Should -Not -Exist
- }
- }
-
- Context "CompressionLevel tests" {
- BeforeAll {
- New-Item -Path TestDrive:/file1.txt -ItemType File
- "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
- }
-
- It "Throws an error when an invalid value is supplied to CompressionLevel" {
- try {
- Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1.zip -CompressionLevel fakelevel
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
- }
-
- Context "Path Structure Preservation Tests" {
- BeforeAll {
- New-Item -Path TestDrive:/file1.txt -ItemType File
- "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
-
- New-Item -Path TestDrive:/directory1 -ItemType Directory
- New-Item -Path TestDrive:/directory1/subdir1 -ItemType Directory
- New-Item -Path TestDrive:/directory1/subdir1/file.txt -ItemType File
- "Hello, World!" | Out-File -FilePath TestDrive:/file.txt
- }
-
- It "Creates an archive containing only a file when the path to that file is not relative to the working directory" {
- $destinationPath = "TestDrive:/archive1.zip"
-
- Push-Location TestDrive:/directory1
-
- Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
-
- Pop-Location
- }
-
- It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
- $destinationPath = "TestDrive:/archive2.zip"
-
- Push-Location TestDrive:/
-
- Compress-Archive -Path directory1/subdir1/file.txt -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
-
- Pop-Location
- }
-
- It "Creates an archive containing a file and its parent directories when the path to the file and its parent directories are descendents of the working directory" {
- $destinationPath = "TestDrive:/archive3.zip"
-
- Push-Location TestDrive:/
-
- Compress-Archive -Path directory1 -DestinationPath $destinationPath
- $destinationPath | Should -BeArchiveOnlyContaining @("directory1/subdir1/file.txt") -Format Zip
-
- Pop-Location
- }
- }
+ It "Validate that relative path can be specified as LiteralPath parameter of Compress-Archive cmdlet" {
+ $sourcePath = "./SourceDir"
+ $destinationPath = "RelativePathForLiteralPathParameter.zip"
+ try
+ {
+ Push-Location TestDrive:/
+ Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Pop-Location
+ }
+ }
+
+ It "Validate that relative path can be specified as DestinationPath parameter of Compress-Archive cmdlet" {
+ $sourcePath = "TestDrive:/SourceDir"
+ $destinationPath = "./RelativePathForDestinationPathParameter.zip"
+ try
+ {
+ Push-Location TestDrive:/
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ Test-Path $destinationPath | Should -Be $true
+ }
+ finally
+ {
+ Pop-Location
+ }
+ }
+ }
+
+ Context "-Format tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+ }
+
+ It "Throws an error when an invalid value is supplied to -Format" {
+ try {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1 -Format fakeformat
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
+
+ It "Creates a zip archive when -Format is not specified and the destination path does not have a matching extension" {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1
+ "TestDrive:/archive1" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ }
+
+ It "Emits a warning when DestinationPath does not have an extension that matches the value of -Format" {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive2.tar -Format Zip -WarningVariable warnings
+ "TestDrive:/archive2.tar" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ $warnings.Count | Should -Be 1
+ }
+
+ It "Emits a warning when DestinationPath does not have an extension that matches any extension for a supported archive format" {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive3.notmatching -WarningVariable warnings
+ "TestDrive:/archive3.notmatching" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ $warnings.Count | Should -Be 1
+ }
+
+ It "Emits a warning when DestinationPath has no extension and -Format is specified" {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive4 -Format Zip -WarningVariable warnings
+ "TestDrive:/archive4" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ $warnings.Count | Should -Be 1
+ }
+
+ It "Emits a warning when DestinationPath has no extension and -Format is not specified" {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive5 -WarningVariable warnings
+ "TestDrive:/archive5" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ $warnings.Count | Should -Be 1
+ }
+
+ It "Does not emit a warning when DestinationPath has an extension that matches the value supplied to -Format" -ForEach @(
+ @{DestinationPath = "TestDrive:/archive6.zip"; Format = "Zip"}
+ @{DestinationPath = "TestDrive:/archive6.tar"; Format = "Tar"}
+ ) {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath $DestinationPath -Format $Format -WarningVariable warnings
+ $DestinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format $Format
+ $warnings.Count | Should -Be 0
+ }
+
+ It "Does not emit a warning when DestinationPath has a .zip extension and -Format is not specified" {
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive7.zip -WarningVariable warnings
+ "TestDrive:/archive4" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ $warnings.Count | Should -Be 0
+ }
+ }
+
+ Context "Pipeline tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+ New-Item -Path TestDrive:/file2.txt -ItemType File
+ "Hello, PowerShell!" | Out-File -FilePath TestDrive:/file2.txt
+ }
+
+ It "Creates an archives when paths are passed via pipeline" {
+ $destinationPath = "TestDrive:/archive1.zip"
+ @("TestDrive:/file1.txt", "TestDrive:/file2.txt") | Compress-Archive -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt", "file2.txt") -Format Zip
+ }
+
+ It "Creates an archives when paths are passed to -Path via pipeline by name" {
+ $destinationPath = "TestDrive:/archive2.zip"
+ $path = [pscustomobject]@{Path = @("TestDrive:/file1.txt", "TestDrive:/file2.txt")}
+ $path | Compress-Archive -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt", "file2.txt") -Format Zip
+ }
+
+ It "Creates an archives when paths are passed to -LiteralPath via pipeline by name" {
+ $destinationPath = "TestDrive:/archive3.zip"
+ $path = [pscustomobject]@{LiteralPath = @("TestDrive:/file1.txt", "TestDrive:/file2.txt")}
+ $path | Compress-Archive -DestinationPath $destinationPath
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt", "file2.txt") -Format Zip
+ }
+ }
+
+ Context "Large file tests" -Tag Slow {
+ BeforeAll {
+ $numberOfBytes = 512
+ $bytes = [byte[]]::new($numberOfBytes)
+ for ($i = 0; $i -lt $numberOfBytes; $i++) {
+ $bytes[$i] = 1
+ }
+
+ # Create a large file containing 1's
+ $largeFilePath = Join-Path $TestDrive "file1"
+ $fileWith1s = [System.IO.File]::Create($largeFilePath)
+
+ $numberOfTimesToWrite = 5GB / $numberOfBytes
+ for ($i=0; $i -lt $numberOfTimesToWrite; $i++) {
+ $fileWith1s.Write($bytes, 0, $numberOfBytes)
+ }
+ $fileWith1s.Close()
+ $f= Get-Item "TestDrive:/file1"
+ $f.Length | Write-Verbose -Verbose
+ $f.Length / 1GB | Write-Verbose -Verbose
+ }
+
+ It "Creates an archive containing files > 4GB" {
+ Compress-Archive -Path "TestDrive:/file1" -DestinationPath "TestDrive:/archive1.zip"
+ $hash = Get-FileHash -Path "TestDrive:/file1" -Algorithm SHA512
+ # We are comparing hashes to see if the archive matches the desired archive
+ $hash.Hash | Should -Be "E3676A06DFFD348EC48B56C0AA2D3BC6BB52AF6903F21AA221FF01493BB4360E58ACEC2D369F954F0739851679F1891AFC31FD630E7863105285FD6595F1E7B5"
+ }
+ }
}
\ No newline at end of file
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index 5d4d65a..206f6c1 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -374,7 +374,7 @@ Describe("Expand-Archive Tests") {
Compress-Archive -Path "TestDrive:/DirectoryToArchive" -DestinationPath (Add-FileExtensionBasedOnFormat "TestDrive:/archive2" -Format $Format)
# Create an archive containing a file and an empty folder
- Compress-Archive -Path "TestDrive:/file1.txt","TestDrive:/DirectoryToArchive" -DestinationPath (Add-FileExtensionBasedOnFormat "TestDrive:/archive3" -Format $Format)
+ Compress-Archive -Path "TestDrive:/file1.txt","TestDrive:/DirectoryToArchive" -DestinationPath "TestDrive:/archive3"
}
It "Expands an archive when a non-existent directory is specified as -DestinationPath with format " {
@@ -414,7 +414,7 @@ Describe("Expand-Archive Tests") {
Pop-Location
}
- It "Expands an archive containing a single top-level directory and no other top-level items to a directory with that directory's name when -DestinationPath is not specified" {
+ It "Expands an archive to a directory with that archive's name when -DestinationPath is not specified" {
$sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive2" -Format $Format
$destinationPath = "TestDrive:/directory4"
@@ -423,30 +423,31 @@ Describe("Expand-Archive Tests") {
Expand-Archive -Path $sourcePath
$itemsInDestinationPath = Get-ChildItem $destinationPath -Recurse
- $itemsInDestinationPath.Count | Should -Be 1
- $itemsInDestinationPath[0].Name | Should -Be "DirectoryToArchive"
-
- Pop-Location
- }
+ $itemsInDestinationPath.Count | Should -Be 2
- It "Expands an archive containing multiple top-level items to a directory with that archive's name when -DestinationPath is not specified" {
- $sourcePath = Add-FileExtensionBasedOnFormat "TestDrive:/archive3" -Format $Format
- $destinationPath = "TestDrive:/directory5"
-
- Push-Location $destinationPath
-
- Expand-Archive -Path $sourcePath
+ $directoryContents = @()
+ $directoryContents += $itemsInDestinationPath[0].FullName
+ $directoryContents += $itemsInDestinationPath[1].FullName
- $itemsInDestinationPath = Get-ChildItem $destinationPath -Name -Recurse
- $itemsInDestinationPath.Count | Should -Be 3
- "archive3" | Should -BeIn $itemsInDestinationPath
- (Join-Path "archive3" "DirectoryToArchive") | Should -BeIn $itemsInDestinationPath
- (Join-Path "archive3" "file1.txt") | Should -BeIn $itemsInDestinationPath
-
+ $directoryContents | Should -Contain (Join-Path $TestDrive "directory4/archive2")
+ $directoryContents | Should -Contain (Join-Path $TestDrive "directory4/archive2/DirectoryToArchive")
Pop-Location
}
+ It "Throws an error when expanding an archive whose name does not have an extension and -DestinationPath is not specified" {
+ Push-Location "TestDrive:/"
+ try {
+ Expand-Archive -Path "TestDrive:/archive3"
+ }
+ catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotDetermineDestinationPath,${CmdletClassName}"
+ }
+ finally {
+ Pop-Location
+ }
+ }
+
It "Expands an archive containing multiple files, non-empty directories, and empty directories" {
# Create an archive containing multiple files, non-empty directories, and empty directories
diff --git a/src/PathHelper.cs b/src/PathHelper.cs
index 87dd646..c3ceeec 100644
--- a/src/PathHelper.cs
+++ b/src/PathHelper.cs
@@ -22,6 +22,9 @@ internal class PathHelper
internal WildcardPattern? _wildCardPattern;
+ // These are the paths to add
+ internal HashSet? _fullyQualifiedPaths;
+
internal PathHelper(PSCmdlet cmdlet)
{
_cmdlet = cmdlet;
@@ -40,6 +43,7 @@ internal List GetArchiveAdditions(HashSet fullyQualifie
Debug.Assert(Path.IsPathFullyQualified(path));
AddAdditionForFullyQualifiedPath(path, archiveAdditions, entryName: null, parentMatchesFilter: false);
}
+
return archiveAdditions;
}
@@ -171,6 +175,9 @@ private string GetEntryName(FileSystemInfo fileSystemInfo, out bool doesPreserve
Debug.Assert(relativePath is not null);
doesPreservePathStructure = true;
entryName = relativePath;
+
+ // In case the relative path contains parent directories that have not been entered by the user,
+ // check for these paths and add them
}
// Otherwise, return the name of the directory or file
else
@@ -246,17 +253,23 @@ private static string GetPrefixForPath(System.IO.DirectoryInfo directoryInfo)
private bool TryGetPathRelativeToCurrentWorkingDirectory(string path, out string? relativePathToWorkingDirectory)
{
Debug.Assert(!string.IsNullOrEmpty(path));
- string? workingDirectoryRoot = Path.GetPathRoot(_cmdlet.SessionState.Path.CurrentFileSystemLocation.Path);
+ string workingDirectory = _cmdlet.SessionState.Path.CurrentFileSystemLocation.ProviderPath;
+ string? workingDirectoryRoot = Path.GetPathRoot(workingDirectory);
string? pathRoot = Path.GetPathRoot(path);
if (workingDirectoryRoot != pathRoot) {
relativePathToWorkingDirectory = null;
return false;
}
- string relativePath = Path.GetRelativePath(_cmdlet.SessionState.Path.CurrentFileSystemLocation.Path, path);
+ string relativePath = Path.GetRelativePath(workingDirectory, path);
relativePathToWorkingDirectory = relativePath.Contains("..") ? null : relativePath;
return relativePathToWorkingDirectory is not null;
}
+ // Adds the parent directories in a path to the list of fully qualified paths
+ private void AddParentDirectoriesToFullyQualifiedPaths(string path) {
+
+ }
+
internal System.Collections.ObjectModel.Collection? GetResolvedPathFromPSProviderPath(string path, bool pathMustExist) {
// Keep the exception at the top, then when an error occurs, use the exception to create an ErrorRecord
Exception? exception = null;
From bb3d9092c641152647ffeac7123d2fcbf908d668 Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 16 Aug 2022 18:56:39 -0700
Subject: [PATCH 31/34] added more tests for Compress-Archive, large file
tests, Flatten tests, etc
---
Tests/Compress-Archive.Tests.ps1 | 339 ++++++++++++++++++-------------
src/PathHelper.cs | 30 ++-
2 files changed, 226 insertions(+), 143 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index f04a720..21a8ead 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -169,20 +169,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
}
}
- # This cannot happen in -WriteMode Create because another error will be throw before
- It "Throws an error when Path and DestinationPath are the same" -Skip {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = $sourcePath
-
- try {
- # Note the cmdlet performs validation on $destinationPath
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect an error when Path and DestinationPath are the same"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SamePathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
It "Throws an error when Path and DestinationPath are the same and -Update is specified" {
$sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
$destinationPath = $sourcePath
@@ -207,18 +193,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
}
}
- It "Throws an error when LiteralPath and DestinationPath are the same" -Skip {
- $sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
- $destinationPath = $sourcePath
-
- try {
- Compress-Archive -LiteralPath $sourcePath -DestinationPath $destinationPath
- throw "Failed to detect an error when LiteralPath and DestinationPath are the same"
- } catch {
- $_.FullyQualifiedErrorId | Should -Be "SameLiteralPathAndDestinationPath,Microsoft.PowerShell.Archive.CompressArchiveCommand"
- }
- }
-
It "Throws an error when LiteralPath and DestinationPath are the same and -Update is specified" {
$sourcePath = "TestDrive:/SourceDir/Sample-1.txt"
$destinationPath = $sourcePath
@@ -388,101 +362,131 @@ Describe("Microsoft.PowerShell.Archive tests") {
}
}
- Context "Zip-specific tests" {
- BeforeAll {
- # Create a file whose last write time is before 1980
- $content | Out-File -FilePath TestDrive:/OldFile.txt
- Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
+ Context "Zip-specific tests" -Tag Slow {
+ BeforeAll {
+ # Create a file whose last write time is before 1980
+ $content | Out-File -FilePath TestDrive:/OldFile.txt
+ Set-ItemProperty -Path TestDrive:/OldFile.txt -Name LastWriteTime -Value '1974-01-16 14:44'
- # Create a directory whose last write time is before 1980
- New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
- Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
- }
+ # Create a directory whose last write time is before 1980
+ New-Item -Path "TestDrive:/olddirectory" -ItemType Directory
+ Set-ItemProperty -Path "TestDrive:/olddirectory" -Name "LastWriteTime" -Value '1974-01-16 14:44'
- It "Compresses a file whose last write time is before 1980" {
- $sourcePath = "TestDrive:/OldFile.txt"
- $destinationPath = "${TestDrive}/archive11.zip"
+
+ $numberOfBytes = 512
+ $bytes = [byte[]]::new($numberOfBytes)
+ for ($i = 0; $i -lt $numberOfBytes; $i++) {
+ $bytes[$i] = 1
+ }
- # Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
- $dateProperty.Year | Should -BeLessThan 1980
+ # Create a large file containing 1's
+ $largeFilePath = Join-Path $TestDrive "file1"
+ $fileWith1s = [System.IO.File]::Create($largeFilePath)
+
+ $numberOfTimesToWrite = 5GB / $numberOfBytes
+ for ($i=0; $i -lt $numberOfTimesToWrite; $i++) {
+ $fileWith1s.Write($bytes, 0, $numberOfBytes)
+ }
+ $fileWith1s.Close()
+ }
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @('OldFile.txt')
-
- # Get the archive
- $fileMode = [System.IO.FileMode]::Open
- $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
- $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
- $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
- $entry = $archive.GetEntry("OldFile.txt")
- $entry | Should -Not -BeNullOrEmpty
-
- $entry.LastWriteTime.Year | Should -BeExactly 1980
- $entry.LastWriteTime.Month| Should -BeExactly 1
- $entry.LastWriteTime.Day | Should -BeExactly 1
- $entry.LastWriteTime.Hour | Should -BeExactly 0
- $entry.LastWriteTime.Minute | Should -BeExactly 0
- $entry.LastWriteTime.Second | Should -BeExactly 0
- $entry.LastWriteTime.Millisecond | Should -BeExactly 0
-
-
- $archive.Dispose()
- $archiveStream.Dispose()
- }
+ It "Compresses a file whose last write time is before 1980" {
+ $sourcePath = "TestDrive:/OldFile.txt"
+ $destinationPath = "${TestDrive}/archive11.zip"
- It "Compresses a directory whose last write time is before 1980 with format " {
- $sourcePath = "TestDrive:/olddirectory"
- $destinationPath = "${TestDrive}/archive12.zip"
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
- $destinationPath | Should -BeZipArchiveOnlyContaining @('olddirectory/')
-
- # Get the archive
- $fileMode = [System.IO.FileMode]::Open
- $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
- $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
- $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
- $entry = $archive.GetEntry("olddirectory/")
- $entry | Should -Not -BeNullOrEmpty
-
- $entry.LastWriteTime.Year | Should -BeExactly 1980
- $entry.LastWriteTime.Month| Should -BeExactly 1
- $entry.LastWriteTime.Day | Should -BeExactly 1
- $entry.LastWriteTime.Hour | Should -BeExactly 0
- $entry.LastWriteTime.Minute | Should -BeExactly 0
- $entry.LastWriteTime.Second | Should -BeExactly 0
- $entry.LastWriteTime.Millisecond | Should -BeExactly 0
-
-
- $archive.Dispose()
- $archiveStream.Dispose()
- }
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @('OldFile.txt')
- It "Writes a warning when compressing a file whose last write time is before 1980 with format " {
- $sourcePath = "TestDrive:/OldFile.txt"
- $destinationPath = "${TestDrive}/archive13.zip"
+ # Get the archive
+ $fileMode = [System.IO.FileMode]::Open
+ $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
+ $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
+ $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
+ $entry = $archive.GetEntry("OldFile.txt")
+ $entry | Should -Not -BeNullOrEmpty
+
+ $entry.LastWriteTime.Year | Should -BeExactly 1980
+ $entry.LastWriteTime.Month| Should -BeExactly 1
+ $entry.LastWriteTime.Day | Should -BeExactly 1
+ $entry.LastWriteTime.Hour | Should -BeExactly 0
+ $entry.LastWriteTime.Minute | Should -BeExactly 0
+ $entry.LastWriteTime.Second | Should -BeExactly 0
+ $entry.LastWriteTime.Millisecond | Should -BeExactly 0
+
+
+ $archive.Dispose()
+ $archiveStream.Dispose()
+ }
- # Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
- $dateProperty.Year | Should -BeLessThan 1980
+ It "Compresses a directory whose last write time is before 1980 with format " {
+ $sourcePath = "TestDrive:/olddirectory"
+ $destinationPath = "${TestDrive}/archive12.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
- $warnings.Length | Should -Be 1
- }
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath
+ $destinationPath | Should -BeZipArchiveOnlyContaining @('olddirectory/')
- It "Writes a warning when compresing a directory whose last write time is before 1980 with format " {
- $sourcePath = "TestDrive:/olddirectory"
- $destinationPath = "${TestDrive}/archive14.zip"
+ # Get the archive
+ $fileMode = [System.IO.FileMode]::Open
+ $archiveStream = New-Object -TypeName System.IO.FileStream -ArgumentList $destinationPath,$fileMode
+ $zipArchiveMode = [System.IO.Compression.ZipArchiveMode]::Read
+ $archive = New-Object -TypeName System.IO.Compression.ZipArchive -ArgumentList $archiveStream,$zipArchiveMode
+ $entry = $archive.GetEntry("olddirectory/")
+ $entry | Should -Not -BeNullOrEmpty
+
+ $entry.LastWriteTime.Year | Should -BeExactly 1980
+ $entry.LastWriteTime.Month| Should -BeExactly 1
+ $entry.LastWriteTime.Day | Should -BeExactly 1
+ $entry.LastWriteTime.Hour | Should -BeExactly 0
+ $entry.LastWriteTime.Minute | Should -BeExactly 0
+ $entry.LastWriteTime.Second | Should -BeExactly 0
+ $entry.LastWriteTime.Millisecond | Should -BeExactly 0
+
+
+ $archive.Dispose()
+ $archiveStream.Dispose()
+ }
- # Assert the last write time of the file is before 1980
- $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
- $dateProperty.Year | Should -BeLessThan 1980
+ It "Writes a warning when compressing a file whose last write time is before 1980 with format " {
+ $sourcePath = "TestDrive:/OldFile.txt"
+ $destinationPath = "${TestDrive}/archive13.zip"
- Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
- $warnings.Length | Should -Be 1
- }
- }
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
+ $warnings.Length | Should -Be 1
+ }
+
+ It "Writes a warning when compresing a directory whose last write time is before 1980 with format " {
+ $sourcePath = "TestDrive:/olddirectory"
+ $destinationPath = "${TestDrive}/archive14.zip"
+
+ # Assert the last write time of the file is before 1980
+ $dateProperty = Get-ItemPropertyValue -Path $sourcePath -Name "LastWriteTime"
+ $dateProperty.Year | Should -BeLessThan 1980
+
+ Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -WarningVariable warnings
+ $warnings.Length | Should -Be 1
+ }
+
+ It "Creates an archive containing files > 4GB" {
+ (Get-Item "TestDrive:/file1").Length | Should -BeGreaterThan 4GB
+ Compress-Archive -Path "TestDrive:/file1" -DestinationPath "TestDrive:/archive1.zip"
+ "TestDrive:/archive1.zip" | Should -BeArchiveOnlyContaining @("file1") -Format Zip
+ }
+
+ It "Creates an an archive > 4GB in size" {
+ $destinationPath = "TestDrive:/archive2.zip"
+ Compress-Archive -Path "TestDrive:/file1" -DestinationPath $destinationPath -CompressionLevel NoCompression
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1") -Format Zip
+ (Get-Item $destinationPath).Length | Should -BeGreaterThan 4GB
+ }
+ }
Context "DestinationPath and -WriteMode Overwrite tests" {
BeforeAll {
@@ -640,10 +644,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
# Ensure the original entries and different than the new entries
$destinationPath | Should -BeZipArchiveOnlyContaining @("Sample-2.txt")
}
- }
-
- Context "Relative Path tests" {
-
}
Context "Special and Wildcard Characters Tests" {
@@ -758,7 +758,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
}
}
-
It "Skips archiving a file in use" {
$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Write
@@ -810,6 +809,13 @@ Describe("Microsoft.PowerShell.Archive tests") {
BeforeAll {
New-Item -Path TestDrive:/file1.txt -ItemType File
"Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+
+ # Compress a file with different CompressionLevel values
+ $path = Join-Path $ScriptRoot "Sample-File"
+ Compress-Archive -Path $path -DestinationPath TestDrive:/archive1.zip -CompressionLevel Optimal
+ Compress-Archive -Path $path -DestinationPath TestDrive:/archive2.zip -CompressionLevel NoCompression
+ Compress-Archive -Path $path -DestinationPath TestDrive:/archive3.zip -CompressionLevel Fastest
+ Compress-Archive -Path $path -DestinationPath TestDrive:/archive4.zip -CompressionLevel SmallestSize
}
It "Throws an error when an invalid value is supplied to CompressionLevel" {
@@ -819,6 +825,14 @@ Describe("Microsoft.PowerShell.Archive tests") {
$_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
+
+ It "Creates an archive with -CompressionLevel" -ForEach @(
+ @{CompressionLevel = [System.IO.Compression.CompressionLevel]::Optimal}
+ ) {
+
+ }
+
+
}
Context "Path Structure Preservation Tests" {
@@ -1036,32 +1050,85 @@ Describe("Microsoft.PowerShell.Archive tests") {
}
Context "Large file tests" -Tag Slow {
+
+ }
+
+ Context "Update tests" {
BeforeAll {
- $numberOfBytes = 512
- $bytes = [byte[]]::new($numberOfBytes)
- for ($i = 0; $i -lt $numberOfBytes; $i++) {
- $bytes[$i] = 1
- }
+ New-Item -Path TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
+ New-Item -Path TestDrive:/file2.txt -ItemType File
+ "Hello, PowerShell!" | Out-File -FilePath TestDrive:/file2.txt
- # Create a large file containing 1's
- $largeFilePath = Join-Path $TestDrive "file1"
- $fileWith1s = [System.IO.File]::Create($largeFilePath)
-
- $numberOfTimesToWrite = 5GB / $numberOfBytes
- for ($i=0; $i -lt $numberOfTimesToWrite; $i++) {
- $fileWith1s.Write($bytes, 0, $numberOfBytes)
- }
- $fileWith1s.Close()
- $f= Get-Item "TestDrive:/file1"
- $f.Length | Write-Verbose -Verbose
- $f.Length / 1GB | Write-Verbose -Verbose
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive1.zip
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive_to_append.zip
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive_to_update.zip
+
+ # Create an archive containing a directory for updating
+ New-Item -Path TestDrive:/directory1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/file1.txt -ItemType File
+
+ Compress-Archive -Path TestDrive:/directory1 -DestinationPath TestDrive:/archive_with_directory.zip
+
+ New-Item -Path TestDrive:/directory1/file2.txt -ItemType File
}
- It "Creates an archive containing files > 4GB" {
- Compress-Archive -Path "TestDrive:/file1" -DestinationPath "TestDrive:/archive1.zip"
- $hash = Get-FileHash -Path "TestDrive:/file1" -Algorithm SHA512
- # We are comparing hashes to see if the archive matches the desired archive
- $hash.Hash | Should -Be "E3676A06DFFD348EC48B56C0AA2D3BC6BB52AF6903F21AA221FF01493BB4360E58ACEC2D369F954F0739851679F1891AFC31FD630E7863105285FD6595F1E7B5"
+ It "Does not throw an error when -Update is specified and the archive already exists" {
+ Compress-Archive -Path TestDrive:/file2.txt -DestinationPath TestDrive:/archive1.zip -WriteMode Update -ErrorVariable errors
+ $errors.Count | Should -Be 0
+ }
+
+ It "Appends a file to an archive when -WriteMode Update is specified" {
+ Compress-Archive -Path TestDrive:/file2.txt -DestinationPath TestDrive:/archive_to_append.zip -WriteMode Update
+ "TestDrive:/archive_to_append.zip" | Should -BeArchiveOnlyContaining @("file1.txt", "file2.txt") -Format Zip
+ }
+
+ It "Modifies a pre-existing file in an archive when -WriteMode Update is specified" {
+ "File has been modified." | Out-File -FilePath TestDrive:/file1.txt
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/archive_to_update.zip -WriteMode Update
+ "TestDrive:/archive_to_update.zip" | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+
+ # Expand the archive and ensure it has the new contents
+ Expand-Archive -Path TestDrive:/archive_to_update.zip -DestinationPath TestDrive:/archive_to_update_contents
+
+ Get-Content TestDrive:/archive_to_update_contents/file1.txt | Should -Be "File has been modified."
+ }
+
+ It "Adds directory's children when updating a directory in the archive" {
+ $archivePath = "TestDrive:/archive_with_directory.zip"
+ Compress-Archive -Path TestDrive:/directory1 -DestinationPath $archivePath -WriteMode Update
+ $archivePath | Should -BeArchiveOnlyContaining @("directory1/", "directory1/file1.txt", "directory1/file2.txt") -Format Zip
+ }
+ }
+
+ Context "Flatten tests" {
+ BeforeAll {
+ New-Item -Path TestDrive:/directory1 -ItemType Directory
+ New-Item -Path TestDrive:/directory1/file1.txt -ItemType File
+ "Hello, World!" | Out-File -FilePath TestDrive:/directory1/file1.txt
+
+ New-Item -Path TestDrive:/directory2 -ItemType Directory
+ New-Item -Path TestDrive:/directory2/file1.txt -ItemType File
+ "Hello, PowerShell!" | Out-File -FilePath TestDrive:/directory2/file1.txt
+ }
+
+ It "Creates a flat archive with -Flatten" {
+ $path = "TestDrive:/directory1"
+ $destinationPath = "TestDrive:/archive1.zip"
+
+ Compress-Archive -Path $path -DestinationPath $destinationPath -Flatten
+ $x = Convert-Path $destinationPath
+ 7z l "${x}" | Write-Verbose -Verbose
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
+ }
+
+ It "Does not throw an error when multiple files have the same entry name when -Flatten is specified" {
+ $path = "TestDrive:/directory1","TestDrive:/directory2"
+ $destinationPath = "TestDrive:/archive2.zip"
+
+ Compress-Archive -Path $path -DestinationPath $destinationPath -Flatten -ErrorVariable errors
+ errors.Count | Should -Be 0
+ $destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
}
}
}
\ No newline at end of file
diff --git a/src/PathHelper.cs b/src/PathHelper.cs
index c3ceeec..1bff480 100644
--- a/src/PathHelper.cs
+++ b/src/PathHelper.cs
@@ -25,6 +25,9 @@ internal class PathHelper
// These are the paths to add
internal HashSet? _fullyQualifiedPaths;
+ // This is used only when flattening to track entry names, so duplicate entry names can be removed
+ internal HashSet? _entryNames;
+
internal PathHelper(PSCmdlet cmdlet)
{
_cmdlet = cmdlet;
@@ -34,6 +37,7 @@ internal List GetArchiveAdditions(HashSet fullyQualifie
{
if (Filter is not null) {
_wildCardPattern = new WildcardPattern(Filter);
+ _entryNames = new HashSet();
}
List archiveAdditions = new List(fullyQualifiedPaths.Count);
foreach (var path in fullyQualifiedPaths)
@@ -44,6 +48,8 @@ internal List GetArchiveAdditions(HashSet fullyQualifie
AddAdditionForFullyQualifiedPath(path, archiveAdditions, entryName: null, parentMatchesFilter: false);
}
+ // If the mode is flatten, there could be
+
return archiveAdditions;
}
@@ -74,13 +80,15 @@ private void AddAdditionForFullyQualifiedPath(string path, List
}
bool doesMatchFilter = true;
- if (!parentMatchesFilter && _wildCardPattern is not null) {
+ if (!parentMatchesFilter && _wildCardPattern is not null)
+ {
doesMatchFilter = _wildCardPattern.IsMatch(fileSystemInfo.Name);
}
// if entryName, then set it as the entry name of the file or directory in the archive
// The entry name will preserve the directory structure as long as the path is relative to the working directory
- if (entryName is null) {
+ if (entryName is null)
+ {
entryName = GetEntryName(fileSystemInfo, out bool doesPreservePathStructure);
}
@@ -101,8 +109,12 @@ private void AddAdditionForFullyQualifiedPath(string path, List
// If the item being added is a directory and does not have any descendent files that match the filter, finalAdditions - initialAdditions = 0
// If the item being added is a directory and has descendent files that match the filter, finalAdditions > initialAdditions
- if (doesMatchFilter || (!doesMatchFilter && finalAdditions - initialAdditions > 0)) {
- additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
+ if (doesMatchFilter || (!doesMatchFilter && finalAdditions - initialAdditions > 0) && (Flatten && fileSystemInfo is not DirectoryInfo)) {
+ if (!Flatten || (_entryNames is not null && _entryNames.Add(entryName)))
+ {
+ additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
+ }
+
}
}
@@ -140,13 +152,17 @@ private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List? GetResolvedPathFromPSProviderPath(string path, bool pathMustExist) {
From cb4829ca7194767678298dc62abf7a9c9edf755f Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Tue, 16 Aug 2022 21:47:26 -0700
Subject: [PATCH 32/34] added tests for Expand-Archive
---
.../Should-BeArchiveOnlyContaining.psm1 | 8 +
Tests/Compress-Archive.Tests.ps1 | 31 ++--
Tests/Expand-Archive.Tests.ps1 | 137 ++++++++++++++++++
src/Cmdlets/ExpandArchiveCommand.cs | 10 +-
src/Error.cs | 5 +-
src/Localized/Messages.resx | 3 +
src/PathHelper.cs | 23 +--
7 files changed, 182 insertions(+), 35 deletions(-)
diff --git a/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1 b/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
index 0ef8d23..3772ffc 100644
--- a/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
+++ b/Tests/Assertions/Should-BeArchiveOnlyContaining.psm1
@@ -24,6 +24,14 @@ function Should-BeArchiveOnlyContaining {
}
if ($Format -eq "Tar") {
return Should-BeTarArchiveOnlyContaining -ActualValue $ActualValue -ExpectedValue $ExpectedValue -Negate:$Negate -Because $Because -LiteralPath:$LiteralPath -CallerSessionState $CallerSessionState
+ }
+ if ($Format -eq "Tgz") {
+ # Get a temp file
+ $gzipFolderPath = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
+ New-Item -Path $gzipFolderPath -ItemType Directory
+ "7z e $ActualValue -o${gzipFolderPath} -tgzip" | Invoke-Expression
+ $tarFilePath = (Get-ChildItem $gzipFolderPath)[0].FullName
+ return Should-BeTarArchiveOnlyContaining -ActualValue $tarFilePath -ExpectedValue $ExpectedValue -Negate:$Negate -Because $Because -LiteralPath:$LiteralPath -CallerSessionState $CallerSessionState
}
return [pscustomobject]@{
Succeeded = $false
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 21a8ead..bd98576 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -22,10 +22,13 @@ Describe("Microsoft.PowerShell.Archive tests") {
)
if ($Format -eq "Zip") {
- return $Path += ".zip"
+ return $Path += ".zip"
}
if ($Format -eq "Tar") {
- return $Path += ".tar"
+ return $Path += ".tar"
+ }
+ if ($Format -eq "Tgz") {
+ return $Path += ".tar.gz"
}
throw "Format type is not supported"
}
@@ -261,7 +264,8 @@ Describe("Microsoft.PowerShell.Archive tests") {
Context "Basic functional tests" -ForEach @(
@{Format = "Zip"},
- @{Format = "Tar"}
+ @{Format = "Tar"},
+ @{Format = "Tgz"}
) {
BeforeAll {
New-Item TestDrive:/SourceDir -Type Directory | Out-Null
@@ -290,7 +294,7 @@ Describe("Microsoft.PowerShell.Archive tests") {
$destinationPath | Should -BeArchiveOnlyContaining @('Sample-2.txt') -Format $Format
}
- It "Compresses a non-empty directory with format " -Tag td1 {
+ It "Compresses a non-empty directory with format " {
$sourcePath = "TestDrive:/SourceDir/ChildDir-1"
$destinationPath = Add-FileExtensionBasedOnFormat -Path "TestDrive:/archive2" -Format $Format
Compress-Archive -Path $sourcePath -DestinationPath $destinationPath -Format $Format
@@ -809,13 +813,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
BeforeAll {
New-Item -Path TestDrive:/file1.txt -ItemType File
"Hello, World!" | Out-File -FilePath TestDrive:/file1.txt
-
- # Compress a file with different CompressionLevel values
- $path = Join-Path $ScriptRoot "Sample-File"
- Compress-Archive -Path $path -DestinationPath TestDrive:/archive1.zip -CompressionLevel Optimal
- Compress-Archive -Path $path -DestinationPath TestDrive:/archive2.zip -CompressionLevel NoCompression
- Compress-Archive -Path $path -DestinationPath TestDrive:/archive3.zip -CompressionLevel Fastest
- Compress-Archive -Path $path -DestinationPath TestDrive:/archive4.zip -CompressionLevel SmallestSize
}
It "Throws an error when an invalid value is supplied to CompressionLevel" {
@@ -825,14 +822,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
$_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,Microsoft.PowerShell.Archive.CompressArchiveCommand"
}
}
-
- It "Creates an archive with -CompressionLevel" -ForEach @(
- @{CompressionLevel = [System.IO.Compression.CompressionLevel]::Optimal}
- ) {
-
- }
-
-
}
Context "Path Structure Preservation Tests" {
@@ -1117,8 +1106,6 @@ Describe("Microsoft.PowerShell.Archive tests") {
$destinationPath = "TestDrive:/archive1.zip"
Compress-Archive -Path $path -DestinationPath $destinationPath -Flatten
- $x = Convert-Path $destinationPath
- 7z l "${x}" | Write-Verbose -Verbose
$destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
}
@@ -1127,7 +1114,7 @@ Describe("Microsoft.PowerShell.Archive tests") {
$destinationPath = "TestDrive:/archive2.zip"
Compress-Archive -Path $path -DestinationPath $destinationPath -Flatten -ErrorVariable errors
- errors.Count | Should -Be 0
+ $errors.Count | Should -Be 0
$destinationPath | Should -BeArchiveOnlyContaining @("file1.txt") -Format Zip
}
}
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index 206f6c1..1620f27 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -645,6 +645,143 @@ Describe("Expand-Archive Tests") {
}
Context "Large File Tests" {
+ BeforeAll {
+ $numberOfBytes = 512
+ $bytes = [byte[]]::new($numberOfBytes)
+ for ($i = 0; $i -lt $numberOfBytes; $i++) {
+ $bytes[$i] = 1
+ }
+
+ # Create a large file containing 1's
+ $largeFilePath = Join-Path $TestDrive "file1"
+ $fileWith1s = [System.IO.File]::Create($largeFilePath)
+
+ $numberOfTimesToWrite = 5GB / $numberOfBytes
+ for ($i=0; $i -lt $numberOfTimesToWrite; $i++) {
+ $fileWith1s.Write($bytes, 0, $numberOfBytes)
+ }
+ $fileWith1s.Close()
+
+ Compress-Archive -Path TestDrive:/file1 -DestinationPath TestDrive:/large_entry_archive.zip
+ Compress-Archive -Path TestDrive:/file1 -DestinationPath TestDrive:/large_archive.zip -CompressionLevel NoCompression
+ }
+
+ It "Expands an archive whose size is > 4GB" {
+ $destinationPath = "TestDrive:/large_archive_output"
+ { Expand-Archive -Path TestDrive:/large_archive.zip -DestinationPath $destinationPath } | Should -Not -Throw
+ $childItems = Get-ChildItem $destinationPath
+ $childItems.Count | Should -Be 1
+ $childItems[0].Name | Should -Be "file1"
+ }
+ }
+
+ Context "Pipeline tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
+ }
+
+ It "Expands an archive when -Path is passed by pipeline" {
+ $destinationPath = "TestDrive:/archive_output1"
+ "TestDrive:/archive1.zip" | Expand-Archive -DestinationPath $destinationPath
+ $childItems = Get-ChildItem $destinationPath
+ $childItems.Count | Should -Be 1
+ $childItems[0].FullName | Should -Be (Join-Path $TestDrive "archive_output1" "file.txt")
+ }
+
+ It "Expands an archive when -Path is passed by pipeline by name" {
+ $destinationPath = "TestDrive:/archive_output2"
+ $path = [pscustomobject]@{Path = "TestDrive:/archive1.zip"}
+ $path | Expand-Archive -DestinationPath $destinationPath
+ $childItems = Get-ChildItem $destinationPath -Verbose
+ $childItems.Count | Should -Be 1
+ $childItems[0].FullName | Should -Be (Join-Path $TestDrive "archive_output2" "file.txt")
+ }
+
+ It "Throws an error when multiple paths are passed by pipeline" {
+ try {
+ $destinationPath = "TestDrive:/archive_output3"
+ $path = @("TestDrive:/archive1.zip", "TestDrive:/archive2.zip")
+ $path | Expand-Archive -DestinationPath $destinationPath
+ }
+ catch {
+ $_.FullyQualifiedErrorId | Should -Be "MultplePathsPassed,${CmdletClassName}"
+ }
+ }
+ }
+
+ Context "Relative Path Tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+
+ New-Item -Path TestDrive:/directory1 -ItemType Directory
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/directory1/archive1.zip
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive2.zip
+ New-Item -Path TestDrive:/directory2 -ItemType Directory
+ }
+
+ It "Expands an archive when -Path is a relative path" {
+ Push-Location TestDrive:/directory1
+ $destinationPath = "TestDrive:/relative_path_directory"
+ Expand-Archive -Path archive1.zip -DestinationPath $destinationPath
+ $childItems = Get-ChildItem $destinationPath -Verbose
+ $childItems.Count | Should -Be 1
+ $childItems[0].Name | Should -Be "file.txt"
+ Pop-Location
+ }
+ It "Expands an archive when -LiteralPath is a relative path" {
+ Push-Location TestDrive:/directory1
+ $destinationPath = "TestDrive:/relative_literal_path_directory"
+ Expand-Archive -LiteralPath archive1.zip -DestinationPath $destinationPath
+ $childItems = Get-ChildItem $destinationPath -Verbose
+ $childItems.Count | Should -Be 1
+ $childItems[0].Name | Should -Be "file.txt"
+ Pop-Location
+ }
+
+ It "Expands an archive when -DestinationPath is a relative path" {
+ Push-Location TestDrive:/directory2
+ $destinationPath = "destination_path_output"
+ Expand-Archive -Path "TestDrive:/directory1/archive1.zip" -DestinationPath $destinationPath
+ $childItems = Get-ChildItem $destinationPath -Verbose
+ $childItems.Count | Should -Be 1
+ $childItems[0].Name | Should -Be "file.txt"
+ Pop-Location
+ }
+ }
+
+ Context "-Format tests" {
+ BeforeAll {
+ New-Item TestDrive:/file.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+ Compress-Archive -Path TestDrive:/file.txt -DestinationPath TestDrive:/archive1.zip
+ }
+
+ It "Throws an error when an invalid value is supplied to -Format" {
+ try {
+ Expand-Archive -Path TestDrive:/archive1.zip -DestinationPath TestDrive:/output_directory
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "CannotConvertArgumentNoMessage,${CmdletClassName}"
+ }
+ }
+ }
+
+ Context "Module tests" {
+ It "Validate module can be imported when current language is not en-US" {
+ $currentCulture = [System.Threading.Thread]::CurrentThread.CurrentUICulture
+ try {
+ [System.Threading.Thread]::CurrentThread.CurrentCulture = [CultureInfo]::new("he-IL")
+ { Import-Module Microsoft.PowerShell.Archive -Force -ErrorAction Stop } | Should -Not -Throw
+ }
+ finally {
+ [System.Threading.Thread]::CurrentThread.CurrentCulture = $currentCulture
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Cmdlets/ExpandArchiveCommand.cs b/src/Cmdlets/ExpandArchiveCommand.cs
index 3405d0a..c8a75fb 100644
--- a/src/Cmdlets/ExpandArchiveCommand.cs
+++ b/src/Cmdlets/ExpandArchiveCommand.cs
@@ -50,6 +50,8 @@ private enum ParameterSet {
private string? _sourcePath;
+ private bool _didCallProcessRecord;
+
#endregion
public ExpandArchiveCommand()
@@ -64,7 +66,13 @@ protected override void BeginProcessing()
protected override void ProcessRecord()
{
-
+ if (_didCallProcessRecord) {
+ // Throw a terminating error if ProcessRecord was called multiple times (if multiple passed were passed by pipeline)
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.MultiplePathsSpecified);
+ ThrowTerminatingError(errorRecord);
+ } else {
+ _didCallProcessRecord = true;
+ }
}
protected override void EndProcessing()
diff --git a/src/Error.cs b/src/Error.cs
index d79ab66..d8be106 100644
--- a/src/Error.cs
+++ b/src/Error.cs
@@ -42,6 +42,7 @@ internal static string GetErrorMessage(ErrorCode errorCode)
ErrorCode.CannotOverwriteWorkingDirectory => Messages.CannotOverwriteWorkingDirectoryMessage,
ErrorCode.PathResolvedToMultiplePaths => Messages.PathResolvedToMultiplePathsMessage,
ErrorCode.CannotDetermineDestinationPath => Messages.CannotDetermineDestinationPath,
+ ErrorCode.MultiplePathsSpecified => Messages.MultiplePathsSpecified,
_ => throw new ArgumentOutOfRangeException(nameof(errorCode))
};
}
@@ -81,6 +82,8 @@ internal enum ErrorCode
// Expand-Archive: used when the DestinationPath could not be determined
CannotDetermineDestinationPath,
// Compress-Archive: Used when an exception occurs when adding an entry to an archive
- ExceptionOccuredWhileAddingEntry
+ ExceptionOccuredWhileAddingEntry,
+ // Expand:Archive: Used when multiple paths are passed by pipeline
+ MultiplePathsSpecified
}
}
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index 2fd8d20..311d31a 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -201,4 +201,7 @@
Expanding archive entry {0} to destination {1}
+
+ Multiple paths were specified to the cmdlet, but only one is allowed.
+
\ No newline at end of file
diff --git a/src/PathHelper.cs b/src/PathHelper.cs
index 1bff480..a799ff5 100644
--- a/src/PathHelper.cs
+++ b/src/PathHelper.cs
@@ -35,10 +35,14 @@ internal PathHelper(PSCmdlet cmdlet)
internal List GetArchiveAdditions(HashSet fullyQualifiedPaths)
{
- if (Filter is not null) {
+ if (Filter is not null)
+ {
_wildCardPattern = new WildcardPattern(Filter);
- _entryNames = new HashSet();
}
+ if (Flatten)
+ {
+ _entryNames = new HashSet();
+ }
List archiveAdditions = new List(fullyQualifiedPaths.Count);
foreach (var path in fullyQualifiedPaths)
{
@@ -109,12 +113,11 @@ private void AddAdditionForFullyQualifiedPath(string path, List
// If the item being added is a directory and does not have any descendent files that match the filter, finalAdditions - initialAdditions = 0
// If the item being added is a directory and has descendent files that match the filter, finalAdditions > initialAdditions
- if (doesMatchFilter || (!doesMatchFilter && finalAdditions - initialAdditions > 0) && (Flatten && fileSystemInfo is not DirectoryInfo)) {
- if (!Flatten || (_entryNames is not null && _entryNames.Add(entryName)))
+ if (doesMatchFilter || (!doesMatchFilter && finalAdditions - initialAdditions > 0)) {
+ if (!Flatten || (Flatten && fileSystemInfo is not DirectoryInfo && _entryNames is not null && _entryNames.Add(entryName)))
{
additions.Add(new ArchiveAddition(entryName: entryName, fileSystemInfo: fileSystemInfo));
}
-
}
}
@@ -144,15 +147,15 @@ private void AddDescendentEntries(System.IO.DirectoryInfo directoryInfo, List
Date: Wed, 17 Aug 2022 00:39:24 -0700
Subject: [PATCH 33/34] added filter support and tests for Expand-Archive
---
Tests/Compress-Archive.Tests.ps1 | 10 ++++++
Tests/Expand-Archive.Tests.ps1 | 49 +++++++++++++++++++++++++--
src/Cmdlets/CompressArchiveCommand.cs | 17 +++++++---
src/Cmdlets/ExpandArchiveCommand.cs | 26 ++++++++++----
src/Error.cs | 7 ++--
src/Formats/GzipArchive.cs | 2 ++
src/Formats/TarArchive.cs | 19 +++++++----
src/Formats/TarGzArchive.cs | 2 ++
src/Formats/ZipArchive.cs | 2 ++
src/IArchive.cs | 3 ++
src/Localized/Messages.resx | 3 ++
11 files changed, 119 insertions(+), 21 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index bd98576..2eac4fb 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -1060,6 +1060,8 @@ Describe("Microsoft.PowerShell.Archive tests") {
Compress-Archive -Path TestDrive:/directory1 -DestinationPath TestDrive:/archive_with_directory.zip
New-Item -Path TestDrive:/directory1/file2.txt -ItemType File
+
+ Compress-Archive -Path TestDrive:/file1.txt -DestinationPath TestDrive:/cantupdate.tar.gz -Format Tgz
}
It "Does not throw an error when -Update is specified and the archive already exists" {
@@ -1088,6 +1090,14 @@ Describe("Microsoft.PowerShell.Archive tests") {
Compress-Archive -Path TestDrive:/directory1 -DestinationPath $archivePath -WriteMode Update
$archivePath | Should -BeArchiveOnlyContaining @("directory1/", "directory1/file1.txt", "directory1/file2.txt") -Format Zip
}
+
+ It "Throws an error when trying to update a tgz archive" {
+ try {
+ Compress-Archive -Path TestDrive:/directory1 -DestinationPath TestDrive:/cantupdate.tar.gz -WriteMode Update
+ } catch {
+ $_.FullyQualifiedErrorId | Should -Be "ArchiveIsNotUpdateable,Microsoft.PowerShell.Archive.CompressArchiveCommand"
+ }
+ }
}
Context "Flatten tests" {
diff --git a/Tests/Expand-Archive.Tests.ps1 b/Tests/Expand-Archive.Tests.ps1
index 1620f27..0d9d16a 100644
--- a/Tests/Expand-Archive.Tests.ps1
+++ b/Tests/Expand-Archive.Tests.ps1
@@ -20,6 +20,9 @@ Describe("Expand-Archive Tests") {
if ($Format -eq "Tar") {
return $Path += ".tar"
}
+ if (Format -eq "Tgz") {
+ return $Path += ".tar.gz"
+ }
throw "Format type is not supported"
}
}
@@ -352,7 +355,8 @@ Describe("Expand-Archive Tests") {
Context "Basic functionality tests" -ForEach @(
@{Format = "Zip"},
- @{Format = "Tar"}
+ @{Format = "Tar"},
+ @{Format = "Tgz"}
) {
# extract to a directory works
# extract to working directory works when DestinationPath is specified
@@ -673,6 +677,14 @@ Describe("Expand-Archive Tests") {
$childItems.Count | Should -Be 1
$childItems[0].Name | Should -Be "file1"
}
+
+ It "Expands an archive containing an entry whose size is > 4GB" {
+ $destinationPath = "TestDrive:/large_archive_output2"
+ { Expand-Archive -Path TestDrive:/large_entry_archive.zip -DestinationPath $destinationPath } | Should -Not -Throw
+ $childItems = Get-ChildItem $destinationPath
+ $childItems.Count | Should -Be 1
+ $childItems[0].Name | Should -Be "file1"
+ }
}
Context "Pipeline tests" {
@@ -709,7 +721,7 @@ Describe("Expand-Archive Tests") {
$path | Expand-Archive -DestinationPath $destinationPath
}
catch {
- $_.FullyQualifiedErrorId | Should -Be "MultplePathsPassed,${CmdletClassName}"
+ $_.FullyQualifiedErrorId | Should -Be "MultiplePathsPassed,${CmdletClassName}"
}
}
}
@@ -784,4 +796,37 @@ Describe("Expand-Archive Tests") {
}
}
}
+
+ Context "Filter tests" {
+ BeforeAll {
+ New-Item TestDrive:/file1.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/file.txt
+ New-Item TestDrive:/file2.rtf -ItemType File
+ "Content" | Out-File -Path TestDrive:/file.rtf
+ Compress-Archive -Path TestDrive:/file1.txt,TestDrive:/file2.rtf -DestinationPath TestDrive:/archive1.zip
+
+ # Create an archive containing a directory
+ New-Item TestDrive:/directory1 -ItemType Directory
+ New-Item TestDrive:/directory1/file1.txt -ItemType File
+ "Hello, World!" | Out-File -Path TestDrive:/directory1/file1.txt
+ Compress-Archive -Path TestDrive:/directory1 -DestinationPath TestDrive:/archive_containing_directory1
+ }
+
+ It "Filter works" {
+ $destinationPath = "TestDrive:/filter_works_output"
+ Expand-Archive -Path TestDrive:/archive1.zip -DestinationPath $destinationPath -Filter *.txt
+ $childItems = Get-ChildItem $destinationPath
+ $childItems.Count | Should -Be 1
+ $childItems[0].Name | Should -Be "file1.txt"
+ }
+
+ It "Expands parent directory of a file that matches filter when the parent directory does not match the filter" {
+ $destinationPath = "TestDrive:/expand_parent_directory_output"
+ Expand-Archive -Path TestDrive:/archive_containing_directory1 -DestinationPath $destinationPath -Filter *.txt
+ $childItems = Get-ChildItem $destinationPath -Recurse -Name
+ $childItems.Count | Should -Be 2
+ $childItems | Should -Contain "directory1"
+ $childItems | Should -Contain (Join-Path "directory1" "file1.txt")
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Cmdlets/CompressArchiveCommand.cs b/src/Cmdlets/CompressArchiveCommand.cs
index 6aac79c..9a32cc4 100644
--- a/src/Cmdlets/CompressArchiveCommand.cs
+++ b/src/Cmdlets/CompressArchiveCommand.cs
@@ -186,7 +186,9 @@ protected override void EndProcessing()
IArchive? archive = null;
try
{
- if (ShouldProcess(target: DestinationPath, action: Messages.Create))
+ // If the archive is in Update mode, we want to skip the ShouldProcess check
+ // This is necessary if we want to check if the archive is updateable
+ if (WriteMode == WriteMode.Update || ShouldProcess(target: DestinationPath, action: Messages.Create))
{
// If the WriteMode is overwrite, delete the existing archive
if (WriteMode == WriteMode.Overwrite)
@@ -198,6 +200,13 @@ protected override void EndProcessing()
archive = ArchiveFactory.GetArchive(format: Format ?? ArchiveFormat.Zip, archivePath: DestinationPath, archiveMode: archiveMode, compressionLevel: CompressionLevel);
_didCreateNewArchive = archiveMode != ArchiveMode.Update;
}
+
+ // If the cmdlet is in Update mode and the archive does not support updates, throw an error
+ if (archive is not null && !archive.IsUpdateable)
+ {
+ var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.ArchiveIsNotUpdateable, DestinationPath);
+ ThrowTerminatingError(errorRecord);
+ }
long numberOfAdditions = archiveAdditions.Count;
long numberOfAddedItems = 0;
@@ -208,8 +217,8 @@ protected override void EndProcessing()
{
// Update progress
var percentComplete = numberOfAddedItems / (float)numberOfAdditions * 100f;
-
- progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, $"{percentComplete:0.0}");
+ var statusDescription = string.Format(Messages.ProgressDisplay, $"{percentComplete:0.0}");
+ progressRecord = new ProgressRecord(activityId: 1, activity: "Compress-Archive", statusDescription: statusDescription);
progressRecord.PercentComplete = (int)percentComplete;
WriteProgress(progressRecord);
@@ -257,7 +266,7 @@ protected override void EndProcessing()
// Once all items in the archive are processed, show progress as 100%
// This code is here and not in the loop because we want it to run even if there are no items to add to the archive
- progressRecord.StatusDescription = string.Format(Messages.ProgressDisplay, "100.0");
+ progressRecord = new ProgressRecord(1, "Compress-Archive", string.Format(Messages.ProgressDisplay, "100.0"));
progressRecord.PercentComplete = 100;
WriteProgress(progressRecord);
}
diff --git a/src/Cmdlets/ExpandArchiveCommand.cs b/src/Cmdlets/ExpandArchiveCommand.cs
index c8a75fb..1e69a8c 100644
--- a/src/Cmdlets/ExpandArchiveCommand.cs
+++ b/src/Cmdlets/ExpandArchiveCommand.cs
@@ -42,6 +42,10 @@ private enum ParameterSet {
[Parameter]
public SwitchParameter PassThru { get; set; }
+ [Parameter]
+ [ValidateNotNullOrEmpty]
+ public string? Filter { get; set; }
+
#region PrivateMembers
private PathHelper _pathHelper;
@@ -52,6 +56,8 @@ private enum ParameterSet {
private bool _didCallProcessRecord;
+ private WildcardPattern? _wildcardPattern;
+
#endregion
public ExpandArchiveCommand()
@@ -78,9 +84,7 @@ protected override void ProcessRecord()
protected override void EndProcessing()
{
// Resolve Path or LiteralPath
- bool checkForWildcards = ParameterSetName == nameof(ParameterSet.Path);
- string path = checkForWildcards ? Path : LiteralPath;
- ValidateSourcePath(path);
+ ValidateSourcePath(ParameterSetName == nameof(ParameterSet.Path) ? Path : LiteralPath);
Debug.Assert(_sourcePath is not null);
// Determine archive format based on sourcePath
@@ -132,13 +136,21 @@ protected override void EndProcessing()
// Write a verbose message saying "Expanding archive ..."
WriteVerbose(string.Format(Messages.ExpandingArchiveMessage, DestinationPath));
+ // If a value has been supplied to -Filter, create the object that will perform wildcard matching
+ if (Filter is not null) {
+ _wildcardPattern = new WildcardPattern(Filter);
+ }
+
// Get the next entry in the archive and process it
var nextEntry = archive.GetNextEntry();
while (nextEntry != null)
{
- // The process function will write the progress
- ProcessArchiveEntry(nextEntry);
- nextEntry = archive.GetNextEntry();
+ // If a value has been supplied to -Filter and the entry name of nextEntry does not match the filter
+ // skip the entry
+ if ((Filter is null) || (_wildcardPattern is not null && _wildcardPattern.IsMatch(nextEntry.Name)))
+ {
+ ProcessArchiveEntry(nextEntry);
+ }
// Update progress info
numberOfExpandedItems++;
@@ -149,6 +161,8 @@ protected override void EndProcessing()
progressRecord.PercentComplete = (int)percentComplete;
WriteProgress(progressRecord);
}
+
+ nextEntry = archive.GetNextEntry();
}
// Show progress as 100% complete
diff --git a/src/Error.cs b/src/Error.cs
index d8be106..84adc58 100644
--- a/src/Error.cs
+++ b/src/Error.cs
@@ -42,7 +42,8 @@ internal static string GetErrorMessage(ErrorCode errorCode)
ErrorCode.CannotOverwriteWorkingDirectory => Messages.CannotOverwriteWorkingDirectoryMessage,
ErrorCode.PathResolvedToMultiplePaths => Messages.PathResolvedToMultiplePathsMessage,
ErrorCode.CannotDetermineDestinationPath => Messages.CannotDetermineDestinationPath,
- ErrorCode.MultiplePathsSpecified => Messages.MultiplePathsSpecified,
+ ErrorCode.MultiplePathsSpecified => Messages.MultiplePathsSpecifiedMessage,
+ ErrorCode.ArchiveIsNotUpdateable => Messages.ArchiveIsNotUpdateableMessage,
_ => throw new ArgumentOutOfRangeException(nameof(errorCode))
};
}
@@ -84,6 +85,8 @@ internal enum ErrorCode
// Compress-Archive: Used when an exception occurs when adding an entry to an archive
ExceptionOccuredWhileAddingEntry,
// Expand:Archive: Used when multiple paths are passed by pipeline
- MultiplePathsSpecified
+ MultiplePathsSpecified,
+ // Compress-Archive: Used when the archive does not support being updated
+ ArchiveIsNotUpdateable
}
}
diff --git a/src/Formats/GzipArchive.cs b/src/Formats/GzipArchive.cs
index d2c08d2..0838686 100644
--- a/src/Formats/GzipArchive.cs
+++ b/src/Formats/GzipArchive.cs
@@ -30,6 +30,8 @@ internal class GzipArchive : IArchive
string IArchive.Path => _path;
+ public bool IsUpdateable => false;
+
public GzipArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel)
{
_mode = mode;
diff --git a/src/Formats/TarArchive.cs b/src/Formats/TarArchive.cs
index 09b2938..6749446 100644
--- a/src/Formats/TarArchive.cs
+++ b/src/Formats/TarArchive.cs
@@ -30,6 +30,8 @@ internal class TarArchive : IArchive
string IArchive.Path => _path;
+ public bool IsUpdateable => true;
+
public TarArchive(string path, ArchiveMode mode, FileStream fileStream)
{
_mode = mode;
@@ -151,16 +153,13 @@ internal class TarArchiveEntry : IEntry {
// Underlying object is System.Formats.Tar.TarEntry
private TarEntry _entry;
- private IEntry _objectAsIEntry;
-
- string IEntry.Name => _entry.Name;
+ public string Name => _entry.Name;
- bool IEntry.IsDirectory => _entry.EntryType == TarEntryType.Directory;
+ public bool IsDirectory => _entry.EntryType == TarEntryType.Directory;
public TarArchiveEntry(TarEntry entry)
{
_entry = entry;
- _objectAsIEntry = this;
}
void IEntry.ExpandTo(string destinationPath)
@@ -173,7 +172,7 @@ void IEntry.ExpandTo(string destinationPath)
}
var lastWriteTime = _entry.ModificationTime.LocalDateTime;
- if (_objectAsIEntry.IsDirectory)
+ if (IsDirectory)
{
Directory.CreateDirectory(destinationPath);
Directory.SetLastWriteTime(destinationPath, lastWriteTime);
@@ -182,10 +181,16 @@ void IEntry.ExpandTo(string destinationPath)
_entry.ExtractToFile(destinationPath, overwrite: false);
File.SetLastWriteTime(destinationPath, lastWriteTime);
}
+
+ SetFileAttributes(destinationPath);
}
private void SetFileAttributes(string destinationPath) {
-
+ if (System.Environment.OSVersion.Platform == System.PlatformID.Unix
+ || System.Environment.OSVersion.Platform == System.PlatformID.MacOSX) {
+
+ File.SetUnixFileMode(destinationPath, _entry.Mode);
+ }
}
}
}
diff --git a/src/Formats/TarGzArchive.cs b/src/Formats/TarGzArchive.cs
index 54d908f..cc45250 100644
--- a/src/Formats/TarGzArchive.cs
+++ b/src/Formats/TarGzArchive.cs
@@ -36,6 +36,8 @@ internal class TarGzArchive : IArchive
string IArchive.Path => _path;
+ public bool IsUpdateable => false;
+
public TarGzArchive(string path, ArchiveMode mode, FileStream fileStream, CompressionLevel compressionLevel)
{
_path = path;
diff --git a/src/Formats/ZipArchive.cs b/src/Formats/ZipArchive.cs
index 5e2a41e..a406928 100644
--- a/src/Formats/ZipArchive.cs
+++ b/src/Formats/ZipArchive.cs
@@ -30,6 +30,8 @@ internal class ZipArchive : IArchive
string IArchive.Path => _archivePath;
+ public bool IsUpdateable => true;
+
internal int NumberOfEntries => _zipArchive.Entries.Count;
public ZipArchive(string archivePath, ArchiveMode mode, FileStream archiveStream, CompressionLevel compressionLevel)
diff --git a/src/IArchive.cs b/src/IArchive.cs
index 18cfc75..a94389a 100644
--- a/src/IArchive.cs
+++ b/src/IArchive.cs
@@ -9,6 +9,9 @@ namespace Microsoft.PowerShell.Archive
{
interface IArchive: IDisposable
{
+ // Can the archive be updated?
+ public bool IsUpdateable { get; }
+
// Get what mode the archive is in
public ArchiveMode Mode { get; }
diff --git a/src/Localized/Messages.resx b/src/Localized/Messages.resx
index 311d31a..42fe119 100644
--- a/src/Localized/Messages.resx
+++ b/src/Localized/Messages.resx
@@ -204,4 +204,7 @@
Multiple paths were specified to the cmdlet, but only one is allowed.
+
+ The archive {0} could not be updated because the format does not support updates.
+
\ No newline at end of file
From 716fd39b20dcb5508a1fbb1f851f565d408cb9ad Mon Sep 17 00:00:00 2001
From: ayousuf3 <23.abdullah.y@gmail.com>
Date: Wed, 17 Aug 2022 12:56:10 -0700
Subject: [PATCH 34/34] fixed failing tar.gz tests
---
Tests/Compress-Archive.Tests.ps1 | 2 +-
src/Cmdlets/CompressArchiveCommand.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Tests/Compress-Archive.Tests.ps1 b/Tests/Compress-Archive.Tests.ps1
index 2eac4fb..95d5d06 100644
--- a/Tests/Compress-Archive.Tests.ps1
+++ b/Tests/Compress-Archive.Tests.ps1
@@ -1091,7 +1091,7 @@ Describe("Microsoft.PowerShell.Archive tests") {
$archivePath | Should -BeArchiveOnlyContaining @("directory1/", "directory1/file1.txt", "directory1/file2.txt") -Format Zip
}
- It "Throws an error when trying to update a tgz archive" {
+ It "Throws an error when trying to update a tgz archive" -Tag hi {
try {
Compress-Archive -Path TestDrive:/directory1 -DestinationPath TestDrive:/cantupdate.tar.gz -WriteMode Update
} catch {
diff --git a/src/Cmdlets/CompressArchiveCommand.cs b/src/Cmdlets/CompressArchiveCommand.cs
index 9a32cc4..05f7662 100644
--- a/src/Cmdlets/CompressArchiveCommand.cs
+++ b/src/Cmdlets/CompressArchiveCommand.cs
@@ -202,7 +202,7 @@ protected override void EndProcessing()
}
// If the cmdlet is in Update mode and the archive does not support updates, throw an error
- if (archive is not null && !archive.IsUpdateable)
+ if (WriteMode == WriteMode.Update && archive is not null && !archive.IsUpdateable)
{
var errorRecord = ErrorMessages.GetErrorRecord(ErrorCode.ArchiveIsNotUpdateable, DestinationPath);
ThrowTerminatingError(errorRecord);