diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f42ee3..1893963 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,31 @@
# CHANGELOG
+## 06/23/2025
+
+- Added commands supporting several algorithms to compress and decompress strings:
+ - `ConvertFrom-BrotliString` & `ConvertTo-BrotliString` (using to BrotliSharpLib)
+ - `ConvertFrom-DeflateString` & `ConvertTo-DeflateString` (from CLR)
+ - `ConvertFrom-ZlibString` & `ConvertTo-ZlibString` (custom implementation)
+- Added commands for `.tar` entry management with a reduced set of operations compared to `zip` entry management:
+ - `Get-TarEntry`: Lists entries, serving as the main entry point for `TarEntry` cmdlets.
+ - `Get-TarEntryContent`: Retrieves the content of a tar entry.
+ - `Expand-TarEntry`: Extracts a tar entry to a file.
+- Added commands to compress files and folders into `.tar` archives and extract `.tar` archives with various compression algorithms:
+ - `Compress-TarArchive` & `Expand-TarArchive`: Supported compression algorithms include `gz`, `bz2`, `zst`, `lz`, and `none` (no compression).
+- Removed commands:
+ - `Compress-GzipArchive` & `Expand-GzipArchive`: These were deprecated as they only supported single-file compression, which is now better handled by the module’s `.tar` archive functionality. For a workaround to compress or decompress single files using gzip, see [Example 2 in `ConvertTo-GzipString`][example2converttogzipstring], which demonstrates using:
+
+ ```powershell
+ [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-GzipString
+ ```
+
+This update was made possible by the following projects. If you find them helpful, please consider starring their repositories:
+
+- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
+- [SharpCompress](https://github.com/adamhathcock/sharpcompress)
+- [BrotliSharpLib](https://github.com/master131/BrotliSharpLib)
+- [ZstdSharp](https://github.com/oleg-st/ZstdSharp)
+
## 01/10/2025
- Code improvements.
@@ -38,3 +64,5 @@
- Moved from `[PSCompression.ZipEntryExtensions]::NormalizePath` to `[PSCompression.Extensions.PathExtensions]::NormalizePath`.
- `Get-ZipEntry` command:
- Renamed Parameter `-EntryType` to `-Type`.
+
+[example2converttogzipstring]: https://github.com/santisq/PSCompression/blob/main/docs/en-US/ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string
diff --git a/README.md b/README.md
index 4184f9e..669a26a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
PSCompression
-Zip and GZip utilities for PowerShell
+Zip, tar, and string compression utilities for PowerShell!
[](https://github.com/santisq/PSCompression/actions/workflows/ci.yml)
@@ -11,157 +11,156 @@
-PSCompression is a PowerShell Module that provides Zip and Gzip utilities for compression, expansion and management. It also solves a few issues with Zip compression existing in _built-in PowerShell_.
+`PSCompression` is a PowerShell module that provides utilities for creating, managing, and extracting zip and tar archives, as well as compressing and decompressing strings. It overcomes limitations in built-in PowerShell archive cmdlets (e.g., 2 GB zip file limits) and supports multiple compression algorithms, including gzip, bzip2, Zstandard, lzip, Brotli, Deflate, and Zlib. Built for cross-platform use, it’s compatible with Windows, Linux, and macOS.
-## What does this Module offer?
+## Features
-### Zip Cmdlets
+- __Zip Archive Management__: Create, list, extract, retrieve content, modify, and remove entries in zip archives with pipeline support.
+- __Tar Archive Management__: Compress and extract tar archives with support for `gz`, `bz2`, `zst`, `lz`, and uncompressed (`none`) formats.
+- __Tar Entry Management__: List, extract, and retrieve content from individual tar entries.
+- __String Compression__: Compress and decompress strings using Brotli, Deflate, Gzip, and Zlib algorithms.
-
-
-
-Cmdlet
-Description
-
-
-
-
-[`Get-ZipEntry`](docs/en-US/Get-ZipEntry.md)
-
-
-
-
-Main entry point for the `*-ZipEntry` cmdlets in this module. It can list zip archive entries from specified paths or input stream.
-
-
-
-
-
-
-[`Expand-ZipEntry`](docs/en-US/Expand-ZipEntry.md)
-
-
-
-
-Expands zip entries to a destination directory.
-
-
-
-
-
-
-[`Get-ZipEntryContent`](docs/en-US/Get-ZipEntryContent.md)
-
-
-
-
-Gets the content of one or more zip entries.
-
-
-
-
-
-
-[`New-ZipEntry`](docs/en-US/New-ZipEntry.md)
-
-
-Creates zip entries from specified path or paths.
-
-
-
-
-[`Remove-ZipEntry`](docs/en-US/Remove-ZipEntry.md)
-
-
-Removes zip entries from one or more zip archives.
-
-
-
-
-[`Rename-ZipEntry`](docs/en-US/Rename-ZipEntry.md)
-
-
-Renames zip entries from one or more zip archives.
-
-
-
-
-[`Set-ZipEntryContent`](docs/en-US/Set-ZipEntryContent.md)
-
-
-Sets or appends content to a zip entry.
-
-
-
+# Cmdlets
-[`Compress-ZipArchive`](docs/en-US/Compress-ZipArchive.md)
+## Cmdlets
-
-
+### Zip Archive Cmdlets
-Similar capabilities as
-[`Compress-Archive`](docs/en-US/https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.archive/compress-archive?view=powershell-7.2)
-and overcomes a few issues with the built-in cmdlet (2 GB limit and more).
-
-
-
+
+
+ Cmdlet
+ Alias
+ Description
+
+
+ Compress-ZipArchive
+ zipcompress
+ Compresses files and folders into a zip archive, overcoming built-in PowerShell limitations.
+
+
+ Expand-ZipEntry
+ unzipentry
+ Extracts individual zip entries to a destination directory.
+
+
+ Get-ZipEntry
+ zipge
+ Lists zip archive entries from paths or streams, serving as the entry point for zip cmdlets.
+
+
+ Get-ZipEntryContent
+ zipgec
+ Retrieves the content of zip entries as text or bytes.
+
+
+ New-ZipEntry
+ zipne
+ Adds new entries to a zip archive from files or paths.
+
+
+ Remove-ZipEntry
+ ziprm
+ Removes entries from one or more zip archives.
+
+
+ Rename-ZipEntry
+ zipren
+ Renames entries in one or more zip archives.
+
+
+ Set-ZipEntryContent
+ zipsc
+ Sets or appends content to a zip entry.
+
-
-### Gzip Cmdlets
+### Tar Archive Cmdlets
-
-
-Cmdlet
-Description
-
-
-
-
-[`Compress-GzipArchive`](docs/en-US/Compress-GzipArchive.md)
-
-
-
-Can compress one or more specified file paths into a Gzip file.
-
-
-
-
-
-[`ConvertFrom-GzipString`](docs/en-US/ConvertFrom-GzipString.md)
-
-
-
-Expands Gzip Base64 input strings.
-
-
-
-
-
-
-[`ConvertTo-GzipString`](docs/en-US/ConvertTo-GzipString.md)
-
-
-
-Can compress input strings into Gzip Base64 strings or raw bytes.
-
-
-
-
-
-
-[`Expand-GzipArchive`](docs/en-US/Expand-GzipArchive.md)
-
-
-
+
+ Cmdlet
+ Alias
+ Description
+
+
+ Compress-TarArchive
+ tarcompress
+ Compresses files and folders into a tar archive with optional compression (gz, bz2, zst, lz, none).
+
+
+ Expand-TarArchive
+ untar
+ Extracts a tar archive with support for gz, bz2, zst, lz, and uncompressed formats.
+
+
+ Expand-TarEntry
+ untarentry
+ Extracts individual tar entries to a destination directory.
+
+
+ Get-TarEntry
+ targe
+ Lists tar archive entries from paths or streams, serving as the entry point for tar cmdlets.
+
+
+ Get-TarEntryContent
+ targc
+ Retrieves the content of tar entries as text or bytes.
+
+
-Expands Gzip compressed files to a destination path or to the [success stream](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_output_streams?view=powershell-7.3#success-stream).
+### String Compression Cmdlets
-
-
+
-
+
+> [!NOTE]
+> The `Compress-GzipArchive` and `Expand-GzipArchive` cmdlets have been removed, as their single-file gzip functionality is now handled by `Compress-TarArchive` and `Expand-TarArchive`. For a workaround to compress or decompress single files using gzip, see [Example 2 in `ConvertTo-GzipString`](./docs/en-US/ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string).
## Documentation
@@ -189,6 +188,18 @@ Set-Location ./PSCompression
This module has no external requirements and is compatible with __Windows PowerShell 5.1__ and [__PowerShell 7+__](https://github.com/PowerShell/PowerShell).
+## Acknowledgments
+
+This module is powered by the following open-source projects:
+
+- [SharpZipLib](https://github.com/icsharpcode/SharpZipLib)
+- [SharpCompress](https://github.com/adamhathcock/sharpcompress)
+- [BrotliSharpLib](https://github.com/master131/BrotliSharpLib)
+- [ZstdSharp](https://github.com/oleg-st/ZstdSharp)
+- [System.IO.Compression](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
+
+If you find these projects helpful, consider starring their repositories!
+
## Contributing
Contributions are more than welcome, if you wish to contribute, fork this repository and submit a pull request with the changes.
diff --git a/build.ps1 b/build.ps1
index 650b40b..0ecffa6 100644
--- a/build.ps1
+++ b/build.ps1
@@ -38,7 +38,7 @@ if (-not ('ProjectBuilder.ProjectInfo' -as [type])) {
}
}
-$projectInfo = [ProjectBuilder.ProjectInfo]::Create($PSScriptRoot, $Configuration)
+$projectInfo = [ProjectBuilder.ProjectInfo]::Create($PSScriptRoot, $Configuration, $PSVersionTable.PSVersion)
$projectInfo.GetRequirements() | Import-Module -DisableNameChecking -Force
$ErrorActionPreference = $prev
diff --git a/docs/en-US/Compress-GzipArchive.md b/docs/en-US/Compress-GzipArchive.md
deleted file mode 100644
index 876e09a..0000000
--- a/docs/en-US/Compress-GzipArchive.md
+++ /dev/null
@@ -1,290 +0,0 @@
----
-external help file: PSCompression-help.xml
-Module Name: PSCompression
-online version: https://github.com/santisq/PSCompression
-schema: 2.0.0
----
-
-# Compress-GzipArchive
-
-## SYNOPSIS
-
-Creates a Gzip compressed file from specified paths or input bytes.
-
-## SYNTAX
-
-### Path (Default)
-
-```powershell
-Compress-GzipArchive
- -Path
- -Destination
- [-CompressionLevel ]
- [-Update]
- [-Force]
- [-PassThru]
- []
-```
-
-### LiteralPath
-
-```powershell
-Compress-GzipArchive
- -LiteralPath
- -Destination
- [-CompressionLevel ]
- [-Update]
- [-Force]
- [-PassThru]
- []
-```
-
-### InputBytes
-
-```powershell
-Compress-GzipArchive
- -InputBytes
- -Destination
- [-CompressionLevel ]
- [-Update]
- [-Force]
- [-PassThru]
- []
-```
-
-## DESCRIPTION
-
-The `Compress-GzipArchive` cmdlet can compress one or more specified file paths into a single Gzip archive using the [`GzipStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream).
-
-> [!TIP]
-> For expansion see [`Expand-GzipArchive`](Expand-ZipEntry.md).
-
-## EXAMPLES
-
-### Example 1: Create a Gzip compressed file from a File Path
-
-```powershell
-PS ..\pwsh> Compress-GzipArchive path\to\myFile.ext -Destination myFile.gz
-```
-
-If the destination does not end with `.gz` the extension is automatically added.
-
-### Example 2: Create a Gzip compressed file from a string
-
-```powershell
-PS ..\pwsh> 'hello world!' | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -Destination .\files\file.gz
-```
-
-Demonstrates how `-AsByteStream` works on `ConvertTo-GzipString`.
-Sends the compressed bytes to `Compress-GzipArchive`.
-
-### Example 3: Append content to a Gzip archive
-
-```powershell
-PS ..\pwsh> 'this is new content...' | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -Destination .\files\file.gz -Update
-```
-
-Demonstrates how `-Update` works.
-
-### Example 4: Replace a Gzip archive with new content
-
-```powershell
-PS ..\pwsh> $lorem = Invoke-RestMethod loripsum.net/api/10/long/plaintext
-PS ..\pwsh> $lorem | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -Destination .\files\file.gz -Force
-```
-
-Demonstrates how `-Force` works.
-
-### Example 5: Compressing multiple files into one Gzip archive
-
-```powershell
-PS ..\pwsh> 0..10 | ForEach-Object {
- Invoke-RestMethod loripsum.net/api/10/long/plaintext -OutFile .\files\lorem$_.txt
-}
-
-# Check the total Length of the downloaded files
-PS ..\pwsh> (Get-Content .\files\lorem*.txt | Measure-Object Length -Sum).Sum / 1kb
-86.787109375
-
-# Check the total Length after compression
-PS ..\pwsh> (Compress-GzipArchive .\files\lorem*.txt -Destination .\files\mergedLorem.gz -PassThru).Length / 1kb
-27.6982421875
-```
-
-> [!NOTE]
-> Due to the nature of Gzip without Tar, all file contents are merged into a single file.
-
-## PARAMETERS
-
-### -CompressionLevel
-
-Define the compression level that should be used.
-See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details.
-
-```yaml
-Type: CompressionLevel
-Parameter Sets: (All)
-Aliases:
-Accepted values: Optimal, Fastest, NoCompression, SmallestSize
-
-Required: False
-Position: Named
-Default value: Optimal
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Destination
-
-The path where to store the Gzip compressed file.
-
-> [!NOTE]
->
-> - The parent directory is created if it does not exist.
-> - If the path does not have an extension, the cmdlet appends the `.gz` file name extension.
-
-```yaml
-Type: String
-Parameter Sets: (All)
-Aliases: DestinationPath
-
-Required: True
-Position: 1
-Default value: None
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Force
-
-Overwrites the Gzip archive if exists, otherwise it creates it.
-
-> [!NOTE]
-> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: (All)
-Aliases:
-
-Required: False
-Position: Named
-Default value: False
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -InputBytes
-
-This cmdlet can take input bytes from pipeline to create the output `.gz` archive file.
-
-> [!NOTE]
-> This parameter is meant to be used exclusively in combination with [`ConvertTo-GzipString -AsByteStream`](./ConvertTo-GzipString.md#example-2-create-a-gzip-compressed-file-from-a-string).
-
-```yaml
-Type: Byte[]
-Parameter Sets: InputBytes
-Aliases:
-
-Required: True
-Position: Named
-Default value: None
-Accept pipeline input: True (ByValue)
-Accept wildcard characters: False
-```
-
-### -LiteralPath
-
-Specifies the path or paths to the files that you want to add to the Gzip archive file.
-Unlike the `-Path` Parameter, the value of `-LiteralPath` is used exactly as it's typed.
-No characters are interpreted as wildcards
-
-```yaml
-Type: String[]
-Parameter Sets: LiteralPath
-Aliases: PSPath
-
-Required: True
-Position: Named
-Default value: None
-Accept pipeline input: True (ByPropertyName)
-Accept wildcard characters: False
-```
-
-### -PassThru
-
-Outputs the object representing the compressed file.
-The cmdlet produces no output by default.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: (All)
-Aliases:
-
-Required: False
-Position: Named
-Default value: False
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Path
-
-Specifies the path or paths to the files that you want to add to the Gzip archive file.
-To specify multiple paths, and include files in multiple locations, use commas to separate the paths.
-This Parameter accepts wildcard characters.
-Wildcard characters allow you to add all files in a directory to your archive file.
-
-```yaml
-Type: String[]
-Parameter Sets: Path
-Aliases:
-
-Required: True
-Position: 0
-Default value: None
-Accept pipeline input: True (ByValue)
-Accept wildcard characters: True
-```
-
-### -Update
-
-Appends content to the existing Gzip file if exists, otherwise it creates it.
-
-> [!NOTE]
-> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: (All)
-Aliases:
-
-Required: False
-Position: Named
-Default value: False
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### CommonParameters
-
-This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
-
-## INPUTS
-
-### String
-
-You can pipe paths to this cmdlet. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet.
-
-## OUTPUTS
-
-### None
-
-By default, this cmdlet produces no output.
-
-### FileInfo
-
-When the `-PassThru` switch is used this cmdlet outputs the `FileInfo` instance representing the compressed file.
diff --git a/docs/en-US/Compress-TarArchive.md b/docs/en-US/Compress-TarArchive.md
new file mode 100644
index 0000000..893a01e
--- /dev/null
+++ b/docs/en-US/Compress-TarArchive.md
@@ -0,0 +1,274 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# Compress-TarArchive
+
+## SYNOPSIS
+
+The `Compress-TarArchive` cmdlet creates a compressed tar archive file from one or more specified files or directories. It supports multiple compression algorithms and provides flexible file inclusion and exclusion options, similar to the `Compress-ZipArchive` cmdlet.
+
+## SYNTAX
+
+### Path
+
+```powershell
+Compress-TarArchive
+ [-Path]
+ [-Destination]
+ [-Algorithm ]
+ [-CompressionLevel ]
+ [-Force]
+ [-PassThru]
+ [-Exclude ]
+ []
+```
+
+### LiteralPath
+
+```powershell
+Compress-TarArchive
+ -LiteralPath
+ [-Destination]
+ [-Algorithm ]
+ [-CompressionLevel ]
+ [-Force]
+ [-PassThru]
+ [-Exclude ]
+ []
+```
+
+## DESCRIPTION
+
+The `Compress-TarArchive` cmdlet creates a tar archive, optionally compressed with algorithms like gzip, bzip2, zstd, or lz4, in a PowerShell-native environment. It simplifies file and directory archiving by integrating seamlessly with PowerShell’s object-oriented pipeline, allowing flexible file selection through cmdlets like `Get-ChildItem` or `Get-Item`. With support for selective inclusion via `-Exclude`, customizable compression levels, and the ability to overwrite existing archives, it provides a convenient alternative to traditional tar utilities for PowerShell users, while preserving directory structures and metadata.
+
+## EXAMPLES
+
+### Example 1: Compress all `.log` files in a directory
+
+```powershell
+Get-ChildItem C:\Logs -Recurse -Filter *.log |
+ Compress-TarArchive -Destination C:\Archives\logs.tar.gz
+```
+
+This example demonstrates how to compress all `.log` files in the `C:\Logs` directory into a gzip-compressed tar archive named `logs.tar.gz` in the `C:\Archives` directory.
+
+> [!NOTE]
+> If not specified, the cmdlet will use the gzip algorithm as default.
+
+### Example 2: Compress a folder using `Fastest` Compression Level
+
+```powershell
+Compress-TarArchive -Path .\path -Destination myPath.tar.gz -CompressionLevel Fastest
+```
+
+This example shows how to compress the entire path directory into a gzip-compressed tar archive named `myPath.tar.gz` using the `Fastest` compression level for quicker processing.
+
+### Example 3: Replacing an existing Tar Archive
+
+```powershell
+Compress-TarArchive -Path .\path -Destination dest.tar.gz -Force
+```
+
+This example illustrates how to create a new tar archive named `dest.tar.gz` from the path directory, overwriting any existing archive with the same name using the `-Force` parameter.
+
+### Example 4: Exclude files and folders from source
+
+```powershell
+Compress-TarArchive -Path .\path -Destination myPath.tar.gz -Exclude *.xyz, *\test\*
+```
+
+This example shows how to compress all items in `path` excluding all files having a `.xyz` extension and excluding
+a folder with name `test` and all its child items.
+
+> [!TIP]
+>
+> The `-Exclude` parameter supports [wildcard patterns](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_wildcards?view=powershell-7.4&viewFallbackFrom=powershell-7.3),
+exclusion patterns are tested against the items `.FullName` property.
+
+### Example 5: Compress a directory using bzip2 algorithm
+
+```powershell
+Compress-TarArchive -Path .\data -Destination C:\Backups\data.tar.bz2 -Algorithm bz2
+```
+
+This example demonstrates how to compress the `data` directory into a bzip2-compressed tar archive named `data.tar.bz2` in the `C:\Backups` directory using the `bz2` algorithm.
+
+## PARAMETERS
+
+### -Algorithm
+
+Specifies the compression algorithm to use when creating the tar archive. Supported algorithms include `gz`, `bz2`, `zst`, `lz`, and `none` (no compression).
+
+> [!NOTE]
+> If not specified, the archive is created using the gzip algorithm (`gz`).
+
+```yaml
+Type: Algorithm
+Parameter Sets: (All)
+Aliases:
+Accepted values: gz, bz2, zst, lz, none
+Required: False
+Position: Named
+Default value: none
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -CompressionLevel
+
+Specifies the compression level for the selected algorithm, balancing speed and file size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details. The default is algorithm-dependent but typically `Optimal`.
+
+```yaml
+Type: CompressionLevel
+Parameter Sets: (All)
+Aliases:
+Accepted values: Optimal, Fastest, NoCompression, SmallestSize
+Required: False
+Position: Named
+Default value: Optimal
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Destination
+
+Specifies the path to the tar archive output file. The destination must include the file name and either an absolute or relative path.
+
+> [!NOTE]
+> If the file name lacks an extension, the `-Algorithm` parameter determines the extension is appended (e.g., `.tar.gz` for `gz`, `.tar` for `none`).
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases: DestinationPath
+Required: True
+Position: 1
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Exclude
+
+Specifies an array of string patterns to exclude files or directories from the archive. Matching items are excluded based on their `.FullName` property. Wildcard characters are supported.
+
+> [!NOTE]
+> Patterns are tested against the object's `.FullName` property.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: True
+```
+
+### -Force
+
+Overwrites the destination tar archive if it exists, creating a new one. All existing entries are lost.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -LiteralPath
+
+Specifies the exact path or paths to the files or directories to include in the archive file. Unlike the `-Path` parameter, the value of `-LiteralPath` is used exactly as typed, with no wildcard character interpretation.
+
+```yaml
+Type: String[]
+Parameter Sets: LiteralPath
+Aliases: PSPath
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -PassThru
+
+Outputs a `FileInfo` object representing the created tar archive. By default, the cmdlet produces no output.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Path
+
+Specifies the path or paths to the files or directories to include in the archive file. To specify multiple paths and include files from multiple locations, use commas to separate the paths. This parameter accepts wildcard characters, allowing you to include all files in a directory or match specific patterns.
+
+> [!TIP]
+> Using wildcards with a root directory affects the archive's contents:
+>
+> - To create an archive that includes the root directory and all its files and subdirectories, specify the root directory without wildcards. For example: `-Path C:\Reference`
+> - To create an archive that excludes the root directory but includes all its files and subdirectories, use the asterisk (`*`) wildcard. For example: `-Path C:\Reference\*`
+> - To create an archive that only includes files in the root directory (excluding subdirectories), use the star-dot-star (`*.*`) wildcard. For example: `-Path C:\Reference\*.*`
+
+```yaml
+Type: String[]
+Parameter Sets: Path
+Aliases:
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: True
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe strings containing paths to files or directories. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet.
+
+## OUTPUTS
+
+### None
+
+By default, this cmdlet produces no output.
+
+### System.IO.FileInfo
+
+When the `-PassThru` switch is used, this cmdlet outputs a `FileInfo` object representing the created tar archive.
+
+## NOTES
+
+This cmdlet is designed to provide a PowerShell-native way to create tar archives with flexible compression options. It integrates seamlessly with other PowerShell cmdlets for file manipulation and filtering.
+
+## RELATED LINKS
+
+[__Compress-ZipArchive__](https://github.com/santisq/PSCompression)
+
+[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib)
+
+[__SharpCompress__](https://github.com/adamhathcock/sharpcompress)
+
+[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
diff --git a/docs/en-US/Compress-ZipArchive.md b/docs/en-US/Compress-ZipArchive.md
index 02dcc62..85dcd91 100644
--- a/docs/en-US/Compress-ZipArchive.md
+++ b/docs/en-US/Compress-ZipArchive.md
@@ -17,8 +17,8 @@ The `Compress-ZipArchive` cmdlet creates a compressed, or zipped, archive file f
```powershell
Compress-ZipArchive
- -Path
- -Destination
+ [-Path]
+ [-Destination]
[-CompressionLevel ]
[-Update]
[-Force]
@@ -32,7 +32,7 @@ Compress-ZipArchive
```powershell
Compress-ZipArchive
-LiteralPath
- -Destination
+ [-Destination]
[-CompressionLevel ]
[-Update]
[-Force]
@@ -126,16 +126,14 @@ exclusion patterns are tested against the items `.FullName` property.
### -Path
-Specifies the path or paths to the files that you want to add to the archive zipped file. To specify multiple paths, and include files in multiple locations, use commas to separate the paths.
-
-This parameter accepts wildcard characters. Wildcard characters allow you to add all files in a directory to your archive file.
+Specifies the path or paths to the files or directories to include in the archive file. To specify multiple paths and include files from multiple locations, use commas to separate the paths. This parameter accepts wildcard characters, allowing you to include all files in a directory or match specific patterns.
> [!TIP]
> Using wildcards with a root directory affects the archive's contents:
>
-> - To create an archive that includes the root directory, and all its files and subdirectories, specify the root directory in the Path without wildcards. For example: `-Path C:\Reference`
-> - To create an archive that excludes the root directory, but zips all its files and subdirectories, use the asterisk (`*`) wildcard. For example: `-Path C:\Reference\*`
-> - To create an archive that only zips the files in the root directory, use the star-dot-star (`*.*`) wildcard. Subdirectories of the root aren't included in the archive. For example: `-Path C:\Reference\*.*`
+> - To create an archive that includes the root directory and all its files and subdirectories, specify the root directory without wildcards. For example: `-Path C:\Reference`
+> - To create an archive that excludes the root directory but includes all its files and subdirectories, use the asterisk (`*`) wildcard. For example: `-Path C:\Reference\*`
+> - To create an archive that only includes files in the root directory (excluding subdirectories), use the star-dot-star (`*.*`) wildcard. For example: `-Path C:\Reference\*.*`
```yaml
Type: String[]
@@ -151,9 +149,7 @@ Accept wildcard characters: True
### -LiteralPath
-Specifies the path or paths to the files that you want to add to the archive zipped file.
-Unlike the Path `-Parameter`, the value of `-LiteralPath` is used exactly as it's typed.
-No characters are interpreted as wildcards
+Specifies the exact path or paths to the files or directories to include in the archive file. Unlike the `-Path` parameter, the value of `-LiteralPath` is used exactly as typed, with no wildcard character interpretation.
```yaml
Type: String[]
@@ -172,7 +168,7 @@ Accept wildcard characters: False
This parameter is required and specifies the path to the archive output file. The destination should include the name of the zipped file, and either the absolute or relative path to the zipped file.
> [!NOTE]
-> If the file name does not have an extension, the cmdlet appends the `.zip` file name extension.
+> If the file name lacks an extension, the cmdlet appends the `.zip` file name extension.
```yaml
Type: String
@@ -205,9 +201,7 @@ Accept wildcard characters: False
### -Exclude
-Specifies an array of one or more string patterns to be matched as the cmdlet gets child items.
-Any matching item is excluded from the created zip archive.
-Wildcard characters are accepted.
+Specifies an array of string patterns to exclude files or directories from the archive. Matching items are excluded based on their `.FullName` property. Wildcard characters are supported.
> [!NOTE]
> Patterns are tested against the object's `.FullName` property.
@@ -245,7 +239,7 @@ Accept wildcard characters: False
### -Force
-Overwrites the destination archive if exists otherwise it creates a new one. All Zip entries are lost.
+Overwrites the destination archive if exists otherwise it creates a new one. All existing entries are lost.
> [!NOTE]
> If `-Force` and `-Update` are used together this cmdlet will add or update entries.
@@ -284,9 +278,9 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### String
+### System.String[]
-You can pipe a string that contains a path to one or more files. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet.
+You can pipe strings containing paths to files or directories. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet.
## OUTPUTS
@@ -294,10 +288,18 @@ You can pipe a string that contains a path to one or more files. Output from `Ge
By default, this cmdlet produces no output.
-### FileInfo
+### System.IO.FileInfo
When the `-PassThru` switch is used this cmdlet outputs the `FileInfo` instance representing the compressed file.
## NOTES
This cmdlet was initially posted to address [this Stack Overflow question](https://stackoverflow.com/a/72611161/15339544). [Another question](https://stackoverflow.com/q/74129754/15339544) in the same site pointed out another limitation with the native cmdlet, it can't compress if another process has a handle on a file. To overcome this issue, and also to emulate explorer's behavior when compressing files used by another process, the cmdlet defaults to __[`FileShare 'ReadWrite, Delete'`](https://learn.microsoft.com/en-us/dotnet/api/system.io.fileshare?view=net-6.0)__ when opening a [`FileStream`](https://learn.microsoft.com/en-us/dotnet/api/system.io.file.open?view=net-7.0).
+
+## RELATED LINKS
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
+
+[__ZipArchive Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchive)
+
+[__ZipArchiveEntry Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.ziparchiveentry)
diff --git a/docs/en-US/ConvertFrom-BrotliString.md b/docs/en-US/ConvertFrom-BrotliString.md
new file mode 100644
index 0000000..189034b
--- /dev/null
+++ b/docs/en-US/ConvertFrom-BrotliString.md
@@ -0,0 +1,139 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# ConvertFrom-BrotliString
+
+## SYNOPSIS
+
+Expands Brotli Base64 compressed input strings.
+
+## SYNTAX
+
+```powershell
+ConvertFrom-BrotliString
+ [-InputObject]
+ [-Encoding ]
+ [-Raw]
+ []
+```
+
+## DESCRIPTION
+
+The `ConvertFrom-BrotliString` cmdlet expands Base64 encoded Brotli compressed strings using the `BrotliStream` class from the `BrotliSharpLib` library. This cmdlet is the counterpart of [`ConvertTo-BrotliString`](./ConvertTo-BrotliString.md).
+
+## EXAMPLES
+
+### Example 1: Expanding a Brotli compressed string
+
+```powershell
+PS ..\pwsh> ConvertFrom-BrotliString CwiAaGVsbG8NCndvcmxkDQohDQoD
+
+hello
+world
+!
+```
+
+This example expands a Brotli Base64 encoded string back to its original strings.
+
+### Example 2: Demonstrates how `-Raw` works
+
+```powershell
+PS ..\pwsh> $strings = 'hello', 'world', '!'
+
+# New lines are preserved when the cmdlet receives an array of strings.
+PS ..\pwsh> $strings | ConvertTo-BrotliString | ConvertFrom-BrotliString
+
+hello
+world
+!
+
+# When using the `-Raw` switch, all strings are returned as a single string
+PS ..\pwsh> $strings | ConvertTo-BrotliString -NoNewLine | ConvertFrom-BrotliString -Raw
+
+helloworld!
+```
+
+This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved.
+
+## PARAMETERS
+
+### -Encoding
+
+Determines the character encoding used when expanding the input strings.
+
+> [!NOTE]
+> The default encoding is `utf8NoBOM`.
+
+```yaml
+Type: Encoding
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: Utf8
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+Specifies the input string or strings to expand.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Raw
+
+Outputs the expanded string as a single string with newlines preserved. By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe Brotli Base64 strings to this cmdlet.
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertTo-BrotliString__](https://github.com/santisq/PSCompression/)
+
+[__BrotliSharpLib__](https://github.com/master131/BrotliSharpLib)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
diff --git a/docs/en-US/ConvertFrom-DeflateString.md b/docs/en-US/ConvertFrom-DeflateString.md
new file mode 100644
index 0000000..46e4a95
--- /dev/null
+++ b/docs/en-US/ConvertFrom-DeflateString.md
@@ -0,0 +1,139 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# ConvertFrom-DeflateString
+
+## SYNOPSIS
+
+Expands Deflate Base64 compressed input strings.
+
+## SYNTAX
+
+```powershell
+ConvertFrom-DeflateString
+ [-InputObject]
+ [-Encoding ]
+ [-Raw]
+ []
+```
+
+## DESCRIPTION
+
+The `ConvertFrom-DeflateString` cmdlet expands Base64 encoded Deflate compressed strings using the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). This cmdlet is the counterpart of [`ConvertTo-DeflateString`](./ConvertTo-DeflateString.md).
+
+## EXAMPLES
+
+### Example 1: Expanding a Deflate compressed string
+
+```powershell
+PS ..\pwsh> ConvertFrom-DeflateString ykjNycnn5SrPL8pJ4eVS5OUCAAAA//8DAA==
+
+hello
+world
+!
+```
+
+This example expands a Deflate Base64 encoded string back to its original strings.
+
+### Example 2: Demonstrates how `-Raw` works
+
+```powershell
+PS ..\pwsh> $strings = 'hello', 'world', '!'
+
+# New lines are preserved when the cmdlet receives an array of strings.
+PS ..\pwsh> $strings | ConvertTo-DeflateString | ConvertFrom-DeflateString
+
+hello
+world
+!
+
+# When using the `-Raw` switch, all strings are returned as a single string
+PS ..\pwsh> $strings | ConvertTo-DeflateString -NoNewLine | ConvertFrom-DeflateString -Raw
+
+helloworld!
+```
+
+This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved.
+
+## PARAMETERS
+
+### -Encoding
+
+Determines the character encoding used when expanding the input strings.
+
+> [!NOTE]
+> The default encoding is `utf8NoBOM`.
+
+```yaml
+Type: Encoding
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: Utf8
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+Specifies the input string or strings to expand.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Raw
+
+Outputs the expanded string as a single string with newlines preserved. By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe Deflate Base64 strings to this cmdlet.
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertTo-DeflateString__](https://github.com/santisq/PSCompression)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0)
+
+[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream)
diff --git a/docs/en-US/ConvertFrom-GzipString.md b/docs/en-US/ConvertFrom-GzipString.md
index 3a1a0a3..45f192d 100644
--- a/docs/en-US/ConvertFrom-GzipString.md
+++ b/docs/en-US/ConvertFrom-GzipString.md
@@ -15,7 +15,7 @@ Expands Gzip Base64 compressed input strings.
```powershell
ConvertFrom-GzipString
- -InputObject
+ [-InputObject]
[-Encoding ]
[-Raw]
[]
@@ -55,6 +55,8 @@ PS ..\pwsh> $strings | ConvertTo-GzipString -NoNewLine | ConvertFrom-GzipString
helloworld!
```
+This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved.
+
## PARAMETERS
### -Encoding
@@ -115,12 +117,22 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### String
+### System.String
You can pipe Gzip Base64 strings to this cmdlet.
## OUTPUTS
-### String
+### System.String
By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertTo-GzipString__](https://github.com/santisq/PSCompression)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
+
+[__GzipStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream)
diff --git a/docs/en-US/ConvertFrom-ZLibString.md b/docs/en-US/ConvertFrom-ZLibString.md
new file mode 100644
index 0000000..6231024
--- /dev/null
+++ b/docs/en-US/ConvertFrom-ZLibString.md
@@ -0,0 +1,141 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# ConvertFrom-ZLibString
+
+## SYNOPSIS
+
+Expands ZLib Base64 compressed input strings.
+
+## SYNTAX
+
+```powershell
+ConvertFrom-ZLibString
+ [-InputObject]
+ [-Encoding ]
+ [-Raw]
+ []
+```
+
+## DESCRIPTION
+
+The `ConvertFrom-ZLibString` cmdlet expands Base64 encoded ZLib compressed strings using a custom Zlib implementation built on the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). For the implementation details, see the PSCompression source code. This cmdlet is the counterpart of [`ConvertTo-ZLibString`](./ConvertTo-ZLibString.md).
+
+## EXAMPLES
+
+### Example 1: Expanding a ZLib compressed string
+
+```powershell
+PS ..\pwsh> ConvertFrom-ZLibString eJzKSM3JyeflKs8vyknh5VLk5QIAAAD//wMAMosEow==
+
+hello
+world
+!
+```
+
+This example expands a ZLib Base64 encoded string back to its original strings.
+
+### Example 2: Demonstrates how `-Raw` works
+
+```powershell
+PS ..\pwsh> $strings = 'hello', 'world', '!'
+
+# New lines are preserved when the cmdlet receives an array of strings.
+PS ..\pwsh> $strings | ConvertTo-ZLibString | ConvertFrom-ZLibString
+
+hello
+world
+!
+
+# When using the `-Raw` switch, all strings are returned as a single string
+PS ..\pwsh> $strings | ConvertTo-ZLibString -NoNewLine | ConvertFrom-ZLibString -Raw
+
+helloworld!
+```
+
+This example shows how the `-Raw` switch concatenates the expanded strings into a single string with newlines preserved.
+
+## PARAMETERS
+
+### -Encoding
+
+Determines the character encoding used when expanding the input strings.
+
+> [!NOTE]
+> The default encoding is `utf8NoBOM`.
+
+```yaml
+Type: Encoding
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: Utf8
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+Specifies the input string or strings to expand.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Raw
+
+Outputs the expanded string as a single string with newlines preserved. By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe ZLib Base64 strings to this cmdlet.
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet streams strings. When the `-Raw` switch is used, it returns a single multi-line string.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertTo-ZLibString__](https://github.com/santisq/PSCompression)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
+
+[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream)
+
+[__ZlibStream Class__](https://github.com/santisq/PSCompression/blob/main/src/PSCompression/ZlibStream.cs)
diff --git a/docs/en-US/ConvertTo-BrotliString.md b/docs/en-US/ConvertTo-BrotliString.md
new file mode 100644
index 0000000..cf5991a
--- /dev/null
+++ b/docs/en-US/ConvertTo-BrotliString.md
@@ -0,0 +1,208 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# ConvertTo-BrotliString
+
+## SYNOPSIS
+
+Creates a Brotli Base64 compressed string from a specified input string or strings.
+
+## SYNTAX
+
+```powershell
+ConvertTo-BrotliString
+ [-InputObject]
+ [-Encoding ]
+ [-CompressionLevel ]
+ [-AsByteStream]
+ [-NoNewLine]
+ []
+```
+
+## DESCRIPTION
+
+The `ConvertTo-BrotliString` cmdlet compresses input strings into Brotli Base64 encoded strings or raw bytes using the `BrotliStream` class from the `BrotliSharpLib` library. For expansion of Base64 Brotli strings, see [`ConvertFrom-BrotliString`](./ConvertFrom-BrotliString.md).
+
+## EXAMPLES
+
+### Example 1: Compress strings to Brotli compressed Base64 encoded string
+
+```powershell
+PS ..\pwsh> $strings = 'hello', 'world', '!'
+PS ..\pwsh> ConvertTo-BrotliString $strings
+
+CwiAaGVsbG8NCndvcmxkDQohDQoD
+
+# Or using pipeline input
+PS ..\pwsh> $strings | ConvertTo-BrotliString
+
+CwiAaGVsbG8NCndvcmxkDQohDQoD
+```
+
+This example demonstrates compressing an array of strings into a single Brotli Base64 encoded string using either positional binding or pipeline input.
+
+### Example 2: Create a Brotli compressed file from a string
+
+```powershell
+PS ..\pwsh> 'hello world!' | ConvertTo-BrotliString -AsByteStream | Set-Content -FilePath .\helloworld.br -AsByteStream
+
+# To read the file back you can use `ConvertFrom-BrotliString` following these steps:
+PS ..\pwsh> $path = Convert-Path .\helloworld.br
+PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-BrotliString
+
+hello world!
+```
+
+Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated.
+
+> [!NOTE]
+> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file.
+
+### Example 3: Compress strings using a specific Encoding
+
+```powershell
+PS ..\pwsh> 'ñ' | ConvertTo-BrotliString -Encoding ansi | ConvertFrom-BrotliString
+�
+
+PS ..\pwsh> 'ñ' | ConvertTo-BrotliString -Encoding utf8BOM | ConvertFrom-BrotliString
+ñ
+```
+
+This example shows how different encodings affect the compression and decompression of special characters. The default encoding is `utf8NoBOM`.
+
+### Example 4: Compressing multiple files into one Brotli Base64 string
+
+```powershell
+# Check the total length of the files
+PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb
+87.216796875
+
+# Check the total length after compression
+PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-BrotliString).Length / 1kb
+35.123456789
+```
+
+This example demonstrates compressing the contents of multiple text files into a single Brotli Base64 string and compares the total length before and after compression.
+
+## PARAMETERS
+
+### -AsByteStream
+
+Outputs the compressed byte array to the Success Stream.
+
+> [!NOTE]
+> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+).
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases: Raw
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -CompressionLevel
+
+Specifies the compression level for the Brotli algorithm, balancing speed and compression size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details.
+
+```yaml
+Type: CompressionLevel
+Parameter Sets: (All)
+Aliases:
+Accepted values: Optimal, Fastest, NoCompression, SmallestSize
+
+Required: False
+Position: Named
+Default value: Optimal
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Encoding
+
+Determines the character encoding used when compressing the input strings.
+
+> [!NOTE]
+> The default encoding is `utf8NoBOM`.
+
+```yaml
+Type: Encoding
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: Utf8
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+Specifies the input string or strings to compress.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -NoNewLine
+
+The encoded string representation of the input objects is concatenated to form the output. No newline character is added after each input string when this switch is used.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe strings to this cmdlet.
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet outputs a single Base64 encoded string.
+
+### System.Byte[]
+
+When the `-AsByteStream` switch is used, this cmdlet outputs a byte array down the pipeline.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertFrom-BrotliString__](https://github.com/santisq/PSCompression)
+
+[__BrotliSharpLib__](https://github.com/master131/BrotliSharpLib)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0)
diff --git a/docs/en-US/ConvertTo-DeflateString.md b/docs/en-US/ConvertTo-DeflateString.md
new file mode 100644
index 0000000..3d962ec
--- /dev/null
+++ b/docs/en-US/ConvertTo-DeflateString.md
@@ -0,0 +1,208 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# ConvertTo-DeflateString
+
+## SYNOPSIS
+
+Creates a Deflate Base64 compressed string from a specified input string or strings.
+
+## SYNTAX
+
+```powershell
+ConvertTo-DeflateString
+ [-InputObject]
+ [-Encoding ]
+ [-CompressionLevel ]
+ [-AsByteStream]
+ [-NoNewLine]
+ []
+```
+
+## DESCRIPTION
+
+The `ConvertTo-DeflateString` cmdlet compresses input strings into Deflate Base64 encoded strings or raw bytes using the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). For expansion of Base64 Deflate strings, see [`ConvertFrom-DeflateString`](./ConvertFrom-DeflateString.md).
+
+## EXAMPLES
+
+### Example 1: Compress strings to Deflate compressed Base64 encoded string
+
+```powershell
+PS ..\pwsh> $strings = 'hello', 'world', '!'
+PS ..\pwsh> ConvertTo-DeflateString $strings
+
+ykjNycnn5SrPL8pJ4eVS5OUCAAAA//8DAA==
+
+# Or using pipeline input
+PS ..\pwsh> $strings | ConvertTo-DeflateString
+
+ykjNycnn5SrPL8pJ4eVS5OUCAAAA//8DAA==
+```
+
+This example demonstrates compressing an array of strings into a single Deflate Base64 encoded string using either positional binding or pipeline input.
+
+### Example 2: Create a Deflate compressed file from a string
+
+```powershell
+PS ..\pwsh> 'hello world!' | ConvertTo-DeflateString -AsByteStream | Set-Content -FilePath .\helloworld.deflate -AsByteStream
+
+# To read the file back you can use `ConvertFrom-BrotliString` following these steps:
+PS ..\pwsh> $path = Convert-Path .\helloworld.deflate
+PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-DeflateString
+
+hello world!
+```
+
+Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated.
+
+> [!NOTE]
+> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file.
+
+### Example 3: Compress strings using a specific Encoding
+
+```powershell
+PS ..\pwsh> 'ñ' | ConvertTo-DeflateString -Encoding ansi | ConvertFrom-DeflateString
+�
+
+PS ..\pwsh> 'ñ' | ConvertTo-DeflateString -Encoding utf8BOM | ConvertFrom-DeflateString
+ñ
+```
+
+This example shows how different encodings affect the compression and decompression of special characters. The default encoding is `utf8NoBOM`.
+
+### Example 4: Compressing multiple files into one Deflate Base64 string
+
+```powershell
+# Check the total length of the files
+PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb
+87.216796875
+
+# Check the total length after compression
+PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-DeflateString).Length / 1kb
+35.123456789
+```
+
+This example demonstrates compressing the contents of multiple text files into a single Deflate Base64 string and compares the total length before and after compression.
+
+## PARAMETERS
+
+### -AsByteStream
+
+Outputs the compressed byte array to the Success Stream.
+
+> [!NOTE]
+> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+).
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases: Raw
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -CompressionLevel
+
+Specifies the compression level for the Deflate algorithm, balancing speed and compression size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details.
+
+```yaml
+Type: CompressionLevel
+Parameter Sets: (All)
+Aliases:
+Accepted values: Optimal, Fastest, NoCompression, SmallestSize
+
+Required: False
+Position: Named
+Default value: Optimal
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Encoding
+
+Determines the character encoding used when compressing the input strings.
+
+> [!NOTE]
+> The default encoding is `utf8NoBOM`.
+
+```yaml
+Type: Encoding
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: Utf8
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+Specifies the input string or strings to compress.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -NoNewLine
+
+The encoded string representation of the input objects is concatenated to form the output. No newline character is added after each input string when this switch is used.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe strings to this cmdlet.
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet outputs a single Base64 encoded string.
+
+### System.Byte[]
+
+When the `-AsByteStream` switch is used, this cmdlet outputs a byte array down the pipeline.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertFrom-DeflateString__](https://github.com/santisq/PSCompression)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0)
+
+[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream)
diff --git a/docs/en-US/ConvertTo-GzipString.md b/docs/en-US/ConvertTo-GzipString.md
index aabea11..6f52368 100644
--- a/docs/en-US/ConvertTo-GzipString.md
+++ b/docs/en-US/ConvertTo-GzipString.md
@@ -15,7 +15,7 @@ Creates a Gzip Base64 compressed string from a specified input string or strings
```powershell
ConvertTo-GzipString
- -InputObject
+ [-InputObject]
[-Encoding ]
[-CompressionLevel ]
[-AsByteStream]
@@ -45,14 +45,24 @@ PS ..\pwsh> $strings | ConvertTo-GzipString
H4sIAAAAAAAEAMtIzcnJ5+Uqzy/KSeHlUuTlAgBLr/K2EQAAAA==
```
+This example demonstrates compressing an array of strings into a single Brotli Base64 encoded string using either positional binding or pipeline input.
+
### Example 2: Create a Gzip compressed file from a string
```powershell
-PS ..\pwsh> 'hello world!' | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -DestinationPath .\files\file.gz
+PS ..\pwsh> 'hello world!' | ConvertTo-GzipString -AsByteStream | Set-Content -FilePath .\helloworld.gz -AsByteStream
+
+# To read the file back you can use `ConvertFrom-BrotliString` following these steps:
+PS ..\pwsh> $path = Convert-Path .\helloworld.gz
+PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-GzipString
+
+hello world!
```
-Demonstrates how `-AsByteStream` works on `ConvertTo-GzipString`, the cmdlet outputs a byte array that is received by `Compress-GzipArchive` and stored in a file. __Note that the byte array is not enumerated__.
+Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated.
+
+> [!NOTE]
+> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file.
### Example 3: Compress strings using a specific Encoding
@@ -69,19 +79,17 @@ The default Encoding is `utf8NoBom`.
### Example 4: Compressing multiple files into one Gzip Base64 string
```powershell
-PS ..\pwsh> 0..10 | ForEach-Object {
- Invoke-RestMethod loripsum.net/api/10/long/plaintext -OutFile .\files\lorem$_.txt
-}
-
-# Check the total Length of the downloaded files
-PS ..\pwsh> (Get-Content .\files\lorem*.txt | Measure-Object Length -Sum).Sum / 1kb
+# Check the total length of the files
+PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb
87.216796875
-# Check the total Length after compression
-PS ..\pwsh> (Get-Content .\files\lorem*.txt | ConvertTo-GzipString).Length / 1kb
-36.94921875
+# Check the total length after compression
+PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-GzipString).Length / 1kb
+35.123456789
```
+This example demonstrates compressing the contents of multiple text files into a single Gzip Base64 string and compares the total length before and after compression.
+
## PARAMETERS
### -AsByteStream
@@ -89,7 +97,7 @@ PS ..\pwsh> (Get-Content .\files\lorem*.txt | ConvertTo-GzipString).Length / 1kb
Outputs the compressed byte array to the Success Stream.
> [!NOTE]
-> This parameter is meant to be used in combination with [`Compress-GzipArchive`](./Compress-GzipArchive.md).
+> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+).
```yaml
Type: SwitchParameter
@@ -192,3 +200,13 @@ By default, this cmdlet outputs a single string.
### Byte[]
When the `-AsByteStream` switch is used this cmdlet outputs a byte array down the pipeline.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertFrom-GzipString__](https://github.com/santisq/PSCompression)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
+
+[__GzipStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream)
diff --git a/docs/en-US/ConvertTo-ZLibString.md b/docs/en-US/ConvertTo-ZLibString.md
new file mode 100644
index 0000000..0ad57e9
--- /dev/null
+++ b/docs/en-US/ConvertTo-ZLibString.md
@@ -0,0 +1,210 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# ConvertTo-ZLibString
+
+## SYNOPSIS
+
+Creates a ZLib Base64 compressed string from a specified input string or strings.
+
+## SYNTAX
+
+```powershell
+ConvertTo-ZLibString
+ [-InputObject]
+ [-Encoding ]
+ [-CompressionLevel ]
+ [-AsByteStream]
+ [-NoNewLine]
+ []
+```
+
+## DESCRIPTION
+
+The `ConvertTo-ZLibString` cmdlet compresses input strings into ZLib Base64 encoded strings or raw bytes using a custom Zlib implementation built on the [`DeflateStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream). For the implementation details, see the PSCompression source code. For expansion of Base64 ZLib strings, see [`ConvertFrom-ZLibString`](./ConvertFrom-ZLibString.md).
+
+## EXAMPLES
+
+### Example 1: Compress strings to ZLib compressed Base64 encoded string
+
+```powershell
+PS ..\pwsh> $strings = 'hello', 'world', '!'
+PS ..\pwsh> ConvertTo-ZLibString $strings
+
+eJzKSM3JyeflKs8vyknh5VLk5QIAAAD//wMAMosEow==
+
+# Or using pipeline input
+PS ..\pwsh> $strings | ConvertTo-ZLibString
+
+eJzKSM3JyeflKs8vyknh5VLk5QIAAAD//wMAMosEow==
+```
+
+This example demonstrates compressing an array of strings into a single ZLib Base64 encoded string using either positional binding or pipeline input.
+
+### Example 2: Create a ZLib compressed file from a string
+
+```powershell
+PS ..\pwsh> 'hello world!' | ConvertTo-ZLibString -AsByteStream | Set-Content -FilePath .\helloworld.zlib -AsByteStream
+
+# To read the file back you can use `ConvertFrom-BrotliString` following these steps:
+PS ..\pwsh> $path = Convert-Path .\helloworld.zlib
+PS ..\pwsh> [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($path)) | ConvertFrom-ZLibString
+
+hello world!
+```
+
+Demonstrates how `-AsByteStream` outputs a byte array that can be saved to a file using `Set-Content` or `Out-File`. Note that the byte array is not enumerated.
+
+> [!NOTE]
+> The example uses `-AsByteStream` with `Set-Content`, which is available in PowerShell 7+. In Windows PowerShell 5.1, use `-Encoding Byte` with `Set-Content` or `Out-File` to write the byte array to a file.
+
+### Example 3: Compress strings using a specific Encoding
+
+```powershell
+PS ..\pwsh> 'ñ' | ConvertTo-ZLibString -Encoding ansi | ConvertFrom-ZLibString
+�
+
+PS ..\pwsh> 'ñ' | ConvertTo-ZLibString -Encoding utf8BOM | ConvertFrom-ZLibString
+ñ
+```
+
+This example shows how different encodings affect the compression and decompression of special characters. The default encoding is `utf8NoBOM`.
+
+### Example 4: Compressing multiple files into one ZLib Base64 string
+
+```powershell
+# Check the total length of the files
+PS ..\pwsh> (Get-Content myLogs\*.txt | Measure-Object Length -Sum).Sum / 1kb
+87.216796875
+
+# Check the total length after compression
+PS ..\pwsh> (Get-Content myLogs\*.txt | ConvertTo-GzipString).Length / 1kb
+35.123456789
+```
+
+This example demonstrates compressing the contents of multiple text files into a single ZLib Base64 string and compares the total length before and after compression.
+
+## PARAMETERS
+
+### -AsByteStream
+
+Outputs the compressed byte array to the Success Stream.
+
+> [!NOTE]
+> This parameter is intended for use with cmdlets that accept byte arrays, such as `Out-File` and `Set-Content` with `-Encoding Byte` (Windows PowerShell 5.1) or `-AsByteStream` (PowerShell 7+).
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases: Raw
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -CompressionLevel
+
+Specifies the compression level for the ZLib algorithm, balancing speed and compression size. See [`CompressionLevel` Enum](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.compressionlevel) for details.
+
+```yaml
+Type: CompressionLevel
+Parameter Sets: (All)
+Aliases:
+Accepted values: Optimal, Fastest, NoCompression, SmallestSize
+
+Required: False
+Position: Named
+Default value: Optimal
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Encoding
+
+Determines the character encoding used when compressing the input strings.
+
+> [!NOTE]
+> The default encoding is `utf8NoBOM`.
+
+```yaml
+Type: Encoding
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: Utf8
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+Specifies the input string or strings to compress.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -NoNewLine
+
+The encoded string representation of the input objects is concatenated to form the output. No newline character is added after each input string when this switch is used.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: False
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe strings to this cmdlet.
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet outputs a single Base64 encoded string.
+
+### System.Byte[]
+
+When the `-AsByteStream` switch is used, this cmdlet outputs a byte array down the pipeline.
+
+## NOTES
+
+## RELATED LINKS
+
+[__ConvertFrom-ZLibString__](https://github.com/santisq/PSCompression)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0)
+
+[__DeflateStream Class__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.deflatestream)
+
+[__ZlibStream Class__](https://github.com/santisq/PSCompression/blob/main/src/PSCompression/ZlibStream.cs)
diff --git a/docs/en-US/Expand-GzipArchive.md b/docs/en-US/Expand-GzipArchive.md
deleted file mode 100644
index af6efac..0000000
--- a/docs/en-US/Expand-GzipArchive.md
+++ /dev/null
@@ -1,277 +0,0 @@
----
-external help file: PSCompression-help.xml
-Module Name: PSCompression
-online version: https://github.com/santisq/PSCompression
-schema: 2.0.0
----
-
-# Expand-GzipArchive
-
-## SYNOPSIS
-
-Expands a Gzip compressed file from a specified File Path or Paths.
-
-## SYNTAX
-
-### Path
-
-```powershell
-Expand-GzipArchive
- -Path
- [-Raw]
- []
-```
-
-### PathDestination
-
-```powershell
-Expand-GzipArchive
- -Path
- -Destination
- [-Encoding ]
- [-PassThru]
- [-Force]
- [-Update]
- []
-```
-
-### LiteralPath
-
-```powershell
-Expand-GzipArchive
- -LiteralPath
- [-Raw]
- []
-```
-
-### LiteralPathDestination
-
-```powershell
-Expand-GzipArchive
- -LiteralPath
- -Destination
- [-Encoding ]
- [-PassThru]
- [-Force]
- [-Update]
- []
-```
-
-## DESCRIPTION
-
-The `Expand-GzipArchive` cmdlet aims to expand Gzip compressed files to a destination path or to the success stream using the [`GzipStream` Class](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression.gzipstream). This cmdlet is the counterpart of [`Compress-GzipArchive`](Compress-GzipArchive.md).
-
-## EXAMPLES
-
-### Example 1: Expanding a Gzip archive to the success stream
-
-```powershell
-PS ..\pwsh> Expand-GzipArchive .\files\file.gz
-
-hello world!
-```
-
-Output goes to the Success Stream when `-Destination` is not used.
-
-### Example 2: Expanding a Gzip archive to a new file
-
-```powershell
-PS ..\pwsh> Expand-GzipArchive .\files\file.gz -Destination .\files\file.txt
-
-# Checking Length Difference
-PS ..\pwsh> Get-Item -Path .\files\file.gz, .\files\file.txt |
- Select-Object Name, Length
-
-Name Length
----- ------
-file.gz 3168
-file.txt 6857
-```
-
-### Example 3: Appending content to an existing file
-
-```powershell
-PS ..\pwsh> Expand-GzipArchive *.gz -Destination .\files\file.txt -Update
-```
-
-### Example 4: Expanding a Gzip archive overwritting an existing file
-
-```powershell
-PS ..\pwsh> Expand-GzipArchive *.gz -Destination .\files\file.txt -Force
-```
-
-## PARAMETERS
-
-### -Path
-
-Specifies the path or paths to the Gzip files to expand.
-To specify multiple paths, and include files in multiple locations, use commas to separate the paths.
-This Parameter accepts wildcard characters.
-Wildcard characters allow you to add all files in a directory to your archive file.
-
-```yaml
-Type: String[]
-Parameter Sets: PathDestination, Path
-Aliases:
-
-Required: True
-Position: 0
-Default value: None
-Accept pipeline input: True (ByValue)
-Accept wildcard characters: False
-```
-
-### -LiteralPath
-
-Specifies the path or paths to the Gzip files to expand.
-Unlike the `-Path` Parameter, the value of `-LiteralPath` is used exactly as it's typed.
-No characters are interpreted as wildcards
-
-```yaml
-Type: String[]
-Parameter Sets: LiteralPathDestination, LiteralPath
-Aliases: PSPath
-
-Required: True
-Position: Named
-Default value: None
-Accept pipeline input: True (ByPropertyName)
-Accept wildcard characters: False
-```
-
-### -Destination
-
-The destination path where to expand the Gzip file.
-The target folder is created if it does not exist.
-
-> [!NOTE]
-> This parameter is Optional, if not used, this cmdlet outputs to the Success Stream.
-
-```yaml
-Type: String
-Parameter Sets: PathDestination, LiteralPathDestination
-Aliases: DestinationPath
-
-Required: True
-Position: Named
-Default value: None
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Encoding
-
-Character encoding used when expanding the Gzip content. This parameter is only available when expanding to the Success Stream.
-
-> [!NOTE]
-> The default encoding is __`utf8NoBOM`__.
-
-```yaml
-Type: Encoding
-Parameter Sets: Path, LiteralPath
-Aliases:
-
-Required: False
-Position: Named
-Default value: utf8NoBOM
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Raw
-
-Outputs the expanded file as a single string with newlines preserved.
-By default, newline characters in the expanded string are used as delimiters to separate the input into an array of strings.
-
-> [!NOTE]
-> This parameter is only available when expanding to the Success Stream.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: Path, LiteralPath
-Aliases:
-
-Required: False
-Position: Named
-Default value: False
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -PassThru
-
-Outputs the object representing the expanded file.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: PathDestination, LiteralPathDestination
-Aliases:
-
-Required: False
-Position: Named
-Default value: False
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Force
-
-The destination file gets overwritten if exists, otherwise created when this switch is used.
-
-> [!NOTE]
-> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: PathDestination, LiteralPathDestination
-Aliases:
-
-Required: False
-Position: Named
-Default value: None
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### -Update
-
-Contents of the expanded file or files are appended to the destination path if exists, otherwise the destination is created.
-
-> [!NOTE]
-> If `-Force` and `-Update` are used together this cmdlet will append content to the destination file.
-
-```yaml
-Type: SwitchParameter
-Parameter Sets: PathDestination, LiteralPathDestination
-Aliases:
-
-Required: False
-Position: Named
-Default value: None
-Accept pipeline input: False
-Accept wildcard characters: False
-```
-
-### CommonParameters
-
-This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
-
-## INPUTS
-
-### String
-
-You can pipe paths to this cmdlet. Output from `Get-ChildItem` or `Get-Item` can be piped to this cmdlet.
-
-## OUTPUTS
-
-### String
-
-This cmdlet outputs an array of string to the success stream when `-Destination` is not used and a single multi-line string when used with the `-Raw` switch.
-
-### None
-
-This cmdlet produces no output when expanding to a file and `-PassThru` is not used.
-
-### FileInfo
-
-When the `-PassThru` switch is used this cmdlet outputs the `FileInfo` instance representing the expanded file.
diff --git a/docs/en-US/Expand-TarArchive.md b/docs/en-US/Expand-TarArchive.md
new file mode 100644
index 0000000..daafdd5
--- /dev/null
+++ b/docs/en-US/Expand-TarArchive.md
@@ -0,0 +1,256 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# Expand-TarArchive
+
+## SYNOPSIS
+
+Extracts files from a tar archive, optionally compressed with gzip, bzip2, Zstandard, or lzip.
+
+## SYNTAX
+
+### Path
+
+```powershell
+Expand-TarArchive
+ [-Path]
+ [[-Destination] ]
+ [-Algorithm ]
+ [-Force]
+ [-PassThru]
+ []
+```
+
+### LiteralPath
+
+```powershell
+Expand-TarArchive
+ -LiteralPath
+ [[-Destination] ]
+ [-Algorithm ]
+ [-Force]
+ [-PassThru]
+ []
+```
+
+## DESCRIPTION
+
+The `Expand-TarArchive` cmdlet extracts files and directories from a tar archive, with support for compressed tar formats using gzip (`.tar.gz`), bzip2 (`.tar.bz2`), Zstandard (`.tar.zst`), lzip (`.tar.lz`), or uncompressed tar (`.tar`). It uses libraries such as `SharpZipLib` for tar handling, `System.IO.Compression` for gzip, `SharpCompress` for lzip, and `ZstdSharp` for Zstandard. This cmdlet is the counterpart to [`Compress-TarArchive`](./Compress-TarArchive.md). By default, files are extracted to the current directory unless a `-Destination` is specified. Use `-Force` to overwrite existing files and `-PassThru` to output the extracted file and directory objects.
+
+> [!NOTE]
+> When the `-Algorithm` parameter is not specified, the cmdlet infers the compression algorithm from the file extension (e.g., `.tar.gz` for gzip, `.tar.zst` for Zstandard, `.tar` for uncompressed). See the `-Algorithm` parameter for supported extensions.
+
+## EXAMPLES
+
+### Example 1: Extract an uncompressed tar archive
+
+```powershell
+PS C:\> Expand-TarArchive -Path .\archive.tar
+
+PS C:\> Get-ChildItem
+ Directory: C:\
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+d---- 2025-06-23 7:00 PM folder1
+-a--- 2025-06-23 7:00 PM 1024 file1.txt
+-a--- 2025-06-23 7:00 PM 2048 file2.txt
+```
+
+Extracts the contents of `archive.tar` to the current directory, creating `folder1`, `file1.txt`, and `file2.txt`.
+
+### Example 2: Extract a gzip-compressed tar archive to a specific destination
+
+```powershell
+PS C:\> Expand-TarArchive -Path .\archive.tar.gz -Destination .\extracted
+
+PS C:\> Get-ChildItem .\extracted
+ Directory: C:\extracted
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+d---- 2025-06-23 7:00 PM folder1
+-a--- 2025-06-23 7:00 PM 1024 file1.txt
+-a--- 2025-06-23 7:00 PM 2048 file2.txt
+```
+
+Extracts `archive.tar.gz` to the `extracted` directory using the gzip algorithm, creating the directory if it doesn’t exist.
+
+### Example 3: Extract multiple Zstandard-compressed tar archives with PassThru
+
+```powershell
+PS C:\> Get-ChildItem *.tar.zst | Expand-TarArchive -PassThru
+
+ Directory: C:\
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+d---- 2025-06-23 7:00 PM folder1
+-a--- 2025-06-23 7:00 PM 1024 file1.txt
+-a--- 2025-06-23 7:00 PM 2048 file2.txt
+d---- 2025-06-23 7:00 PM folder2
+-a--- 2025-06-23 7:00 PM 4096 file3.txt
+```
+
+Extracts all `.tar.zst` files in the current directory and outputs the extracted files and directories to the pipeline.
+
+### Example 4: Overwrite existing files with Force
+
+```powershell
+PS C:\> Expand-TarArchive -Path .\archive.tar -Destination .\extracted -Force
+
+PS C:\> Get-ChildItem .\extracted
+ Directory: C:\extracted
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+-a--- 2025-06-23 7:00 PM 1024 file1.txt
+```
+
+Extracts `archive.tar` to the `extracted` directory, overwriting any existing `file1.txt` due to the `-Force` parameter.
+
+## PARAMETERS
+
+### -Algorithm
+
+Specifies the compression algorithm used for the tar archive. Accepted values and their corresponding file extensions are:
+
+- `gz`: Gzip compression (`.gz`, `.gzip`, `.tgz`).
+- `bz2`: Bzip2 compression (`.bz2`, `.bzip2`, `.tbz2`, `.tbz`)
+- `zst`: Zstandard compression (`.zst`).
+- `lz`: Lzip compression (`.lz`).
+- `none`: Uncompressed tar archive (`.tar`).
+
+> [!NOTE]
+> If not specified, the cmdlet infers the algorithm from the file extension. If the extension is unrecognized, it defaults to `none` (uncompressed tar).
+
+```yaml
+Type: Algorithm
+Parameter Sets: (All)
+Aliases:
+Accepted values: gz, bz2, zst, lz, none
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Destination
+
+Specifies the path to the directory where the archive contents are extracted. If not provided, the current directory is used. If the directory does not exist, it is created.
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 1
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Force
+
+Overwrites existing files in the destination directory without prompting. If not specified, the cmdlet skips files that already exist.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -LiteralPath
+
+Specifies the exact path(s) to the tar archive(s) to extract. Unlike `-Path`, `-LiteralPath` does not support wildcard characters and treats the path as a literal string. Use this parameter when the archive name contains special characters.
+
+```yaml
+Type: String[]
+Parameter Sets: LiteralPath
+Aliases: PSPath
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -PassThru
+
+Outputs `System.IO.FileInfo` and `System.IO.DirectoryInfo` objects representing the extracted files and directories to the pipeline. By default, no output is produced unless an error occurs.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Path
+
+Specifies the path(s) to the tar archive(s) to extract. Supports wildcard characters, allowing multiple archives to be processed. Paths can be provided via pipeline input.
+
+```yaml
+Type: String[]
+Parameter Sets: Path
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: True
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.String[]
+
+You can pipe paths to tar archives to this cmdlet via the `-Path` parameter or provide literal paths via the `-LiteralPath` parameter.
+
+## OUTPUTS
+
+### None
+
+By default, this cmdlet returns no output.
+
+### System.IO.FileSystemInfo
+
+When the `-PassThru` parameter is used, the cmdlet outputs objects representing the extracted files and directories.
+
+## NOTES
+
+## RELATED LINKS
+
+[__Compress-TarArchive__](https://github.com/santisq/PSCompression)
+
+[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib)
+
+[__SharpCompress__](https://github.com/adamhathcock/sharpcompress)
+
+[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0)
diff --git a/docs/en-US/Expand-TarEntry.md b/docs/en-US/Expand-TarEntry.md
new file mode 100644
index 0000000..f64d840
--- /dev/null
+++ b/docs/en-US/Expand-TarEntry.md
@@ -0,0 +1,197 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# Expand-TarEntry
+
+## SYNOPSIS
+
+Expands tar archive entries to a destination directory.
+
+## SYNTAX
+
+```powershell
+Expand-TarEntry
+ -InputObject
+ [[-Destination] ]
+ [-Force]
+ [-PassThru]
+ []
+```
+
+## DESCRIPTION
+
+The `Expand-TarEntry` cmdlet extracts tar entries output by the [`Get-TarEntry`](./Get-TarEntry.md) cmdlet to a destination directory. Expanded entries maintain their original folder structure based on their relative path within the tar archive. This cmdlet supports both uncompressed (`.tar`) and compressed tar archives (e.g., `.tar.gz`, `.tar.bz2`) processed by `Get-TarEntry`.
+
+## EXAMPLES
+
+### Example 1: Extract all `.txt` files from a tar archive to the current directory
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Include *.txt | Expand-TarEntry
+```
+
+Extracts all `.txt` files from `archive.tar` to the current directory, preserving their relative paths.
+
+### Example 2: Extract all `.txt` files from a tar archive to a specific directory
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar.gz -Include *.txt | Expand-TarEntry -Destination .\extracted
+```
+
+Extracts all `.txt` files from `archive.tar.gz` to the `extracted` directory, creating the directory if it doesn’t exist.
+
+### Example 3: Extract all entries excluding `.txt` files from a tar archive
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Exclude *.txt | Expand-TarEntry
+```
+
+Extracts all entries except `.txt` files from `archive.tar` to the current directory.
+
+### Example 4: Extract entries overwriting existing files
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Include *.txt | Expand-TarEntry -Force
+```
+
+Demonstrates how the `-Force` switch overwrites existing files in the destination directory.
+
+### Example 5: Extract entries and output the expanded items
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Exclude *.txt | Expand-TarEntry -PassThru
+
+ Directory: C:\
+
+Mode LastWriteTime Length Name
+---- ------------- ------ ----
+d---- 2025-06-23 7:00 PM folder1
+-a--- 2025-06-23 7:00 PM 2048 image.png
+```
+
+By default, this cmdlet produces no output. When `-PassThru` is used, it outputs `FileInfo` and `DirectoryInfo` objects representing the extracted entries.
+
+### Example 6: Extract a specific entry from a compressed tar archive
+
+```powershell
+PS C:\> $stream = Invoke-WebRequest https://example.com/archive.tar.gz
+PS C:\> $file = $stream | Get-TarEntry -Include readme.md -Algorithm gz | Expand-TarEntry -PassThru
+PS C:\> Get-Content $file.FullName
+
+# My Project
+This is the README file for my project.
+```
+
+Extracts the `readme.md` file from a gzip-compressed tar archive retrieved via a web request and displays its contents.
+
+> [!NOTE]
+> When `Get-TarEntry` processes a stream, it defaults to the `gz` (gzip) algorithm. Specify the `-Algorithm` parameter (e.g., `-Algorithm bz2` for bzip2) to match the compression type of the tar archive, or an error may occur if the stream is not gzip-compressed.
+
+## PARAMETERS
+
+### -Destination
+
+The destination directory where tar entries are extracted. If not specified, entries are extracted to their relative path in the current directory, creating any necessary subdirectories.
+
+```yaml
+Type: String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: 0
+Default value: $PWD
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Force
+
+Overwrites existing files in the destination directory when this switch is used.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -InputObject
+
+The tar entries to extract. These are instances of `TarEntryBase` (`TarEntryFile` or `TarEntryDirectory`) output by the [`Get-TarEntry`](./Get-TarEntry.md) cmdlet.
+
+> [!NOTE]
+> This parameter accepts pipeline input from `Get-TarEntry`. Binding by name is also supported.
+
+```yaml
+Type: TarEntryBase[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -PassThru
+
+Outputs `FileInfo` and `DirectoryInfo` objects representing the extracted entries when this switch is used. By default, the cmdlet produces no output.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### PSCompression.Abstractions.TarEntryBase[]
+
+You can pipe instances of `TarEntryFile` or `TarEntryDirectory` from [`Get-TarEntry`](./Get-TarEntry.md) to this cmdlet.
+
+## OUTPUTS
+
+### None
+
+By default, this cmdlet produces no output.
+
+### System.IO.FileSystemInfo
+
+When the `-PassThru` switch is used, the cmdlet outputs objects representing the extracted files and directories.
+
+## NOTES
+
+## RELATED LINKS
+
+[__Get-TarEntry__](https://github.com/santisq/PSCompression)
+
+[__Expand-TarArchive__](https://github.com/santisq/PSCompression)
+
+[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib)
+
+[__SharpCompress__](https://github.com/adamhathcock/sharpcompress)
+
+[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression)
diff --git a/docs/en-US/Expand-ZipEntry.md b/docs/en-US/Expand-ZipEntry.md
index 96c2300..3039aba 100644
--- a/docs/en-US/Expand-ZipEntry.md
+++ b/docs/en-US/Expand-ZipEntry.md
@@ -66,7 +66,7 @@ By default this cmdlet produces no output. When `-PassThru` is used, this cmdlet
```powershell
PS ..\pwsh> $package = Invoke-WebRequest https://www.powershellgallery.com/api/v2/package/PSCompression
-PS ..\pwsh> $file = $package | Get-ZipEntry -Include *.psd1 | Expand-ZipEntry -PassThru -Force
+PS ..\pwsh> $file = $package | Get-ZipEntry -Include *.psd1 | Expand-ZipEntry -PassThru
PS ..\pwsh> Get-Content $file.FullName -Raw | Invoke-Expression
Name Value
@@ -168,7 +168,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### ZipEntryBase
+### PSCompression.Abstractions.ZipEntryBase
You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets.
@@ -178,6 +178,6 @@ You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet.
By default, this cmdlet produces no output.
-### FileSystemInfo
+### System.IO.FileSystemInfo
The cmdlet outputs the `FileInfo` and `DirectoryInfo` instances of the extracted entries when `-PassThru` switch is used.
diff --git a/docs/en-US/Get-TarEntry.md b/docs/en-US/Get-TarEntry.md
new file mode 100644
index 0000000..4b9adb7
--- /dev/null
+++ b/docs/en-US/Get-TarEntry.md
@@ -0,0 +1,318 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# Get-TarEntry
+
+## SYNOPSIS
+
+Lists tar archive entries from a specified path or input stream.
+
+## SYNTAX
+
+### Path (Default)
+
+```powershell
+Get-TarEntry
+ [-Path]
+ [-Algorithm ]
+ [-Type ]
+ [-Include ]
+ [-Exclude ]
+ []
+```
+
+### Stream
+
+```powershell
+Get-TarEntry
+ [-InputStream]
+ [-Algorithm ]
+ [-Type ]
+ [-Include ]
+ [-Exclude ]
+ []
+```
+
+### LiteralPath
+
+```powershell
+Get-TarEntry
+ -LiteralPath
+ [-Algorithm ]
+ [-Type ]
+ [-Include ]
+ [-Exclude ]
+ []
+```
+
+## DESCRIPTION
+
+The `Get-TarEntry` cmdlet lists entries in tar archives, including both uncompressed (`.tar`) and compressed formats (e.g., `.tar.gz`, `.tar.bz2`, `.tar.zst`, `.tar.lz`). It supports input from file paths or streams and outputs `TarEntryFile` or `TarEntryDirectory` objects, which can be piped to cmdlets like [`Expand-TarEntry`](./Expand-TarEntry.md) and [`Get-TarEntryContent`](./Get-TarEntryContent.md). The cmdlet uses libraries such as `SharpZipLib` for tar handling, `System.IO.Compression` for gzip, `SharpCompress` for lzip, and `ZstdSharp` for Zstandard. Use `-Include` and `-Exclude` to filter entries by name and `-Type` to filter by entry type (file or directory).
+
+## EXAMPLES
+
+### Example 1: List entries for a specified tar archive
+
+```powershell
+PS ..\pwsh> Get-TarEntry .\archive.tar
+
+ Directory: /folder1/
+
+Type LastWriteTime Size Name
+---- ------------- ---- ----
+Directory 6/23/2025 11:08 PM folder1
+Archive 6/23/2025 11:08 PM 1.00 KB file1.txt
+Archive 6/23/2025 11:08 PM 2.00 KB file2.txt
+```
+
+Lists all entries in `archive.tar`, including directories and files.
+
+### Example 2: List entries from all gzip-compressed tar archives in the current directory
+
+```powershell
+PS ..\pwsh> Get-TarEntry *.tar.gz
+
+ Directory: /folder1/
+
+Type LastWriteTime Size Name
+---- ------------- ---- ----
+Directory 6/23/2025 11:08 PM folder1
+Archive 6/23/2025 11:08 PM 1.00 KB file1.txt
+Archive 6/23/2025 11:08 PM 2.00 KB file2.txt
+```
+
+> [!TIP]
+> The `-Path` parameter supports wildcards.
+
+### Example 3: List all file entries from a tar archive
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Type Archive
+
+ Directory: /folder1/
+
+Type LastWriteTime Size Name
+---- ------------- ---- ----
+Archive 6/23/2025 11:08 PM 1.00 KB file1.txt
+Archive 6/23/2025 11:08 PM 2.00 KB file2.txt
+```
+
+Filters entries to show only files using `-Type Archive`.
+
+### Example 4: Filter entries with Include and Exclude parameters
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tbz2 -Include folder1/* -Exclude *.txt
+
+ Directory: /folder1/
+
+Type LastWriteTime Size Name
+---- ------------- ---- ----
+Directory 2025-06-23 7:00 PM folder1
+Archive 2025-06-23 7:00 PM 3.00 KB image.png
+```
+
+Filters entries to include only those under `folder1/` but excludes `.txt` files.
+
+> [!NOTE]
+> If not specified, the cmdlet infers the compression algorithm from the file extension: `gz` for `.gz`, `.gzip`, `.tgz`; `bz2` for `.bz2`, `.bzip2`, `.tbz2`, `.tbz`; `zst` for `.zst`; `lz` for `.lz`; `none` for `.tar`. If the extension is unrecognized, it defaults to `none` (uncompressed tar).
+
+### Example 5: List entries from an input stream
+
+```powershell
+PS C:\> $stream = Invoke-WebRequest https://example.com/archive.tar.gz
+PS C:\> $stream | Get-TarEntry -Algorithm gz | Select-Object -First 3
+
+ Directory: /docs/
+
+Type LastWriteTime Size Name
+---- ------------- ---- ----
+Directory 2025-06-23 7:00 PM docs/
+Archive 2025-06-23 7:00 PM 1.50 KB readme.md
+Archive 2025-06-23 7:00 PM 2.50 KB license.txt
+```
+
+Lists the first three entries from a gzip-compressed tar archive stream, specifying `-Algorithm gz`.
+
+> [!NOTE]
+> When processing a stream, the cmdlet defaults to the `gz` (gzip) algorithm. Specify `-Algorithm` (e.g., `-Algorithm bz2` for bzip2) to match the compression type, or an error may occur if the stream is not gzip-compressed.
+
+## PARAMETERS
+
+### -Algorithm
+
+Specifies the compression algorithm used for the tar archive. Accepted values and their corresponding file extensions are:
+
+- `gz`: Gzip compression (`.gz`, `.gzip`, `.tgz`).
+- `bz2`: Bzip2 compression (`.bz2`, `.bzip2`, `.tbz2`, `.tbz`)
+- `zst`: Zstandard compression (`.zst`).
+- `lz`: Lzip compression (`.lz`).
+- `none`: Uncompressed tar archive (`.tar`).
+
+> [!NOTE]
+> If not specified, the cmdlet infers the algorithm from the file extension. If the extension is unrecognized, it defaults to `none` (uncompressed tar).
+
+```yaml
+Type: Algorithm
+Parameter Sets: (All)
+Aliases:
+Accepted values: gz, bz2, zst, lz, none
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Exclude
+
+Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are excluded from the output. Wildcard characters are supported.
+
+> [!NOTE]
+> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: True
+```
+
+### -Include
+
+Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are included in the output. Wildcard characters are supported.
+
+> [!NOTE]
+> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions.
+
+```yaml
+Type: String[]
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: True
+```
+
+### -InputStream
+
+Specifies an input stream containing a tar archive.
+
+> [!NOTE]
+>
+> - Output from `Invoke-WebRequest` is automatically bound to this parameter.
+> - The cmdlet defaults to the `gz` (gzip) algorithm for streams. Specify `-Algorithm` to match the compression type (e.g., `-Algorithm zst` for Zstandard) to avoid errors.
+
+```yaml
+Type: Stream
+Parameter Sets: Stream
+Aliases: RawContentStream
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByPropertyName, ByValue)
+Accept wildcard characters: False
+```
+
+### -LiteralPath
+
+Specifies one or more paths to tar archives. The value is used exactly as typed, with no wildcard character interpretation.
+
+```yaml
+Type: String[]
+Parameter Sets: LiteralPath
+Aliases: PSPath
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByPropertyName)
+Accept wildcard characters: False
+```
+
+### -Path
+
+Specifies one or more paths to tar archives. Wildcard characters are supported.
+
+```yaml
+Type: String[]
+Parameter Sets: Path
+Aliases:
+
+Required: True
+Position: 0
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: True
+```
+
+### -Type
+
+Filters entries by type: `Archive` for files or `Directory` for directories.
+
+```yaml
+Type: EntryType
+Parameter Sets: (All)
+Aliases:
+Accepted values: Directory, Archive
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### System.IO.Stream
+
+You can pipe a stream containing a tar archive to this cmdlet, such as output from `Invoke-WebRequest`.
+
+### System.String[]
+
+You can pipe strings containing paths to tar archives, such as output from `Get-ChildItem` or `Get-Item`.
+
+## OUTPUTS
+
+### PSCompression.TarEntryDirectory
+
+### PSCompression.TarEntryFile
+
+Outputs objects representing directories or files in the tar archive.
+
+## NOTES
+
+## RELATED LINKS
+
+[__Expand-TarEntry__](https://github.com/santisq/PSCompression)
+
+[__Expand-TarArchive__](https://github.com/santisq/PSCompression)
+
+[__Get-TarEntryContent__](https://github.com/santisq/PSCompression)
+
+[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib)
+
+[__SharpCompress__](https://github.com/adamhathcock/sharpcompress)
+
+[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp)
+
+[__System.IO.Compression__](https://learn.microsoft.com/en-us/dotnet/api/system.io.compression?view=net-6.0)
diff --git a/docs/en-US/Get-TarEntryContent.md b/docs/en-US/Get-TarEntryContent.md
new file mode 100644
index 0000000..70970e8
--- /dev/null
+++ b/docs/en-US/Get-TarEntryContent.md
@@ -0,0 +1,243 @@
+---
+external help file: PSCompression.dll-Help.xml
+Module Name: PSCompression
+online version: https://github.com/santisq/PSCompression
+schema: 2.0.0
+---
+
+# Get-TarEntryContent
+
+## SYNOPSIS
+
+Gets the content of a tar archive entry.
+
+## SYNTAX
+
+### Stream (Default)
+
+```powershell
+Get-TarEntryContent
+ -Entry
+ [-Encoding ]
+ [-Raw]
+ []
+```
+
+### Bytes
+
+```powershell
+Get-TarEntryContent
+ -Entry
+ [-Raw]
+ [-AsByteStream]
+ [-BufferSize ]
+ []
+```
+
+## DESCRIPTION
+
+The `Get-TarEntryContent` cmdlet retrieves the content of one or more `TarEntryFile` instances. This cmdlet is designed to be used with [`Get-TarEntry`](./Get-TarEntry.md) as the entry point.
+
+> [!TIP]
+> Entries output by `Get-TarEntry` can be piped to this cmdlet.
+
+## EXAMPLES
+
+### Example 1: Get the content of a tar archive entry
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Include folder1/file1.txt | Get-TarEntryContent
+
+Line 1 of file1.txt
+Line 2 of file1.txt
+```
+
+The `-Include` parameter from `Get-TarEntry` targets a specific entry by its relative path, and the output is piped to `Get-TarEntryContent`. By default, the cmdlet streams content line by line.
+
+### Example 2: Get raw content of a tar archive entry
+
+```powershell
+PS C:\> Get-TarEntry .\archive.tar -Include folder1/file1.txt | Get-TarEntryContent -Raw
+
+Line 1 of file1.txt
+Line 2 of file1.txt
+```
+
+The cmdlet outputs a single multi-line string when the `-Raw` switch is used instead of line-by-line streaming.
+
+### Example 3: Get the bytes of a tar archive entry as a stream
+
+```powershell
+PS C:\> $bytes = Get-TarEntry .\archive.tar -Include folder1/helloworld.txt | Get-TarEntryContent -AsByteStream
+PS C:\> $bytes
+104
+101
+108
+108
+111
+32
+119
+111
+114
+108
+100
+33
+13
+10
+
+PS C:\> [System.Text.Encoding]::UTF8.GetString($bytes)
+hello world!
+```
+
+The `-AsByteStream` switch is useful for reading non-text tar entries.
+
+### Example 4: Get contents of all `.md` files as byte arrays
+
+```powershell
+PS C:\> $bytes = Get-TarEntry .\archive.tar.gz -Algorithm gz -Include *.md | Get-TarEntryContent -AsByteStream -Raw
+PS C:\> $bytes[0].GetType()
+
+IsPublic IsSerial Name BaseType
+-------- -------- ---- --------
+True True Byte[] System.Array
+
+PS C:\> $bytes[1].Length
+7767
+```
+
+When the `-Raw` and `-AsByteStream` switches are used together, the cmdlet outputs `byte[]` as single objects for each tar entry.
+
+### Example 5: Get content from an input stream
+
+```powershell
+PS C:\> $stream = Invoke-WebRequest https://example.com/archive.tar.gz
+PS C:\> $content = $stream | Get-TarEntry -Include readme.md -Algorithm gz | Get-TarEntryContent -Raw
+PS C:\> $content
+
+# My Project
+This is the README file for my project.
+```
+
+> [!NOTE]
+> When `Get-TarEntry` processes a stream, it defaults to the `gz` (gzip) algorithm. Specify `-Algorithm` (e.g., `-Algorithm bz2` for bzip2) to match the compression type, or an error may occur if the stream is not gzip-compressed.
+
+## PARAMETERS
+
+### -AsByteStream
+
+Specifies that the content should be read as a stream of bytes.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: Bytes
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -BufferSize
+
+Determines the number of bytes read into the buffer before outputting the stream of bytes. This parameter applies only when `-Raw` is not used. The default buffer size is 128 KiB.
+
+```yaml
+Type: Int32
+Parameter Sets: Bytes
+Aliases:
+
+Required: False
+Position: Named
+Default value: 128000
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Encoding
+
+Specifies the character encoding used to read the entry content. . The default encoding is `utf8NoBOM`.
+
+> [!NOTE]
+>
+> - This parameter applies only when `-AsByteStream` is not used.
+> - The default encoding is __`utf8NoBOM`__.
+
+```yaml
+Type: Encoding
+Parameter Sets: Stream
+Aliases:
+
+Required: False
+Position: Named
+Default value: utf8NoBOM
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### -Entry
+
+The tar entry or entries to get the content from. This parameter is designed to accept pipeline input from `Get-TarEntry` but can also be used as a named parameter.
+
+```yaml
+Type: TarEntryFile[]
+Parameter Sets: (All)
+Aliases:
+
+Required: True
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -Raw
+
+Returns the entire contents of an entry as a single string with newlines preserved, ignoring newline characters. By default, newline characters are used to separate the content into an array of strings.
+
+```yaml
+Type: SwitchParameter
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
+### CommonParameters
+
+This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216).
+
+## INPUTS
+
+### PSCompression.TarEntryFile[]
+
+You can pipe instances of `TarEntryFile` to this cmdlet, produced by [`Get-TarEntry`](./Get-TarEntry.md).
+
+## OUTPUTS
+
+### System.String
+
+By default, this cmdlet returns the content as an array of strings, one per line. When the `-Raw` parameter is used, it returns a single string.
+
+### System.Byte
+
+This cmdlet returns the content as bytes when the `-AsByteStream` parameter is used.
+
+## NOTES
+
+## RELATED LINKS
+
+[__Get-TarEntry__](https://github.com/santisq/PSCompression)
+
+[__Expand-TarEntry__](https://github.com/santisq/PSCompression)
+
+[__SharpZipLib__](https://github.com/icsharpcode/SharpZipLib)
+
+[__SharpCompress__](https://github.com/adamhathcock/sharpcompress)
+
+[__ZstdSharp__](https://github.com/oleg-st/ZstdSharp)
diff --git a/docs/en-US/Get-ZipEntry.md b/docs/en-US/Get-ZipEntry.md
index 28bb1f8..3308690 100644
--- a/docs/en-US/Get-ZipEntry.md
+++ b/docs/en-US/Get-ZipEntry.md
@@ -40,7 +40,7 @@ Get-ZipEntry
```powershell
Get-ZipEntry
-InputStream
- [-Type ]
+ [-Type ]
[-Include ]
[-Exclude ]
[]
@@ -84,7 +84,7 @@ PS ..\pwsh> Get-ZipEntry .\PSCompression.zip -Include PSCompression/docs/en-us*
Type LastWriteTime CompressedSize Size Name
---- ------------- -------------- ---- ----
-Directory 2/22/2024 1:19 PM 0.00 B 0.00 B en-US
+Directory 2/22/2024 1:19 PM 0.00 B en-US
Archive 2/22/2024 1:19 PM 2.08 KB 6.98 KB Compress-GzipArchive.md
Archive 2/22/2024 1:19 PM 2.74 KB 8.60 KB Compress-ZipArchive.md
Archive 2/22/2024 1:19 PM 1.08 KB 2.67 KB ConvertFrom-GzipString.md
@@ -105,7 +105,7 @@ PS ..\pwsh> Get-ZipEntry .\PSCompression.zip -Include PSCompression/docs/en-us*
Type LastWriteTime CompressedSize Size Name
---- ------------- -------------- ---- ----
-Directory 2/22/2024 1:19 PM 0.00 B 0.00 B en-US
+Directory 2/22/2024 1:19 PM 0.00 B en-US
Archive 2/22/2024 1:19 PM 1.08 KB 2.67 KB ConvertFrom-GzipString.md
Archive 2/22/2024 1:19 PM 1.67 KB 4.63 KB ConvertTo-GzipString.md
Archive 2/22/2024 1:19 PM 1.74 KB 6.28 KB Expand-GzipArchive.md
@@ -170,10 +170,10 @@ Archive 11/6/2024 10:29 PM 635.00 B 1.55 KB 3212d87de0
### -Type
-Lists entries of a specified type, `Archive` or `Directory`.
+Filters entries by type: `Archive` for files or `Directory` for directories.
```yaml
-Type: ZipEntryType
+Type: EntryType
Parameter Sets: (All)
Aliases:
Accepted values: Directory, Archive
@@ -187,11 +187,10 @@ Accept wildcard characters: False
### -Exclude
-Specifies an array of one or more string patterns to be matched as the cmdlet lists entries. Any matching item is excluded from the output. Wildcard characters are accepted.
+Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are excluded from the output. Wildcard characters are supported.
> [!NOTE]
-> Inclusion and Exclusion patterns are applied to the entries relative path.
-Exclusions are applied after the inclusions.
+> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions.
```yaml
Type: String[]
@@ -207,11 +206,10 @@ Accept wildcard characters: True
### -Include
-Specifies an array of one or more string patterns to be matched as the cmdlet lists entries. Any matching item is included in the output. Wildcard characters are accepted.
+Specifies an array of string patterns to match as the cmdlet lists entries. Matching entries are included in the output. Wildcard characters are supported.
> [!NOTE]
-> Inclusion and Exclusion patterns are applied to the entries relative path.
-Exclusions are applied after the inclusions.
+> Inclusion and exclusion patterns are applied to the entries’ relative paths. Exclusions are applied after inclusions.
```yaml
Type: String[]
@@ -259,7 +257,7 @@ Accept wildcard characters: True
### -InputStream
-Specifies an input stream.
+Specifies an input stream containing a zip archive.
> [!TIP]
> Output from `Invoke-WebRequest` is bound to this paremeter automatically.
@@ -292,6 +290,6 @@ You can pipe a Stream to this cmdlet. Output from `Invoke-WebRequest` can be pip
## OUTPUTS
-### ZipEntryDirectory
+### PSCompression.ZipEntryDirectory
-### ZipEntryFile
+### PSCompression.ZipEntryFile
diff --git a/docs/en-US/Get-ZipEntryContent.md b/docs/en-US/Get-ZipEntryContent.md
index 53cb35c..a8d3772 100644
--- a/docs/en-US/Get-ZipEntryContent.md
+++ b/docs/en-US/Get-ZipEntryContent.md
@@ -17,7 +17,7 @@ Gets the content of a zip entry.
```powershell
Get-ZipEntryContent
- -ZipEntry
+ -Entry
[-Encoding ]
[-Raw]
[]
@@ -27,7 +27,7 @@ Get-ZipEntryContent
```powershell
Get-ZipEntryContent
- -ZipEntry
+ -Entry
[-Raw]
[-AsByteStream]
[-BufferSize ]
@@ -132,7 +132,7 @@ AliasesToExport {gziptofile, gzipfromfile, gziptostring, gzipfrom
### -BufferSize
-This parameter determines the total number of bytes read into the buffer before outputting the stream of bytes. __This parameter is applicable only when `-Raw` is not used.__ The buffer default value is __128 KiB.__
+Determines the number of bytes read into the buffer before outputting the stream of bytes. This parameter applies only when `-Raw` is not used. The default buffer size is 128 KiB.
```yaml
Type: Int32
@@ -148,11 +148,11 @@ Accept wildcard characters: False
### -Encoding
-The character encoding used to read the entry content.
+Specifies the character encoding used to read the entry content. . The default encoding is `utf8NoBOM`.
> [!NOTE]
>
-> - __This parameter is applicable only when `-AsByteStream` is not used.__
+> - This parameter applies only when `-AsByteStream` is not used.
> - The default encoding is __`utf8NoBOM`__.
```yaml
@@ -169,7 +169,7 @@ Accept wildcard characters: False
### -Raw
-Ignores newline characters and returns the entire contents of an entry in one string with the newlines preserved. By default, newline characters in a file are used as delimiters to separate the input into an array of strings.
+Returns the entire contents of an entry as a single string with newlines preserved, ignoring newline characters. By default, newline characters are used to separate the content into an array of strings.
```yaml
Type: SwitchParameter
@@ -183,9 +183,9 @@ Accept pipeline input: False
Accept wildcard characters: False
```
-### -ZipEntry
+### -Entry
-The entry or entries to get the content from. This parameter can be and is meant to be bound from pipeline however can be also used as a named parameter.
+The zip entry or entries to get the content from. This parameter is designed to accept pipeline input from `Get-ZipEntry` but can also be used as a named parameter.
```yaml
Type: ZipEntryFile[]
@@ -221,16 +221,16 @@ This cmdlet supports the common parameters. See [about_CommonParameters](http://
## INPUTS
-### ZipEntryFile
+### PSCompression.ZipEntryFile
You can pipe instances of `ZipEntryFile` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets.
## OUTPUTS
-### String
+### System.String
By default, this cmdlet returns the content as an array of strings, one per line. When the `-Raw` parameter is used, it returns a single string.
-### Byte
+### System.Byte
This cmdlet returns the content as bytes when the `-AsByteStream` parameter is used.
diff --git a/docs/en-US/New-ZipEntry.md b/docs/en-US/New-ZipEntry.md
index cf72907..926b06e 100644
--- a/docs/en-US/New-ZipEntry.md
+++ b/docs/en-US/New-ZipEntry.md
@@ -249,12 +249,12 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### String
+### System.String
You can pipe a value for the new zip entry to this cmdlet.
## OUTPUTS
-### ZipEntryDirectory
+### PSCompression.ZipEntryDirectory
-### ZipEntryFile
+### PSCompression.ZipEntryFile
diff --git a/docs/en-US/Remove-ZipEntry.md b/docs/en-US/Remove-ZipEntry.md
index 0c9dd7b..60ad969 100644
--- a/docs/en-US/Remove-ZipEntry.md
+++ b/docs/en-US/Remove-ZipEntry.md
@@ -109,7 +109,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### ZipEntryBase
+### PSCompression.Abstractions.ZipEntryBase
You can pipe instances of `ZipEntryFile` and `ZipEntryDirectory` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets.
diff --git a/docs/en-US/Rename-ZipEntry.md b/docs/en-US/Rename-ZipEntry.md
index b74c4bf..4dc2464 100644
--- a/docs/en-US/Rename-ZipEntry.md
+++ b/docs/en-US/Rename-ZipEntry.md
@@ -213,7 +213,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### ZipEntryBase
+### PSCompression.Abstractions.ZipEntryBase
You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet. These instances are produced by [`Get-ZipEntry`](Get-ZipEntry.md) and [`New-ZipEntry`](New-ZipEntry.md) cmdlets.
@@ -223,8 +223,8 @@ You can pipe instances of `ZipEntryFile` or `ZipEntryDirectory` to this cmdlet.
By default, this cmdlet produces no output.
-### ZipEntryFile
+### PSCompression.ZipEntryFile
-### ZipEntryDirectory
+### PSCompression.ZipEntryDirectory
This cmdlet outputs the renamed entries when the `-PassThru` switch is used.
diff --git a/docs/en-US/Set-ZipEntryContent.md b/docs/en-US/Set-ZipEntryContent.md
index 60d3281..b95e612 100644
--- a/docs/en-US/Set-ZipEntryContent.md
+++ b/docs/en-US/Set-ZipEntryContent.md
@@ -224,7 +224,7 @@ This cmdlet supports the common parameters. For more information, see [about_Com
## INPUTS
-### Object
+### System.Object
You can pipe strings or bytes to this cmdlet.
@@ -234,6 +234,6 @@ You can pipe strings or bytes to this cmdlet.
This cmdlet produces no output by default .
-### ZipEntryFile
+### PSCompression.ZipEntryFile
This cmdlet outputs the updated entry when the `-PassThru` switch is used.
diff --git a/module/PSCompression.Format.ps1xml b/module/PSCompression.Format.ps1xml
index cae0a22..6afc6f1 100644
--- a/module/PSCompression.Format.ps1xml
+++ b/module/PSCompression.Format.ps1xml
@@ -4,7 +4,7 @@
zipentryview
- PSCompression.ZipEntryBase
+ PSCompression.Abstractions.ZipEntryBase
[PSCompression.Internal._Format]::GetDirectoryPath($_)
@@ -68,5 +68,60 @@
+
+ tarentryview
+
+ PSCompression.Abstractions.TarEntryBase
+
+
+ [PSCompression.Internal._Format]::GetDirectoryPath($_)
+ Directory
+
+
+
+
+ Type
+ 10
+ Left
+
+
+ LastWriteTime
+ 26
+ Right
+
+
+ Size
+ 15
+ Right
+
+
+ Name
+ Left
+
+
+
+
+
+
+ Type
+
+
+ [PSCompression.Internal._Format]::GetFormattedDate($_.LastWriteTime)
+
+
+
+ if ($_ -is [PSCompression.TarEntryFile]) {
+ [PSCompression.Internal._Format]::GetFormattedLength($_.Length)
+ }
+
+
+
+ Name
+
+
+
+
+
+
diff --git a/module/PSCompression.psd1 b/module/PSCompression.psd1
index 6c84776..66574f7 100644
--- a/module/PSCompression.psd1
+++ b/module/PSCompression.psd1
@@ -11,7 +11,7 @@
RootModule = 'bin/netstandard2.0/PSCompression.dll'
# Version number of this module.
- ModuleVersion = '2.1.0'
+ ModuleVersion = '3.0.0'
# Supported PSEditions
# CompatiblePSEditions = @()
@@ -29,7 +29,7 @@
Copyright = '(c) Santiago Squarzon. All rights reserved.'
# Description of the functionality provided by this module
- Description = 'Zip and GZip utilities for PowerShell!'
+ Description = 'Zip, tar, and string compression utilities for PowerShell!'
# Minimum version of the PowerShell engine required by this module
PowerShellVersion = '5.1'
@@ -83,10 +83,19 @@
'Expand-ZipEntry'
'ConvertTo-GzipString'
'ConvertFrom-GzipString'
- 'Expand-GzipArchive'
- 'Compress-GzipArchive'
'Compress-ZipArchive'
'Rename-ZipEntry'
+ 'ConvertFrom-ZLibString'
+ 'ConvertTo-ZLibString'
+ 'ConvertFrom-DeflateString'
+ 'ConvertTo-DeflateString'
+ 'ConvertFrom-BrotliString'
+ 'ConvertTo-BrotliString'
+ 'Compress-TarArchive'
+ 'Get-TarEntry'
+ 'Get-TarEntryContent'
+ 'Expand-TarEntry'
+ 'Expand-TarArchive'
)
# Variables to export from this module
@@ -94,13 +103,27 @@
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = @(
- 'gziptofile'
- 'gzipfromfile'
- 'gziptostring'
- 'gzipfromstring'
- 'zip'
- 'ziparchive'
- 'gze'
+ 'tarcompress' # Compress-TarArchive
+ 'zipcompress' # Compress-ZipArchive
+ 'frombrotlistring' # ConvertFrom-BrotliString
+ 'fromdeflatestring' # ConvertFrom-DeflateString
+ 'fromgzipstring' # ConvertFrom-GzipString
+ 'fromzlibstring' # ConvertFrom-ZlibString
+ 'tobrotlistring' # ConvertTo-BrotliString
+ 'todeflatestring' # ConvertTo-DeflateString
+ 'togzipstring' # ConvertTo-GzipString
+ 'tozlibstring' # ConvertTo-ZlibString
+ 'untar' # Expand-TarArchive
+ 'untarentry' # Expand-TarEntry
+ 'unzipentry' # Expand-ZipEntry
+ 'targe' # Get-TarEntry
+ 'targec' # Get-TarEntryContent
+ 'zipge' # Get-ZipEntry
+ 'zipgec' # Get-ZipEntryContent
+ 'zipne' # New-ZipEntry
+ 'ziprm' # Remove-ZipEntry
+ 'zipren' # Rename-ZipEntry
+ 'zipsc' # Set-ZipEntryContent
)
# DSC resources to export from this module
@@ -120,9 +143,26 @@
'powershell'
'zip'
'zip-compression'
+ 'tar'
+ 'tar-compression'
'gzip'
'gzip-compression'
+ 'bzip2'
+ 'bzip2-compression'
+ 'zstd'
+ 'zstd-compression'
+ 'lzip'
+ 'lzip-compression'
+ 'brotli'
+ 'brotli-compression'
+ 'zlib'
+ 'zlib-compression'
+ 'deflate'
+ 'deflate-compression'
'compression'
+ 'decompression'
+ 'archive'
+ 'cross-platform'
'csharp'
)
diff --git a/src/PSCompression/CommandWithPathBase.cs b/src/PSCompression/Abstractions/CommandWithPathBase.cs
similarity index 98%
rename from src/PSCompression/CommandWithPathBase.cs
rename to src/PSCompression/Abstractions/CommandWithPathBase.cs
index a5bc887..bb9e292 100644
--- a/src/PSCompression/CommandWithPathBase.cs
+++ b/src/PSCompression/Abstractions/CommandWithPathBase.cs
@@ -6,7 +6,7 @@
using PSCompression.Exceptions;
using PSCompression.Extensions;
-namespace PSCompression;
+namespace PSCompression.Abstractions;
[EditorBrowsable(EditorBrowsableState.Never)]
public abstract class CommandWithPathBase : PSCmdlet
diff --git a/src/PSCompression/Abstractions/EntryBase.cs b/src/PSCompression/Abstractions/EntryBase.cs
new file mode 100644
index 0000000..78847fe
--- /dev/null
+++ b/src/PSCompression/Abstractions/EntryBase.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+
+namespace PSCompression.Abstractions;
+
+public abstract class EntryBase(string source)
+{
+ protected Stream? _stream;
+
+ protected string? _formatDirectoryPath;
+
+ internal string? FormatDirectoryPath { get => _formatDirectoryPath ??= GetFormatDirectoryPath(); }
+
+ internal bool FromStream { get => _stream is not null; }
+
+ public string Source { get; } = source;
+
+ public abstract string Name { get; protected set; }
+
+ public abstract string RelativePath { get; }
+
+ public abstract DateTime LastWriteTime { get; }
+
+ public abstract long Length { get; internal set; }
+
+ public abstract EntryType Type { get; }
+
+ protected abstract string GetFormatDirectoryPath();
+
+ public override string ToString() => RelativePath;
+}
diff --git a/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs
new file mode 100644
index 0000000..0bed864
--- /dev/null
+++ b/src/PSCompression/Abstractions/ExpandEntryCommandBase.cs
@@ -0,0 +1,70 @@
+using System;
+using System.IO;
+using System.Management.Automation;
+using PSCompression.Extensions;
+using PSCompression.Exceptions;
+using System.ComponentModel;
+
+namespace PSCompression.Abstractions;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public abstract class ExpandEntryCommandBase : PSCmdlet
+ where T : EntryBase
+{
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public T[] InputObject { get; set; } = null!;
+
+ [Parameter(Position = 0)]
+ [ValidateNotNullOrEmpty]
+ public string? Destination { get; set; }
+
+ [Parameter]
+ public SwitchParameter Force { get; set; }
+
+ [Parameter]
+ public SwitchParameter PassThru { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ Destination = Destination is null
+ // PowerShell is retarded and decided to mix up ProviderPath & Path
+ ? SessionState.Path.CurrentFileSystemLocation.ProviderPath
+ : Destination.ResolvePath(this);
+
+ if (File.Exists(Destination))
+ {
+ ThrowTerminatingError(ExceptionHelper.NotDirectoryPath(
+ Destination, nameof(Destination)));
+ }
+
+ Directory.CreateDirectory(Destination);
+ }
+
+ protected override void ProcessRecord()
+ {
+ Dbg.Assert(Destination is not null);
+
+ foreach (T entry in InputObject)
+ {
+ try
+ {
+ FileSystemInfo info = Extract(entry);
+
+ if (PassThru)
+ {
+ WriteObject(info.AppendPSProperties());
+ }
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToExtractEntryError(entry));
+ }
+ }
+ }
+
+ protected abstract FileSystemInfo Extract(T entry);
+}
diff --git a/src/PSCompression/Abstractions/FromCompressedStringCommandBase.cs b/src/PSCompression/Abstractions/FromCompressedStringCommandBase.cs
new file mode 100644
index 0000000..14fb148
--- /dev/null
+++ b/src/PSCompression/Abstractions/FromCompressedStringCommandBase.cs
@@ -0,0 +1,65 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Management.Automation;
+using System.Text;
+using PSCompression.Exceptions;
+using PSCompression.Extensions;
+
+namespace PSCompression.Abstractions;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public abstract class FromCompressedStringCommandBase : PSCmdlet
+{
+ protected delegate Stream DecompressionStreamFactory(Stream inputStream);
+
+ [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
+ public string[] InputObject { get; set; } = null!;
+
+ [Parameter]
+ [ArgumentCompleter(typeof(EncodingCompleter))]
+ [EncodingTransformation]
+ [ValidateNotNullOrEmpty]
+ public Encoding Encoding { get; set; } = new UTF8Encoding();
+
+ [Parameter]
+ public SwitchParameter Raw { get; set; }
+
+ private void Decompress(
+ string base64string,
+ DecompressionStreamFactory decompressionStreamFactory)
+ {
+ using MemoryStream inStream = new(Convert.FromBase64String(base64string));
+ using Stream decompressStream = decompressionStreamFactory(inStream);
+ using StreamReader reader = new(decompressStream, Encoding);
+
+ if (Raw)
+ {
+ reader.WriteAllTextToPipeline(this);
+ return;
+ }
+
+ reader.WriteLinesToPipeline(this);
+ }
+
+ protected abstract Stream CreateDecompressionStream(Stream inputStream);
+
+ protected override void ProcessRecord()
+ {
+ foreach (string line in InputObject)
+ {
+ try
+ {
+ Decompress(line, CreateDecompressionStream);
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToEnumerationError(line));
+ }
+ }
+ }
+}
diff --git a/src/PSCompression/Abstractions/GetEntryCommandBase.cs b/src/PSCompression/Abstractions/GetEntryCommandBase.cs
new file mode 100644
index 0000000..3e93b7a
--- /dev/null
+++ b/src/PSCompression/Abstractions/GetEntryCommandBase.cs
@@ -0,0 +1,172 @@
+using System.Linq;
+using System.Management.Automation;
+using System.IO;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System;
+using PSCompression.Exceptions;
+using ICSharpCode.SharpZipLib.Tar;
+using ZstdSharp;
+
+namespace PSCompression.Abstractions;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public abstract class GetEntryCommandBase : CommandWithPathBase
+{
+ internal abstract ArchiveType ArchiveType { get; }
+
+ [Parameter(
+ ParameterSetName = "Stream",
+ Position = 0,
+ Mandatory = true,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [Alias("RawContentStream")]
+ public Stream? InputStream { get; set; }
+
+ private WildcardPattern[]? _includePatterns;
+
+ private WildcardPattern[]? _excludePatterns;
+
+ [Parameter]
+ public EntryType? Type { get; set; }
+
+ [Parameter]
+ [SupportsWildcards]
+ public string[]? Include { get; set; }
+
+ [Parameter]
+ [SupportsWildcards]
+ public string[]? Exclude { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ if (Exclude is null && Include is null)
+ {
+ return;
+ }
+
+ const WildcardOptions options = WildcardOptions.Compiled
+ | WildcardOptions.CultureInvariant
+ | WildcardOptions.IgnoreCase;
+
+ if (Exclude is not null)
+ {
+ _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))];
+ }
+
+ if (Include is not null)
+ {
+ _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))];
+ }
+ }
+
+ protected override void ProcessRecord()
+ {
+ if (InputStream is not null)
+ {
+ HandleFromStream(InputStream);
+ return;
+ }
+
+ foreach (string path in EnumerateResolvedPaths())
+ {
+ if (path.WriteErrorIfNotArchive(
+ IsLiteral ? nameof(LiteralPath) : nameof(Path), this))
+ {
+ continue;
+ }
+
+ try
+ {
+ WriteObject(
+ GetEntriesFromFile(path).ToEntrySort(),
+ enumerateCollection: true);
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception) when (IsInvalidArchive(exception))
+ {
+ ThrowTerminatingError(exception.ToInvalidArchive(ArchiveType));
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToOpenError(path));
+ }
+ }
+ }
+
+ private void HandleFromStream(Stream stream)
+ {
+ try
+ {
+ if (stream.CanSeek)
+ {
+ stream.Seek(0, SeekOrigin.Begin);
+ }
+
+ WriteObject(
+ GetEntriesFromStream(stream).ToEntrySort(),
+ enumerateCollection: true);
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception) when (IsInvalidArchive(exception))
+ {
+ ThrowTerminatingError(exception.ToInvalidArchive(ArchiveType, isStream: true));
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToOpenError("InputStream"));
+ }
+ }
+
+ protected abstract IEnumerable GetEntriesFromFile(string path);
+
+ protected abstract IEnumerable GetEntriesFromStream(Stream stream);
+
+ private static bool MatchAny(
+ string name,
+ WildcardPattern[] patterns)
+ {
+ foreach (WildcardPattern pattern in patterns)
+ {
+ if (pattern.IsMatch(name))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected bool ShouldInclude(string name)
+ {
+ if (_includePatterns is null)
+ {
+ return true;
+ }
+
+ return MatchAny(name, _includePatterns);
+ }
+
+ protected bool ShouldExclude(string name)
+ {
+ if (_excludePatterns is null)
+ {
+ return false;
+ }
+
+ return MatchAny(name, _excludePatterns);
+ }
+
+ protected bool ShouldSkipEntry(bool isDirectory) =>
+ isDirectory && Type is EntryType.Archive || !isDirectory && Type is EntryType.Directory;
+
+ private bool IsInvalidArchive(Exception exception) =>
+ exception is InvalidDataException or TarException or ZstdException or IOException;
+}
diff --git a/src/PSCompression/Abstractions/GetEntryContentCommandBase.cs b/src/PSCompression/Abstractions/GetEntryContentCommandBase.cs
new file mode 100644
index 0000000..0427221
--- /dev/null
+++ b/src/PSCompression/Abstractions/GetEntryContentCommandBase.cs
@@ -0,0 +1,29 @@
+using System.ComponentModel;
+using System.Management.Automation;
+using System.Text;
+
+namespace PSCompression.Abstractions;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public abstract class GetEntryContentCommandBase : PSCmdlet
+ where T : EntryBase
+{
+ [Parameter(Mandatory = true, ValueFromPipeline = true)]
+ public T[] Entry { get; set; } = null!;
+
+ [Parameter(ParameterSetName = "Stream")]
+ [ArgumentCompleter(typeof(EncodingCompleter))]
+ [EncodingTransformation]
+ [ValidateNotNullOrEmpty]
+ public Encoding Encoding { get; set; } = new UTF8Encoding();
+
+ [Parameter]
+ public SwitchParameter Raw { get; set; }
+
+ [Parameter(ParameterSetName = "Bytes")]
+ public SwitchParameter AsByteStream { get; set; }
+
+ [Parameter(ParameterSetName = "Bytes")]
+ [ValidateNotNullOrEmpty]
+ public int BufferSize { get; set; } = 128_000;
+}
diff --git a/src/PSCompression/Abstractions/TarEntryBase.cs b/src/PSCompression/Abstractions/TarEntryBase.cs
new file mode 100644
index 0000000..b83a4b4
--- /dev/null
+++ b/src/PSCompression/Abstractions/TarEntryBase.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+using ICSharpCode.SharpZipLib.Tar;
+using PSCompression.Extensions;
+
+namespace PSCompression.Abstractions;
+
+public abstract class TarEntryBase(TarEntry entry, string source) : EntryBase(source)
+{
+ public override string Name { get; protected set; } = Path.GetFileName(entry.Name);
+
+ public override string RelativePath { get; } = entry.Name;
+
+ public override DateTime LastWriteTime { get; } = entry.ModTime;
+
+ public override long Length { get; internal set; } = entry.Size;
+
+ protected TarEntryBase(TarEntry entry, Stream? stream)
+ : this(entry, $"InputStream.{Guid.NewGuid()}")
+ {
+ _stream = stream;
+ }
+
+ internal FileSystemInfo ExtractTo(
+ string destination,
+ bool overwrite)
+ {
+ destination = Path.GetFullPath(
+ Path.Combine(destination, RelativePath));
+
+ if (this is not TarEntryFile entryFile)
+ {
+ DirectoryInfo dir = new(destination);
+ dir.Create(overwrite);
+ return dir;
+ }
+
+ FileInfo file = new(destination);
+ file.Directory.Create();
+
+ using FileStream destStream = File.Open(
+ destination,
+ overwrite ? FileMode.Create : FileMode.CreateNew,
+ FileAccess.Write);
+
+ entryFile.GetContentStream(destStream);
+ return file;
+ }
+}
diff --git a/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs
new file mode 100644
index 0000000..2338d13
--- /dev/null
+++ b/src/PSCompression/Abstractions/ToCompressedFileCommandBase.cs
@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Management.Automation;
+using PSCompression.Extensions;
+using PSCompression.Exceptions;
+using System.ComponentModel;
+using IOPath = System.IO.Path;
+
+namespace PSCompression.Abstractions;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public abstract class ToCompressedFileCommandBase : CommandWithPathBase, IDisposable
+ where T : IDisposable
+{
+ private T? _archive;
+
+ private FileStream? _destination;
+
+ private WildcardPattern[]? _excludePatterns;
+
+ private readonly Queue _queue = new();
+
+ private readonly HashSet _processed = [];
+
+ private bool _disposed;
+
+ private FileMode FileMode
+ {
+ get => (Update.IsPresent, Force.IsPresent) switch
+ {
+ (true, _) => FileMode.OpenOrCreate,
+ (_, true) => FileMode.Create,
+ _ => FileMode.CreateNew
+ };
+ }
+
+ protected abstract string FileExtension { get; }
+
+ [Parameter(Mandatory = true, Position = 1)]
+ [Alias("DestinationPath")]
+ public string Destination { get; set; } = null!;
+
+ [Parameter]
+ public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal;
+
+ [Parameter]
+ public virtual SwitchParameter Update { get; set; }
+
+ [Parameter]
+ public SwitchParameter Force { get; set; }
+
+ [Parameter]
+ public SwitchParameter PassThru { get; set; }
+
+ [Parameter]
+ [SupportsWildcards]
+ [ValidateNotNullOrEmpty]
+ public string[]? Exclude { get; set; }
+
+ protected abstract T CreateCompressionStream(Stream outputStream);
+
+ protected override void BeginProcessing()
+ {
+ Destination = ResolvePath(Destination)
+ .AddExtensionIfMissing(FileExtension);
+
+ try
+ {
+ Directory.CreateDirectory(IOPath.GetDirectoryName(Destination));
+
+ _destination = File.Open(Destination, FileMode);
+ _archive = CreateCompressionStream(_destination);
+ }
+ catch (Exception exception)
+ {
+ ThrowTerminatingError(exception.ToStreamOpenError(Destination));
+ }
+
+ if (Exclude is not null)
+ {
+ const WildcardOptions options = WildcardOptions.Compiled
+ | WildcardOptions.CultureInvariant
+ | WildcardOptions.IgnoreCase;
+
+ _excludePatterns = [.. Exclude.Select(pattern => new WildcardPattern(pattern, options))];
+ }
+ }
+
+ protected override void ProcessRecord()
+ {
+ Dbg.Assert(_archive is not null);
+ _queue.Clear();
+
+ foreach (string path in EnumerateResolvedPaths())
+ {
+ if (ShouldExclude(path) || ItemIsDestination(path, Destination))
+ {
+ continue;
+ }
+
+ if (Directory.Exists(path))
+ {
+ Traverse(new DirectoryInfo(path), _archive);
+ continue;
+ }
+
+ FileInfo file = new(path);
+ CreateOrUpdateFileEntry(_archive, file, file.Name);
+ }
+ }
+
+ private void Traverse(DirectoryInfo dir, T archive)
+ {
+ _queue.Enqueue(dir);
+ IEnumerable enumerator;
+ int length = dir.Parent.FullName.Length + 1;
+
+ while (_queue.Count > 0)
+ {
+ DirectoryInfo current = _queue.Dequeue();
+ CreateDirectoryEntry(archive, current, current.RelativeTo(length));
+
+ try
+ {
+ enumerator = current.EnumerateFileSystemInfos();
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToEnumerationError(current));
+ continue;
+ }
+
+ foreach (FileSystemInfo item in enumerator)
+ {
+ if (ShouldExclude(item.FullName))
+ {
+ continue;
+ }
+
+ if (item is DirectoryInfo directory)
+ {
+ _queue.Enqueue(directory);
+ continue;
+ }
+
+ FileInfo file = (FileInfo)item;
+ if (ItemIsDestination(file.FullName, Destination))
+ {
+ continue;
+ }
+
+ CreateOrUpdateFileEntry(archive, file, file.RelativeTo(length));
+ }
+ }
+ }
+
+ protected abstract void CreateDirectoryEntry(
+ T archive,
+ DirectoryInfo directory,
+ string path);
+
+ protected abstract void CreateOrUpdateFileEntry(
+ T archive,
+ FileInfo file,
+ string path);
+
+ protected static FileStream OpenFileStream(FileInfo file) =>
+ file.Open(
+ mode: FileMode.Open,
+ access: FileAccess.Read,
+ share: FileShare.ReadWrite | FileShare.Delete);
+
+ private static bool ItemIsDestination(string source, string destination) =>
+ source.Equals(destination, StringComparison.InvariantCultureIgnoreCase);
+
+ private bool ShouldExclude(string path)
+ {
+ if (!_processed.Add(path))
+ {
+ return true;
+ }
+
+ if (_excludePatterns is null)
+ {
+ return false;
+ }
+
+ foreach (WildcardPattern pattern in _excludePatterns)
+ {
+ if (pattern.IsMatch(path))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected override void EndProcessing()
+ {
+ _archive?.Dispose();
+ _destination?.Dispose();
+
+ if (PassThru.IsPresent && _destination is not null)
+ {
+ FileInfo passthru = new(_destination.Name);
+ WriteObject(passthru.AppendPSProperties(passthru.DirectoryName));
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing && !_disposed)
+ {
+ _archive?.Dispose();
+ _destination?.Dispose();
+ }
+
+ _disposed = true;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs b/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs
new file mode 100644
index 0000000..735e2a4
--- /dev/null
+++ b/src/PSCompression/Abstractions/ToCompressedStringCommandBase.cs
@@ -0,0 +1,89 @@
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using System.Text;
+using PSCompression.Exceptions;
+using PSCompression.Extensions;
+
+namespace PSCompression.Abstractions;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public abstract class ToCompressedStringCommandBase : PSCmdlet, IDisposable
+{
+ private StreamWriter? _writer;
+
+ private Stream? _compressStream;
+
+ private readonly MemoryStream _outstream = new();
+
+ [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
+ [AllowEmptyString]
+ public string[] InputObject { get; set; } = null!;
+
+ [Parameter]
+ [ArgumentCompleter(typeof(EncodingCompleter))]
+ [EncodingTransformation]
+ [ValidateNotNullOrEmpty]
+ public Encoding Encoding { get; set; } = new UTF8Encoding();
+
+ [Parameter]
+ public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal;
+
+ [Parameter]
+ [Alias("Raw")]
+ public SwitchParameter AsByteStream { get; set; }
+
+ [Parameter]
+ public SwitchParameter NoNewLine { get; set; }
+
+ protected abstract Stream CreateCompressionStream(
+ Stream outputStream,
+ CompressionLevel compressionLevel);
+
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ _compressStream ??= CreateCompressionStream(_outstream, CompressionLevel);
+ _writer ??= new StreamWriter(_compressStream, Encoding);
+
+ if (NoNewLine.IsPresent)
+ {
+ _writer.WriteContent(InputObject);
+ return;
+ }
+
+ _writer.WriteLines(InputObject);
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToWriteError(InputObject));
+ }
+ }
+
+ protected override void EndProcessing()
+ {
+ _writer?.Dispose();
+ _compressStream?.Dispose();
+ _outstream.Dispose();
+
+ if (AsByteStream)
+ {
+ // On purpose, we don't want to enumerate the byte[] for efficiency
+ WriteObject(_outstream.ToArray(), enumerateCollection: false);
+ return;
+ }
+
+ WriteObject(Convert.ToBase64String(_outstream.ToArray()));
+ }
+
+ public void Dispose()
+ {
+ _writer?.Dispose();
+ _compressStream?.Dispose();
+ _outstream.Dispose();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/src/PSCompression/ZipContentOpsBase.cs b/src/PSCompression/Abstractions/ZipContentOpsBase.cs
similarity index 69%
rename from src/PSCompression/ZipContentOpsBase.cs
rename to src/PSCompression/Abstractions/ZipContentOpsBase.cs
index 71b9e44..e8918a5 100644
--- a/src/PSCompression/ZipContentOpsBase.cs
+++ b/src/PSCompression/Abstractions/ZipContentOpsBase.cs
@@ -1,22 +1,22 @@
using System;
using System.IO.Compression;
-namespace PSCompression;
+namespace PSCompression.Abstractions;
internal abstract class ZipContentOpsBase(ZipArchive zip) : IDisposable
{
- protected ZipArchive _zip = zip;
-
protected byte[]? _buffer;
- public bool Disposed { get; internal set; }
+ protected ZipArchive _zip = zip;
+
+ protected bool _disposed;
protected virtual void Dispose(bool disposing)
{
- if (disposing && !Disposed)
+ if (disposing && !_disposed)
{
- _zip?.Dispose();
- Disposed = true;
+ _zip.Dispose();
+ _disposed = true;
}
}
diff --git a/src/PSCompression/ZipEntryBase.cs b/src/PSCompression/Abstractions/ZipEntryBase.cs
similarity index 60%
rename from src/PSCompression/ZipEntryBase.cs
rename to src/PSCompression/Abstractions/ZipEntryBase.cs
index 06072b6..d4a4344 100644
--- a/src/PSCompression/ZipEntryBase.cs
+++ b/src/PSCompression/Abstractions/ZipEntryBase.cs
@@ -1,44 +1,32 @@
using System;
using System.IO;
using System.IO.Compression;
-using PSCompression.Extensions;
using PSCompression.Exceptions;
+using PSCompression.Extensions;
-namespace PSCompression;
+namespace PSCompression.Abstractions;
-public abstract class ZipEntryBase(ZipArchiveEntry entry, string source)
+public abstract class ZipEntryBase(ZipArchiveEntry entry, string source) : EntryBase(source)
{
- protected string? _formatDirectoryPath;
-
- protected Stream? _stream;
-
- internal bool FromStream { get => _stream is not null; }
-
- internal abstract string FormatDirectoryPath { get; }
+ public override string Name { get; protected set; } = entry.Name;
- public string Source { get; } = source;
+ public override string RelativePath { get; } = entry.FullName;
- public string Name { get; protected set; } = entry.Name;
+ public override DateTime LastWriteTime { get; } = entry.LastWriteTime.LocalDateTime;
- public string RelativePath { get; } = entry.FullName;
-
- public DateTime LastWriteTime { get; } = entry.LastWriteTime.LocalDateTime;
-
- public long Length { get; internal set; } = entry.Length;
+ public override long Length { get; internal set; } = entry.Length;
public long CompressedLength { get; internal set; } = entry.CompressedLength;
- public abstract ZipEntryType Type { get; }
-
protected ZipEntryBase(ZipArchiveEntry entry, Stream? stream)
: this(entry, $"InputStream.{Guid.NewGuid()}")
{
_stream = stream;
}
- public ZipArchive OpenRead() => _stream is null
- ? ZipFile.OpenRead(Source)
- : new ZipArchive(_stream);
+ public ZipArchive OpenRead() => FromStream
+ ? new ZipArchive(_stream)
+ : ZipFile.OpenRead(Source);
public ZipArchive OpenWrite()
{
@@ -97,31 +85,45 @@ internal static string Move(
{
sourceStream.CopyTo(destinationStream);
}
- sourceEntry.Delete();
+ sourceEntry.Delete();
return destination;
}
internal ZipArchive OpenZip(ZipArchiveMode mode) =>
- _stream is null
- ? ZipFile.Open(Source, mode)
- : new ZipArchive(_stream, mode, true);
+ FromStream
+ ? new ZipArchive(_stream, mode, true)
+ : ZipFile.Open(Source, mode);
public FileSystemInfo ExtractTo(string destination, bool overwrite)
{
- using ZipArchive zip = _stream is null
- ? ZipFile.OpenRead(Source)
- : new ZipArchive(_stream);
+ using ZipArchive zip = _stream is not null
+ ? new ZipArchive(_stream, ZipArchiveMode.Read, leaveOpen: true)
+ : ZipFile.OpenRead(Source);
+
+ return ExtractTo(destination, overwrite, zip);
+ }
- (string path, bool isArchive) = this.ExtractTo(zip, destination, overwrite);
+ internal FileSystemInfo ExtractTo(
+ string destination,
+ bool overwrite,
+ ZipArchive zip)
+ {
+ destination = Path.GetFullPath(
+ Path.Combine(destination, RelativePath));
- if (isArchive)
+ if (Type == EntryType.Directory)
{
- return new FileInfo(path);
+ DirectoryInfo dir = new(destination);
+ dir.Create(overwrite);
+ return dir;
}
- return new DirectoryInfo(path);
- }
+ FileInfo file = new(destination);
+ file.Directory.Create();
- public override string ToString() => RelativePath;
+ ZipArchiveEntry entry = zip.GetEntry(RelativePath);
+ entry.ExtractToFile(destination, overwrite);
+ return file;
+ }
}
diff --git a/src/PSCompression/Algorithm.cs b/src/PSCompression/Algorithm.cs
new file mode 100644
index 0000000..b3967fb
--- /dev/null
+++ b/src/PSCompression/Algorithm.cs
@@ -0,0 +1,10 @@
+namespace PSCompression;
+
+public enum Algorithm
+{
+ gz,
+ bz2,
+ zst,
+ lz,
+ none
+}
diff --git a/src/PSCompression/AlgorithmMappings.cs b/src/PSCompression/AlgorithmMappings.cs
new file mode 100644
index 0000000..f9fc456
--- /dev/null
+++ b/src/PSCompression/AlgorithmMappings.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace PSCompression;
+
+internal static class AlgorithmMappings
+{
+ private static readonly Dictionary _mappings = new(
+ StringComparer.InvariantCultureIgnoreCase)
+ {
+ // Gzip
+ [".gz"] = Algorithm.gz,
+ [".gzip"] = Algorithm.gz,
+ [".tgz"] = Algorithm.gz,
+
+ // Bzip2
+ [".bz2"] = Algorithm.bz2,
+ [".bzip2"] = Algorithm.bz2,
+ [".tbz2"] = Algorithm.bz2,
+ [".tbz"] = Algorithm.bz2,
+
+ // Zstandard
+ [".zst"] = Algorithm.zst,
+
+ // Lzip
+ [".lz"] = Algorithm.lz,
+
+ // No compression
+ [".tar"] = Algorithm.none
+ };
+
+ internal static Algorithm Parse(string path) =>
+ _mappings.TryGetValue(Path.GetExtension(path), out Algorithm value)
+ ? value : Algorithm.none;
+}
diff --git a/src/PSCompression/ArchiveType.cs b/src/PSCompression/ArchiveType.cs
new file mode 100644
index 0000000..1f0d5ba
--- /dev/null
+++ b/src/PSCompression/ArchiveType.cs
@@ -0,0 +1,7 @@
+namespace PSCompression;
+
+internal enum ArchiveType
+{
+ zip,
+ tar
+}
diff --git a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs b/src/PSCompression/Commands/CompressGzipArchiveCommand.cs
deleted file mode 100644
index ab42ddf..0000000
--- a/src/PSCompression/Commands/CompressGzipArchiveCommand.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-using System;
-using System.IO;
-using System.IO.Compression;
-using System.Management.Automation;
-using PSCompression.Extensions;
-using PSCompression.Exceptions;
-
-namespace PSCompression.Commands;
-
-[Cmdlet(VerbsData.Compress, "GzipArchive", DefaultParameterSetName = "Path")]
-[OutputType(typeof(FileInfo))]
-[Alias("gziptofile")]
-public sealed class CompressGzipArchive : CommandWithPathBase, IDisposable
-{
- private FileStream? _destination;
-
- private GZipStream? _gzip;
-
- private FileMode Mode
- {
- get => (Update.IsPresent, Force.IsPresent) switch
- {
- (true, _) => FileMode.Append,
- (_, true) => FileMode.Create,
- _ => FileMode.CreateNew
- };
- }
-
- [Parameter(
- ParameterSetName = "InputBytes",
- Mandatory = true,
- ValueFromPipeline = true)]
- public byte[]? InputBytes { get; set; }
-
- [Parameter(Mandatory = true, Position = 1)]
- [Alias("DestinationPath")]
- public string Destination { get; set; } = null!;
-
- [Parameter]
- public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal;
-
- [Parameter]
- public SwitchParameter Update { get; set; }
-
- [Parameter]
- public SwitchParameter Force { get; set; }
-
- [Parameter]
- public SwitchParameter PassThru { get; set; }
-
- protected override void BeginProcessing()
- {
- Destination = ResolvePath(Destination).AddExtensionIfMissing(".gz");
-
- try
- {
- string parent = Destination.GetParent();
-
- if (!Directory.Exists(parent))
- {
- Directory.CreateDirectory(parent);
- }
-
- _destination = File.Open(Destination, Mode);
- }
- catch (Exception exception)
- {
- ThrowTerminatingError(exception.ToStreamOpenError(Destination));
- }
- }
-
- protected override void ProcessRecord()
- {
- Dbg.Assert(_destination is not null);
-
- if (InputBytes is not null)
- {
- try
- {
- _destination.Write(InputBytes, 0, InputBytes.Length);
- }
- catch (Exception exception)
- {
- WriteError(exception.ToWriteError(InputBytes));
- }
-
- return;
- }
-
- _gzip ??= new GZipStream(_destination, CompressionLevel);
-
- foreach (string path in EnumerateResolvedPaths())
- {
- if (!path.IsArchive())
- {
- WriteError(ExceptionHelper.NotArchivePath(
- path,
- IsLiteral ? nameof(LiteralPath) : nameof(Path)));
-
- continue;
- }
-
- try
- {
- using FileStream stream = File.OpenRead(path);
- stream.CopyTo(_gzip);
- }
- catch (Exception exception)
- {
- WriteError(exception.ToWriteError(path));
- }
- }
- }
-
- protected override void EndProcessing()
- {
- _gzip?.Dispose();
- _destination?.Dispose();
-
- if (PassThru.IsPresent && _destination is not null)
- {
- WriteObject(new FileInfo(_destination.Name));
- }
- }
-
- public void Dispose()
- {
- _gzip?.Dispose();
- _destination?.Dispose();
- GC.SuppressFinalize(this);
- }
-}
diff --git a/src/PSCompression/Commands/CompressTarArchiveCommand.cs b/src/PSCompression/Commands/CompressTarArchiveCommand.cs
new file mode 100644
index 0000000..2bfecb7
--- /dev/null
+++ b/src/PSCompression/Commands/CompressTarArchiveCommand.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Management.Automation;
+using System.Text;
+using ICSharpCode.SharpZipLib.Tar;
+using PSCompression.Abstractions;
+using PSCompression.Exceptions;
+using PSCompression.Extensions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.Compress, "TarArchive")]
+[OutputType(typeof(FileInfo))]
+[Alias("tarcompress")]
+public sealed class CompressTarArchiveCommand : ToCompressedFileCommandBase
+{
+ private Stream? _compressionStream;
+
+ // override this parameter without adding the decoration since this isn't supported for .tar
+ [ExcludeFromCodeCoverage]
+ public override SwitchParameter Update { get; set; }
+
+ [Parameter]
+ [ValidateNotNull]
+ public Algorithm Algorithm { get; set; } = Algorithm.gz;
+
+ protected override string FileExtension
+ {
+ get => Algorithm == Algorithm.none ? ".tar" : $".tar.{Algorithm}";
+ }
+
+ protected override TarOutputStream CreateCompressionStream(Stream outputStream)
+ {
+ if (Algorithm == Algorithm.lz &&
+ MyInvocation.BoundParameters.ContainsKey(nameof(CompressionLevel)))
+ {
+ WriteWarning(
+ "The lzip algorithm does not support custom CompressionLevel settings. " +
+ "The default compression level will be used.");
+ }
+
+ _compressionStream = Algorithm.ToCompressedStream(outputStream, CompressionLevel);
+ return new TarOutputStream(_compressionStream, Encoding.UTF8);
+ }
+
+ protected override void CreateDirectoryEntry(
+ TarOutputStream archive,
+ DirectoryInfo directory,
+ string path)
+ {
+ archive.CreateTarEntry(
+ entryName: path,
+ modTime: directory.LastWriteTimeUtc,
+ size: 0);
+
+ archive.CloseEntry();
+ }
+
+ protected override void CreateOrUpdateFileEntry(
+ TarOutputStream archive,
+ FileInfo file,
+ string path)
+ {
+ archive.CreateTarEntry(
+ entryName: path,
+ modTime: file.LastWriteTimeUtc,
+ size: file.Length);
+
+ try
+ {
+ using FileStream fs = OpenFileStream(file);
+ fs.CopyTo(archive);
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToStreamOpenError(file.FullName));
+ }
+ finally
+ {
+ archive.CloseEntry();
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ _compressionStream?.Dispose();
+ }
+}
diff --git a/src/PSCompression/Commands/CompressZipArchiveCommand.cs b/src/PSCompression/Commands/CompressZipArchiveCommand.cs
index 0a17b1f..9a335fe 100644
--- a/src/PSCompression/Commands/CompressZipArchiveCommand.cs
+++ b/src/PSCompression/Commands/CompressZipArchiveCommand.cs
@@ -1,27 +1,18 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
-using System.Linq;
using System.Management.Automation;
using PSCompression.Extensions;
using PSCompression.Exceptions;
+using PSCompression.Abstractions;
namespace PSCompression.Commands;
[Cmdlet(VerbsData.Compress, "ZipArchive")]
[OutputType(typeof(FileInfo))]
-[Alias("ziparchive")]
-public sealed class CompressZipArchiveCommand : CommandWithPathBase, IDisposable
+[Alias("zipcompress")]
+public sealed class CompressZipArchiveCommand : ToCompressedFileCommandBase
{
- private ZipArchive? _zip;
-
- private FileStream? _destination;
-
- private WildcardPattern[]? _excludePatterns;
-
- private readonly Queue _queue = new();
-
private ZipArchiveMode ZipArchiveMode
{
get => Force.IsPresent || Update.IsPresent
@@ -29,156 +20,7 @@ private ZipArchiveMode ZipArchiveMode
: ZipArchiveMode.Create;
}
- private FileMode FileMode
- {
- get => (Update.IsPresent, Force.IsPresent) switch
- {
- (true, _) => FileMode.OpenOrCreate,
- (_, true) => FileMode.Create,
- _ => FileMode.CreateNew
- };
- }
-
- [Parameter(Mandatory = true, Position = 1)]
- [Alias("DestinationPath")]
- public string Destination { get; set; } = null!;
-
- [Parameter]
- public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal;
-
- [Parameter]
- public SwitchParameter Update { get; set; }
-
- [Parameter]
- public SwitchParameter Force { get; set; }
-
- [Parameter]
- public SwitchParameter PassThru { get; set; }
-
- [Parameter]
- [SupportsWildcards]
- [ValidateNotNullOrEmpty]
- public string[]? Exclude { get; set; }
-
- protected override void BeginProcessing()
- {
- Destination = ResolvePath(Destination).AddExtensionIfMissing(".zip");
-
- try
- {
- string parent = Destination.GetParent();
-
- if (!Directory.Exists(parent))
- {
- Directory.CreateDirectory(parent);
- }
-
- _destination = File.Open(Destination, FileMode);
- _zip = new ZipArchive(_destination, ZipArchiveMode);
- }
- catch (Exception exception)
- {
- ThrowTerminatingError(exception.ToStreamOpenError(Destination));
- }
-
- if (Exclude is not null)
- {
- const WildcardOptions options = WildcardOptions.Compiled
- | WildcardOptions.CultureInvariant
- | WildcardOptions.IgnoreCase;
-
- _excludePatterns = [.. Exclude.Select(pattern => new WildcardPattern(pattern, options))];
- }
- }
-
- protected override void ProcessRecord()
- {
- Dbg.Assert(_zip is not null);
- _queue.Clear();
-
- foreach (string path in EnumerateResolvedPaths())
- {
- if (ShouldExclude(_excludePatterns, path))
- {
- continue;
- }
-
- if (!path.IsArchive())
- {
- Traverse(new DirectoryInfo(path), _zip);
- continue;
- }
-
- FileInfo file = new(path);
- if (Update.IsPresent && _zip.TryGetEntry(file.Name, out ZipArchiveEntry? entry))
- {
- UpdateEntry(file, entry);
- continue;
- }
-
- CreateEntry(file, _zip, file.Name);
- }
- }
-
- private void Traverse(DirectoryInfo dir, ZipArchive zip)
- {
- _queue.Enqueue(dir);
- IEnumerable enumerator;
- int length = dir.Parent.FullName.Length + 1;
-
- while (_queue.Count > 0)
- {
- DirectoryInfo current = _queue.Dequeue();
-
- string relative = current.RelativeTo(length);
-
- if (!Update.IsPresent || !zip.TryGetEntry(relative, out _))
- {
- zip.CreateEntry(current.RelativeTo(length));
- }
-
- try
- {
- enumerator = current.EnumerateFileSystemInfos();
- }
- catch (Exception exception)
- {
- WriteError(exception.ToEnumerationError(current));
- continue;
- }
-
- foreach (FileSystemInfo item in enumerator)
- {
- if (ShouldExclude(_excludePatterns, item.FullName))
- {
- continue;
- }
-
- if (item is DirectoryInfo directory)
- {
- _queue.Enqueue(directory);
- continue;
- }
-
- FileInfo file = (FileInfo)item;
-
- if (ItemIsDestination(file.FullName, Destination))
- {
- continue;
- }
-
- relative = file.RelativeTo(length);
-
- if (Update.IsPresent && zip.TryGetEntry(relative, out ZipArchiveEntry? entry))
- {
- UpdateEntry(file, entry);
- continue;
- }
-
- CreateEntry(file, zip, relative);
- }
- }
- }
+ protected override string FileExtension => ".zip";
private void CreateEntry(
FileInfo file,
@@ -187,7 +29,7 @@ private void CreateEntry(
{
try
{
- using FileStream fileStream = Open(file);
+ using FileStream fileStream = OpenFileStream(file);
using Stream stream = zip
.CreateEntry(relativepath, CompressionLevel)
.Open();
@@ -200,19 +42,13 @@ private void CreateEntry(
}
}
- private static FileStream Open(FileInfo file) =>
- file.Open(
- mode: FileMode.Open,
- access: FileAccess.Read,
- share: FileShare.ReadWrite | FileShare.Delete);
-
private void UpdateEntry(
FileInfo file,
ZipArchiveEntry entry)
{
try
{
- using FileStream fileStream = Open(file);
+ using FileStream fileStream = OpenFileStream(file);
using Stream stream = entry.Open();
stream.SetLength(0);
fileStream.CopyTo(stream);
@@ -223,44 +59,31 @@ private void UpdateEntry(
}
}
- private bool ItemIsDestination(string source, string destination) =>
- source.Equals(destination, StringComparison.InvariantCultureIgnoreCase);
+ protected override ZipArchive CreateCompressionStream(Stream outputStream) =>
+ new(outputStream, ZipArchiveMode);
- private static bool ShouldExclude(
- WildcardPattern[]? patterns,
+ protected override void CreateDirectoryEntry(
+ ZipArchive archive,
+ DirectoryInfo directory,
string path)
{
- if (patterns is null)
- {
- return false;
- }
-
- foreach (WildcardPattern pattern in patterns)
+ if (!Update || !archive.TryGetEntry(path, out _))
{
- if (pattern.IsMatch(path))
- {
- return true;
- }
+ archive.CreateEntry(path);
}
-
- return false;
}
- protected override void EndProcessing()
+ protected override void CreateOrUpdateFileEntry(
+ ZipArchive archive,
+ FileInfo file,
+ string path)
{
- _zip?.Dispose();
- _destination?.Dispose();
-
- if (PassThru.IsPresent && _destination is not null)
+ if (Update && archive.TryGetEntry(path, out ZipArchiveEntry? entry))
{
- WriteObject(new FileInfo(_destination.Name));
+ UpdateEntry(file, entry);
+ return;
}
- }
- public void Dispose()
- {
- _zip?.Dispose();
- _destination?.Dispose();
- GC.SuppressFinalize(this);
+ CreateEntry(file, archive, path);
}
}
diff --git a/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs
new file mode 100644
index 0000000..dfe29a7
--- /dev/null
+++ b/src/PSCompression/Commands/ConvertFromBrotliStringCommand.cs
@@ -0,0 +1,16 @@
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using BrotliSharpLib;
+using PSCompression.Abstractions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.ConvertFrom, "BrotliString")]
+[OutputType(typeof(string))]
+[Alias("frombrotlistring")]
+public sealed class ConvertFromBrotliStringCommand : FromCompressedStringCommandBase
+{
+ protected override Stream CreateDecompressionStream(Stream inputStream) =>
+ new BrotliStream(inputStream, CompressionMode.Decompress);
+}
diff --git a/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs
new file mode 100644
index 0000000..8b4bf8a
--- /dev/null
+++ b/src/PSCompression/Commands/ConvertFromDeflateStringCommand.cs
@@ -0,0 +1,15 @@
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.ConvertFrom, "DeflateString")]
+[OutputType(typeof(string))]
+[Alias("fromdeflatestring")]
+public sealed class ConvertFromDeflateStringCommand : FromCompressedStringCommandBase
+{
+ protected override Stream CreateDecompressionStream(Stream inputStream) =>
+ new DeflateStream(inputStream, CompressionMode.Decompress);
+}
diff --git a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs
index 2016913..6f4442a 100644
--- a/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs
+++ b/src/PSCompression/Commands/ConvertFromGzipStringCommand.cs
@@ -1,58 +1,15 @@
-using System;
using System.IO;
using System.IO.Compression;
using System.Management.Automation;
-using System.Text;
-using PSCompression.Exceptions;
+using PSCompression.Abstractions;
namespace PSCompression.Commands;
[Cmdlet(VerbsData.ConvertFrom, "GzipString")]
[OutputType(typeof(string))]
-[Alias("gzipfromstring")]
-public sealed class ConvertFromGzipStringCommand : PSCmdlet
+[Alias("fromgzipstring")]
+public sealed class ConvertFromGzipStringCommand : FromCompressedStringCommandBase
{
- [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
- public string[] InputObject { get; set; } = null!;
-
- [Parameter]
- [ArgumentCompleter(typeof(EncodingCompleter))]
- [EncodingTransformation]
- [ValidateNotNullOrEmpty]
- public Encoding Encoding { get; set; } = new UTF8Encoding();
-
- [Parameter]
- public SwitchParameter Raw { get; set; }
-
- protected override void ProcessRecord()
- {
- foreach (string line in InputObject)
- {
- try
- {
- using MemoryStream inStream = new(Convert.FromBase64String(line));
- using GZipStream gzip = new(inStream, CompressionMode.Decompress);
- using StreamReader reader = new(gzip, Encoding);
-
- if (Raw.IsPresent)
- {
- WriteObject(reader.ReadToEnd());
- return;
- }
-
- while (!reader.EndOfStream)
- {
- WriteObject(reader.ReadLine());
- }
- }
- catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
- {
- throw;
- }
- catch (Exception exception)
- {
- WriteError(exception.ToEnumerationError(line));
- }
- }
- }
+ protected override Stream CreateDecompressionStream(Stream inputStream) =>
+ new GZipStream(inputStream, CompressionMode.Decompress);
}
diff --git a/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs
new file mode 100644
index 0000000..3df4254
--- /dev/null
+++ b/src/PSCompression/Commands/ConvertFromZLibStringCommand.cs
@@ -0,0 +1,19 @@
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.ConvertFrom, "ZLibString")]
+[OutputType(typeof(string))]
+[Alias("fromzlibstring")]
+public sealed class ConvertFromZLibStringCommand : FromCompressedStringCommandBase
+{
+ protected override Stream CreateDecompressionStream(Stream inputStream)
+ {
+ inputStream.Seek(2, SeekOrigin.Begin);
+ DeflateStream deflate = new(inputStream, CompressionMode.Decompress);
+ return deflate;
+ }
+}
diff --git a/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs
new file mode 100644
index 0000000..82d16f7
--- /dev/null
+++ b/src/PSCompression/Commands/ConvertToBrotliStringCommand.cs
@@ -0,0 +1,18 @@
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+using PSCompression.Extensions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.ConvertTo, "BrotliString")]
+[OutputType(typeof(byte[]), typeof(string))]
+[Alias("tobrotlistring")]
+public sealed class ConvertToBrotliStringCommand : ToCompressedStringCommandBase
+{
+ protected override Stream CreateCompressionStream(
+ Stream outputStream,
+ CompressionLevel compressionLevel)
+ => outputStream.AsBrotliCompressedStream(compressionLevel);
+}
diff --git a/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs
new file mode 100644
index 0000000..f43f8f0
--- /dev/null
+++ b/src/PSCompression/Commands/ConvertToDeflateStringCommand.cs
@@ -0,0 +1,17 @@
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.ConvertTo, "DeflateString")]
+[OutputType(typeof(byte[]), typeof(string))]
+[Alias("todeflatestring")]
+public sealed class ConvertToDeflateStringCommand : ToCompressedStringCommandBase
+{
+ protected override Stream CreateCompressionStream(
+ Stream outputStream,
+ CompressionLevel compressionLevel)
+ => new DeflateStream(outputStream, compressionLevel);
+}
diff --git a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs
index 1d8d722..f1b2366 100644
--- a/src/PSCompression/Commands/ConvertToGzipStringCommand.cs
+++ b/src/PSCompression/Commands/ConvertToGzipStringCommand.cs
@@ -1,115 +1,17 @@
-using System;
using System.IO;
using System.IO.Compression;
using System.Management.Automation;
-using System.Text;
-using PSCompression.Exceptions;
+using PSCompression.Abstractions;
namespace PSCompression.Commands;
[Cmdlet(VerbsData.ConvertTo, "GzipString")]
[OutputType(typeof(byte[]), typeof(string))]
-[Alias("gziptostring")]
-public sealed class ConvertToGzipStringCommand : PSCmdlet, IDisposable
+[Alias("togzipstring")]
+public sealed class ConvertToGzipStringCommand : ToCompressedStringCommandBase
{
- private StreamWriter? _writer;
-
- private GZipStream? _gzip;
-
- private readonly MemoryStream _outstream = new();
-
- [Parameter(Mandatory = true, ValueFromPipeline = true, Position = 0)]
- [AllowEmptyString]
- public string[] InputObject { get; set; } = null!;
-
- [Parameter]
- [ArgumentCompleter(typeof(EncodingCompleter))]
- [EncodingTransformation]
- [ValidateNotNullOrEmpty]
- public Encoding Encoding { get; set; } = new UTF8Encoding();
-
- [Parameter]
- public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal;
-
- [Parameter]
- [Alias("Raw")]
- public SwitchParameter AsByteStream { get; set; }
-
- [Parameter]
- public SwitchParameter NoNewLine { get; set; }
-
- protected override void ProcessRecord()
- {
- try
- {
- _gzip ??= new(_outstream, CompressionLevel);
- _writer ??= new(_gzip, Encoding);
-
- if (NoNewLine.IsPresent)
- {
- Write(_writer, InputObject);
- return;
- }
-
- WriteLines(_writer, InputObject);
- }
- catch (Exception exception)
- {
- WriteError(exception.ToWriteError(InputObject));
- }
- }
-
- protected override void EndProcessing()
- {
- _writer?.Dispose();
- _gzip?.Dispose();
- _outstream?.Dispose();
-
- try
- {
- if (_writer is null || _gzip is null || _outstream is null)
- {
- return;
- }
-
- if (AsByteStream.IsPresent)
- {
- WriteObject(_outstream.ToArray(), enumerateCollection: false);
- return;
- }
-
- WriteObject(Convert.ToBase64String(_outstream.ToArray()));
- }
- catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
- {
- throw;
- }
- catch (Exception exception)
- {
- WriteError(exception.ToWriteError(_outstream));
- }
- }
-
- private void WriteLines(StreamWriter writer, string[] lines)
- {
- foreach (string line in lines)
- {
- writer.WriteLine(line);
- }
- }
-
- private void Write(StreamWriter writer, string[] lines)
- {
- foreach (string line in lines)
- {
- writer.Write(line);
- }
- }
-
- public void Dispose()
- {
- _writer?.Dispose();
- _gzip?.Dispose();
- _outstream?.Dispose();
- }
+ protected override Stream CreateCompressionStream(
+ Stream outputStream,
+ CompressionLevel compressionLevel)
+ => new GZipStream(outputStream, compressionLevel);
}
diff --git a/src/PSCompression/Commands/ConvertToZLibStringCommand.cs b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs
new file mode 100644
index 0000000..f15e547
--- /dev/null
+++ b/src/PSCompression/Commands/ConvertToZLibStringCommand.cs
@@ -0,0 +1,17 @@
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.ConvertTo, "ZLibString")]
+[OutputType(typeof(byte[]), typeof(string))]
+[Alias("tozlibstring")]
+public sealed class ConvertToZLibStringCommand : ToCompressedStringCommandBase
+{
+ protected override Stream CreateCompressionStream(
+ Stream outputStream,
+ CompressionLevel compressionLevel)
+ => new ZlibStream(outputStream, compressionLevel);
+}
diff --git a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs b/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs
deleted file mode 100644
index e454822..0000000
--- a/src/PSCompression/Commands/ExpandGzipArchiveCommand.cs
+++ /dev/null
@@ -1,179 +0,0 @@
-using System;
-using System.IO;
-using System.Management.Automation;
-using System.Text;
-using PSCompression.Extensions;
-using PSCompression.Exceptions;
-
-namespace PSCompression.Commands;
-
-[Cmdlet(VerbsData.Expand, "GzipArchive")]
-[OutputType(
- typeof(string),
- ParameterSetName = ["Path", "LiteralPath"])]
-[OutputType(
- typeof(FileInfo),
- ParameterSetName = ["PathDestination", "LiteralPathDestination"])]
-[Alias("gzipfromfile")]
-public sealed class ExpandGzipArchiveCommand : CommandWithPathBase, IDisposable
-{
- private FileStream? _destination;
-
- private FileMode FileMode
- {
- get => (Update.IsPresent, Force.IsPresent) switch
- {
- (true, _) => FileMode.Append,
- (_, true) => FileMode.Create,
- _ => FileMode.CreateNew
- };
- }
-
- [Parameter(
- ParameterSetName = "Path",
- Position = 0,
- Mandatory = true,
- ValueFromPipeline = true)]
- [Parameter(
- ParameterSetName = "PathDestination",
- Position = 0,
- Mandatory = true,
- ValueFromPipeline = true)]
- [SupportsWildcards]
- public override string[] Path
- {
- get => _paths;
- set => _paths = value;
- }
-
- [Parameter(
- ParameterSetName = "LiteralPath",
- Mandatory = true,
- ValueFromPipelineByPropertyName = true)]
- [Parameter(
- ParameterSetName = "LiteralPathDestination",
- Mandatory = true,
- ValueFromPipelineByPropertyName = true)]
- [Alias("PSPath")]
- public override string[] LiteralPath
- {
- get => _paths;
- set => _paths = value;
- }
-
- [Parameter(
- Mandatory = true,
- Position = 1,
- ParameterSetName = "PathDestination")]
- [Parameter(
- Mandatory = true,
- Position = 1,
- ParameterSetName = "LiteralPathDestination")]
- [Alias("DestinationPath")]
- public string Destination { get; set; } = null!;
-
- [Parameter(ParameterSetName = "PathDestination")]
- [Parameter(ParameterSetName = "LiteralPathDestination")]
- [ArgumentCompleter(typeof(EncodingCompleter))]
- [EncodingTransformation]
- [ValidateNotNullOrEmpty]
- public Encoding Encoding { get; set; } = new UTF8Encoding();
-
- [Parameter(ParameterSetName = "Path")]
- [Parameter(ParameterSetName = "LiteralPath")]
- public SwitchParameter Raw { get; set; }
-
- [Parameter(ParameterSetName = "PathDestination")]
- [Parameter(ParameterSetName = "LiteralPathDestination")]
- public SwitchParameter PassThru { get; set; }
-
- [Parameter(ParameterSetName = "PathDestination")]
- [Parameter(ParameterSetName = "LiteralPathDestination")]
- public SwitchParameter Force { get; set; }
-
- [Parameter(ParameterSetName = "PathDestination")]
- [Parameter(ParameterSetName = "LiteralPathDestination")]
- public SwitchParameter Update { get; set; }
-
- protected override void BeginProcessing()
- {
- if (Destination is not null && _destination is null)
- {
- try
- {
- Destination = ResolvePath(Destination);
- string parent = Destination.GetParent();
-
- if (!Directory.Exists(parent))
- {
- Directory.CreateDirectory(parent);
- }
-
- _destination = File.Open(Destination, FileMode);
- }
- catch (Exception exception)
- {
- ThrowTerminatingError(exception.ToStreamOpenError(Destination));
- }
- }
- }
-
- protected override void ProcessRecord()
- {
- foreach (string path in EnumerateResolvedPaths())
- {
- if (!path.IsArchive())
- {
- WriteError(ExceptionHelper.NotArchivePath(
- path,
- IsLiteral ? nameof(LiteralPath) : nameof(Path)));
-
- continue;
- }
-
- try
- {
- if (_destination is not null)
- {
- GzipReaderOps.CopyTo(
- path: path,
- isCoreCLR: PSVersionHelper.IsCoreCLR,
- destination: _destination);
-
- continue;
- }
-
- GzipReaderOps.GetContent(
- path: path,
- isCoreCLR: PSVersionHelper.IsCoreCLR,
- raw: Raw.IsPresent,
- encoding: Encoding,
- cmdlet: this);
- }
- catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
- {
- throw;
- }
- catch (Exception exception)
- {
- WriteError(exception.ToOpenError(path));
- }
- }
- }
-
- protected override void EndProcessing()
- {
- _destination?.Dispose();
-
- if (PassThru.IsPresent && _destination is not null)
- {
- WriteObject(new FileInfo(_destination.Name));
- }
- }
-
- public void Dispose()
- {
- _destination?.Dispose();
- GC.SuppressFinalize(this);
- }
-}
diff --git a/src/PSCompression/Commands/ExpandTarArchiveCommand.cs b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs
new file mode 100644
index 0000000..669e9eb
--- /dev/null
+++ b/src/PSCompression/Commands/ExpandTarArchiveCommand.cs
@@ -0,0 +1,142 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Management.Automation;
+using System.Text;
+using ICSharpCode.SharpZipLib.Tar;
+using PSCompression.Abstractions;
+using PSCompression.Exceptions;
+using PSCompression.Extensions;
+using IO = System.IO;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.Expand, "TarArchive")]
+[OutputType(typeof(FileInfo), typeof(DirectoryInfo))]
+[Alias("untar")]
+public sealed class ExpandTarArchiveCommand : CommandWithPathBase
+{
+ private bool _shouldInferAlgo;
+
+ [Parameter(Position = 1)]
+ public string? Destination { get; set; }
+
+ [Parameter]
+ public Algorithm Algorithm { get; set; }
+
+ [Parameter]
+ public SwitchParameter Force { get; set; }
+
+ [Parameter]
+ public SwitchParameter PassThru { get; set; }
+
+ protected override void BeginProcessing()
+ {
+ Destination = Destination is null
+ // PowerShell is retarded and decided to mix up ProviderPath & Path
+ ? SessionState.Path.CurrentFileSystemLocation.ProviderPath
+ : Destination.ResolvePath(this);
+
+ if (File.Exists(Destination))
+ {
+ ThrowTerminatingError(ExceptionHelper.NotDirectoryPath(
+ Destination, nameof(Destination)));
+ }
+
+ Directory.CreateDirectory(Destination);
+
+ _shouldInferAlgo = !MyInvocation.BoundParameters
+ .ContainsKey(nameof(Algorithm));
+ }
+
+ protected override void ProcessRecord()
+ {
+ Dbg.Assert(Destination is not null);
+
+ foreach (string path in EnumerateResolvedPaths())
+ {
+ if (_shouldInferAlgo)
+ {
+ Algorithm = AlgorithmMappings.Parse(path);
+ }
+
+ try
+ {
+ FileSystemInfo[] output = ExtractArchive(path);
+
+ if (PassThru)
+ {
+ IOrderedEnumerable result = output
+ .Select(PathExtensions.AppendPSProperties)
+ .OrderBy(pso => pso.Properties["PSParentPath"].Value);
+
+ WriteObject(result, enumerateCollection: true);
+ }
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToWriteError(path));
+ }
+ }
+ }
+
+ private FileSystemInfo[] ExtractArchive(string path)
+ {
+ using FileStream fs = File.OpenRead(path);
+ using Stream decompress = Algorithm.FromCompressedStream(fs);
+ using TarInputStream tar = new(decompress, Encoding.UTF8);
+
+ List result = [];
+ foreach (TarEntry entry in tar.EnumerateEntries())
+ {
+ try
+ {
+ result.Add(ExtractEntry(entry, tar));
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToExtractEntryError(entry));
+ }
+ }
+
+ return [.. result];
+ }
+
+ private FileSystemInfo ExtractEntry(TarEntry entry, TarInputStream tar)
+ {
+ string destination = IO.Path.GetFullPath(
+ IO.Path.Combine(Destination, entry.Name));
+
+ if (entry.IsDirectory)
+ {
+ DirectoryInfo dir = new(destination);
+ dir.Create(Force);
+ return dir;
+ }
+
+ FileInfo file = new(destination);
+ file.Directory.Create();
+
+ using (FileStream destStream = File.Open(
+ destination,
+ Force ? FileMode.Create : FileMode.CreateNew,
+ FileAccess.Write))
+ {
+ if (entry.Size > 0)
+ {
+ tar.CopyTo(destStream, (int)entry.Size);
+ }
+ }
+
+ return file;
+ }
+}
diff --git a/src/PSCompression/Commands/ExpandTarEntryCommand.cs b/src/PSCompression/Commands/ExpandTarEntryCommand.cs
new file mode 100644
index 0000000..321ff8c
--- /dev/null
+++ b/src/PSCompression/Commands/ExpandTarEntryCommand.cs
@@ -0,0 +1,14 @@
+using System.IO;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsData.Expand, "TarEntry")]
+[OutputType(typeof(FileInfo), typeof(DirectoryInfo))]
+[Alias("untarentry")]
+public sealed class ExpandTarEntryCommand : ExpandEntryCommandBase
+{
+ protected override FileSystemInfo Extract(TarEntryBase entry) =>
+ entry.ExtractTo(Destination!, Force);
+}
diff --git a/src/PSCompression/Commands/ExpandZipEntryCommand.cs b/src/PSCompression/Commands/ExpandZipEntryCommand.cs
index e13e849..a981300 100644
--- a/src/PSCompression/Commands/ExpandZipEntryCommand.cs
+++ b/src/PSCompression/Commands/ExpandZipEntryCommand.cs
@@ -1,84 +1,19 @@
using System;
using System.IO;
using System.Management.Automation;
-using PSCompression.Extensions;
-using PSCompression.Exceptions;
+using PSCompression.Abstractions;
namespace PSCompression.Commands;
[Cmdlet(VerbsData.Expand, "ZipEntry")]
-[OutputType(typeof(FileSystemInfo))]
-public sealed class ExpandZipEntryCommand : PSCmdlet, IDisposable
+[OutputType(typeof(FileInfo), typeof(DirectoryInfo))]
+[Alias("unzipentry")]
+public sealed class ExpandZipEntryCommand : ExpandEntryCommandBase, IDisposable
{
private readonly ZipArchiveCache _cache = new();
- [Parameter(Mandatory = true, ValueFromPipeline = true)]
- public ZipEntryBase[] InputObject { get; set; } = null!;
-
- [Parameter(Position = 0)]
- [ValidateNotNullOrEmpty]
- public string? Destination { get; set; }
-
- [Parameter]
- public SwitchParameter Force { get; set; }
-
- [Parameter]
- public SwitchParameter PassThru { get; set; }
-
- protected override void BeginProcessing()
- {
- Destination = Destination is null
- ? SessionState.Path.CurrentFileSystemLocation.Path
- : Destination.ResolvePath(this);
-
- if (Destination.IsArchive())
- {
- ThrowTerminatingError(
- ExceptionHelper.NotDirectoryPath(
- Destination,
- nameof(Destination)));
- }
-
- if (!Directory.Exists(Destination))
- {
- Directory.CreateDirectory(Destination);
- }
- }
-
- protected override void ProcessRecord()
- {
- Dbg.Assert(Destination is not null);
-
- foreach (ZipEntryBase entry in InputObject)
- {
- try
- {
- (string path, bool isfile) = entry.ExtractTo(
- _cache.GetOrAdd(entry),
- Destination,
- Force.IsPresent);
-
- if (PassThru.IsPresent)
- {
- if (isfile)
- {
- WriteObject(new FileInfo(path));
- continue;
- }
-
- WriteObject(new DirectoryInfo(path));
- }
- }
- catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
- {
- throw;
- }
- catch (Exception exception)
- {
- WriteError(exception.ToExtractEntryError(entry));
- }
- }
- }
+ protected override FileSystemInfo Extract(ZipEntryBase entry) =>
+ entry.ExtractTo(Destination!, Force, _cache.GetOrAdd(entry));
public void Dispose()
{
diff --git a/src/PSCompression/Commands/GetTarEntryCommand.cs b/src/PSCompression/Commands/GetTarEntryCommand.cs
new file mode 100644
index 0000000..994e4d7
--- /dev/null
+++ b/src/PSCompression/Commands/GetTarEntryCommand.cs
@@ -0,0 +1,72 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Management.Automation;
+using System.Text;
+using ICSharpCode.SharpZipLib.Tar;
+using PSCompression.Abstractions;
+using PSCompression.Extensions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsCommon.Get, "TarEntry", DefaultParameterSetName = "Path")]
+[OutputType(typeof(TarEntryDirectory), typeof(TarEntryFile))]
+[Alias("targe")]
+public sealed class GetTarEntryCommand : GetEntryCommandBase
+{
+ [Parameter]
+ public Algorithm Algorithm { get; set; }
+
+ internal override ArchiveType ArchiveType => ArchiveType.tar;
+
+ protected override IEnumerable GetEntriesFromFile(string path)
+ {
+ if (!MyInvocation.BoundParameters.ContainsKey(nameof(Algorithm)))
+ {
+ Algorithm = AlgorithmMappings.Parse(path);
+ }
+
+ using FileStream fs = File.OpenRead(path);
+ using Stream stream = Algorithm.FromCompressedStream(fs);
+ using TarInputStream tar = new(stream, Encoding.UTF8);
+
+ foreach (TarEntry entry in tar.EnumerateEntries())
+ {
+ if (ShouldSkipEntry(entry.IsDirectory))
+ {
+ continue;
+ }
+
+ if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name))
+ {
+ continue;
+ }
+
+ yield return entry.IsDirectory
+ ? new TarEntryDirectory(entry, path)
+ : new TarEntryFile(entry, path, Algorithm);
+ }
+ }
+
+ protected override IEnumerable GetEntriesFromStream(Stream stream)
+ {
+ Stream decompressStream = Algorithm.FromCompressedStream(stream);
+ TarInputStream tar = new(decompressStream, Encoding.UTF8);
+
+ foreach (TarEntry entry in tar.EnumerateEntries())
+ {
+ if (ShouldSkipEntry(entry.IsDirectory))
+ {
+ continue;
+ }
+
+ if (!ShouldInclude(entry.Name) || ShouldExclude(entry.Name))
+ {
+ continue;
+ }
+
+ yield return entry.IsDirectory
+ ? new TarEntryDirectory(entry, stream)
+ : new TarEntryFile(entry, stream, Algorithm);
+ }
+ }
+}
diff --git a/src/PSCompression/Commands/GetTarEntryContentCommand.cs b/src/PSCompression/Commands/GetTarEntryContentCommand.cs
new file mode 100644
index 0000000..e997f9a
--- /dev/null
+++ b/src/PSCompression/Commands/GetTarEntryContentCommand.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Management.Automation;
+using PSCompression.Abstractions;
+using PSCompression.Exceptions;
+using PSCompression.Extensions;
+
+namespace PSCompression.Commands;
+
+[Cmdlet(VerbsCommon.Get, "TarEntryContent", DefaultParameterSetName = "Stream")]
+[OutputType(typeof(string), ParameterSetName = ["Stream"])]
+[OutputType(typeof(byte), ParameterSetName = ["Bytes"])]
+[Alias("targec")]
+public sealed class GetTarEntryContentCommand : GetEntryContentCommandBase
+{
+ private byte[]? _buffer;
+
+ protected override void ProcessRecord()
+ {
+ foreach (TarEntryFile entry in Entry)
+ {
+ try
+ {
+ ReadEntry(entry);
+ }
+ catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
+ {
+ throw;
+ }
+ catch (Exception exception)
+ {
+ WriteError(exception.ToOpenError(entry.Source));
+ }
+ }
+ }
+
+ private void ReadEntry(TarEntryFile entry)
+ {
+ using MemoryStream mem = new();
+
+ if (!entry.GetContentStream(mem))
+ {
+ return;
+ }
+
+ if (AsByteStream)
+ {
+ if (Raw)
+ {
+ WriteObject(mem.ToArray());
+ return;
+ }
+
+ StreamBytes(mem);
+ return;
+ }
+
+ using StreamReader reader = new(mem, Encoding);
+
+ if (Raw.IsPresent)
+ {
+ reader.WriteAllTextToPipeline(this);
+ return;
+ }
+
+ reader.WriteLinesToPipeline(this);
+ }
+
+ private void StreamBytes(Stream stream)
+ {
+ int bytesRead;
+ _buffer ??= new byte[BufferSize];
+
+ while ((bytesRead = stream.Read(_buffer, 0, BufferSize)) > 0)
+ {
+ for (int i = 0; i < bytesRead; i++)
+ {
+ WriteObject(_buffer[i]);
+ }
+ }
+ }
+}
diff --git a/src/PSCompression/Commands/GetZipEntryCommand.cs b/src/PSCompression/Commands/GetZipEntryCommand.cs
index d4508be..a00b9aa 100644
--- a/src/PSCompression/Commands/GetZipEntryCommand.cs
+++ b/src/PSCompression/Commands/GetZipEntryCommand.cs
@@ -1,203 +1,71 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.IO.Compression;
-using System.Linq;
using System.Management.Automation;
-using PSCompression.Extensions;
-using PSCompression.Exceptions;
using System.IO;
+using PSCompression.Abstractions;
namespace PSCompression.Commands;
[Cmdlet(VerbsCommon.Get, "ZipEntry", DefaultParameterSetName = "Path")]
[OutputType(typeof(ZipEntryDirectory), typeof(ZipEntryFile))]
-[Alias("gezip")]
-public sealed class GetZipEntryCommand : CommandWithPathBase
+[Alias("zipge")]
+public sealed class GetZipEntryCommand : GetEntryCommandBase
{
- [Parameter(
- ParameterSetName = "Stream",
- Position = 0,
- Mandatory = true,
- ValueFromPipeline = true,
- ValueFromPipelineByPropertyName = true)]
- [Alias("RawContentStream")]
- public Stream? InputStream { get; set; }
+ internal override ArchiveType ArchiveType => ArchiveType.zip;
- private readonly List _output = [];
-
- private WildcardPattern[]? _includePatterns;
-
- private WildcardPattern[]? _excludePatterns;
-
- [Parameter]
- public ZipEntryType? Type { get; set; }
-
- [Parameter]
- [SupportsWildcards]
- public string[]? Include { get; set; }
-
- [Parameter]
- [SupportsWildcards]
- public string[]? Exclude { get; set; }
-
- protected override void BeginProcessing()
- {
- if (Exclude is null && Include is null)
- {
- return;
- }
-
- const WildcardOptions options = WildcardOptions.Compiled
- | WildcardOptions.CultureInvariant
- | WildcardOptions.IgnoreCase;
-
- if (Exclude is not null)
- {
- _excludePatterns = [.. Exclude.Select(e => new WildcardPattern(e, options))];
- }
-
- if (Include is not null)
- {
- _includePatterns = [.. Include.Select(e => new WildcardPattern(e, options))];
- }
- }
-
- protected override void ProcessRecord()
+ protected override IEnumerable GetEntriesFromFile(string path)
{
- IEnumerable entries;
- if (InputStream is not null)
+ List entries = [];
+ using (ZipArchive zip = ZipFile.OpenRead(path))
{
- ZipEntryBase CreateFromStream(ZipArchiveEntry entry, bool isDirectory) =>
- isDirectory
- ? new ZipEntryDirectory(entry, InputStream)
- : new ZipEntryFile(entry, InputStream);
-
- try
+ foreach (ZipArchiveEntry entry in zip.Entries)
{
- using (ZipArchive zip = new(InputStream, ZipArchiveMode.Read, true))
+ bool isDirectory = string.IsNullOrEmpty(entry.Name);
+
+ if (ShouldSkipEntry(isDirectory))
{
- entries = GetEntries(zip, CreateFromStream);
+ continue;
}
- WriteObject(entries, enumerateCollection: true);
- return;
- }
- catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
- {
- throw;
- }
- catch (InvalidDataException exception)
- {
- ThrowTerminatingError(exception.ToInvalidZipArchive());
- }
- catch (Exception exception)
- {
- WriteError(exception.ToOpenError("InputStream"));
- }
- }
-
- foreach (string path in EnumerateResolvedPaths())
- {
- ZipEntryBase CreateFromFile(ZipArchiveEntry entry, bool isDirectory) =>
- isDirectory
- ? new ZipEntryDirectory(entry, path)
- : new ZipEntryFile(entry, path);
-
- if (!path.IsArchive())
- {
- WriteError(
- ExceptionHelper.NotArchivePath(
- path,
- IsLiteral ? nameof(LiteralPath) : nameof(Path)));
-
- continue;
- }
-
- try
- {
- using (ZipArchive zip = ZipFile.OpenRead(path))
+ if (!ShouldInclude(entry.FullName) || ShouldExclude(entry.FullName))
{
- entries = GetEntries(zip, CreateFromFile);
+ continue;
}
- WriteObject(entries, enumerateCollection: true);
- }
- catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
- {
- throw;
- }
- catch (InvalidDataException exception)
- {
- ThrowTerminatingError(exception.ToInvalidZipArchive());
- }
- catch (Exception exception)
- {
- WriteError(exception.ToOpenError(path));
- }
- }
- }
-
- private IEnumerable GetEntries(
- ZipArchive zip,
- Func createMethod)
- {
- _output.Clear();
- foreach (ZipArchiveEntry entry in zip.Entries)
- {
- bool isDirectory = string.IsNullOrEmpty(entry.Name);
-
- if (ShouldSkipEntry(isDirectory))
- {
- continue;
- }
-
- if (!ShouldInclude(entry) || ShouldExclude(entry))
- {
- continue;
+ entries.Add(isDirectory
+ ? new ZipEntryDirectory(entry, path)
+ : new ZipEntryFile(entry, path));
}
-
- _output.Add(createMethod(entry, isDirectory));
}
- return _output.ZipEntrySort();
+ return [.. entries];
}
- private static bool MatchAny(
- ZipArchiveEntry entry,
- WildcardPattern[] patterns)
+ protected override IEnumerable GetEntriesFromStream(Stream stream)
{
- foreach (WildcardPattern pattern in patterns)
+ List entries = [];
+ using (ZipArchive zip = new(stream, ZipArchiveMode.Read, true))
{
- if (pattern.IsMatch(entry.FullName))
+ foreach (ZipArchiveEntry entry in zip.Entries)
{
- return true;
- }
- }
+ bool isDirectory = string.IsNullOrEmpty(entry.Name);
- return false;
- }
-
- private bool ShouldInclude(ZipArchiveEntry entry)
- {
- if (_includePatterns is null)
- {
- return true;
- }
+ if (ShouldSkipEntry(isDirectory))
+ {
+ continue;
+ }
- return MatchAny(entry, _includePatterns);
- }
+ if (!ShouldInclude(entry.FullName) || ShouldExclude(entry.FullName))
+ {
+ continue;
+ }
- private bool ShouldExclude(ZipArchiveEntry entry)
- {
- if (_excludePatterns is null)
- {
- return false;
+ entries.Add(isDirectory
+ ? new ZipEntryDirectory(entry, stream)
+ : new ZipEntryFile(entry, stream));
+ }
}
- return MatchAny(entry, _excludePatterns);
+ return [.. entries];
}
-
- private bool ShouldSkipEntry(bool isDirectory) =>
- isDirectory && Type is ZipEntryType.Archive
- || !isDirectory && Type is ZipEntryType.Directory;
}
diff --git a/src/PSCompression/Commands/GetZipEntryContentCommand.cs b/src/PSCompression/Commands/GetZipEntryContentCommand.cs
index a6030ff..29e530e 100644
--- a/src/PSCompression/Commands/GetZipEntryContentCommand.cs
+++ b/src/PSCompression/Commands/GetZipEntryContentCommand.cs
@@ -1,7 +1,6 @@
using System;
-using System.IO.Compression;
using System.Management.Automation;
-using System.Text;
+using PSCompression.Abstractions;
using PSCompression.Exceptions;
namespace PSCompression.Commands;
@@ -9,37 +8,18 @@ namespace PSCompression.Commands;
[Cmdlet(VerbsCommon.Get, "ZipEntryContent", DefaultParameterSetName = "Stream")]
[OutputType(typeof(string), ParameterSetName = ["Stream"])]
[OutputType(typeof(byte), ParameterSetName = ["Bytes"])]
-[Alias("gczip")]
-public sealed class GetZipEntryContentCommand : PSCmdlet, IDisposable
+[Alias("zipgec")]
+public sealed class GetZipEntryContentCommand : GetEntryContentCommandBase, IDisposable
{
private readonly ZipArchiveCache _cache = new();
- [Parameter(Mandatory = true, ValueFromPipeline = true)]
- public ZipEntryFile[] ZipEntry { get; set; } = null!;
-
- [Parameter(ParameterSetName = "Stream")]
- [ArgumentCompleter(typeof(EncodingCompleter))]
- [EncodingTransformation]
- [ValidateNotNullOrEmpty]
- public Encoding Encoding { get; set; } = new UTF8Encoding();
-
- [Parameter]
- public SwitchParameter Raw { get; set; }
-
- [Parameter(ParameterSetName = "Bytes")]
- public SwitchParameter AsByteStream { get; set; }
-
- [Parameter(ParameterSetName = "Bytes")]
- [ValidateNotNullOrEmpty]
- public int BufferSize { get; set; } = 128_000;
-
protected override void ProcessRecord()
{
- foreach (ZipEntryFile entry in ZipEntry)
+ foreach (ZipEntryFile entry in Entry)
{
try
{
- ZipContentReader reader = new(GetOrAdd(entry));
+ ZipContentReader reader = new(_cache.GetOrAdd(entry));
ReadEntry(entry, reader);
}
catch (Exception _) when (_ is PipelineStoppedException or FlowControlException)
@@ -55,33 +35,27 @@ protected override void ProcessRecord()
private void ReadEntry(ZipEntryFile entry, ZipContentReader reader)
{
- if (AsByteStream.IsPresent)
+ if (AsByteStream)
{
- if (Raw.IsPresent)
+ if (Raw)
{
- WriteObject(reader.ReadAllBytes(entry));
+ reader.ReadAllBytes(entry, this);
return;
}
- WriteObject(
- reader.StreamBytes(entry, BufferSize),
- enumerateCollection: true);
+ reader.StreamBytes(entry, BufferSize, this);
return;
}
if (Raw.IsPresent)
{
- WriteObject(reader.ReadToEnd(entry, Encoding));
+ reader.ReadToEnd(entry, Encoding, this);
return;
}
- WriteObject(
- reader.StreamLines(entry, Encoding),
- enumerateCollection: true);
+ reader.StreamLines(entry, Encoding, this);
}
- private ZipArchive GetOrAdd(ZipEntryFile entry) => _cache.GetOrAdd(entry);
-
public void Dispose()
{
_cache?.Dispose();
diff --git a/src/PSCompression/Commands/NewZipEntryCommand.cs b/src/PSCompression/Commands/NewZipEntryCommand.cs
index 68b16e0..3de6656 100644
--- a/src/PSCompression/Commands/NewZipEntryCommand.cs
+++ b/src/PSCompression/Commands/NewZipEntryCommand.cs
@@ -7,11 +7,13 @@
using System.Text;
using PSCompression.Extensions;
using PSCompression.Exceptions;
+using PSCompression.Abstractions;
namespace PSCompression.Commands;
[Cmdlet(VerbsCommon.New, "ZipEntry", DefaultParameterSetName = "Value")]
[OutputType(typeof(ZipEntryDirectory), typeof(ZipEntryFile))]
+[Alias("zipne")]
public sealed class NewZipEntryCommand : PSCmdlet, IDisposable
{
private readonly List _entries = [];
@@ -55,13 +57,7 @@ public string[]? EntryPath
protected override void BeginProcessing()
{
Destination = Destination.ResolvePath(this);
- if (!Destination.IsArchive())
- {
- ThrowTerminatingError(
- ExceptionHelper.NotArchivePath(
- Destination,
- nameof(Destination)));
- }
+ Destination.WriteErrorIfNotArchive(nameof(Destination), this, isTerminating: true);
try
{
@@ -78,10 +74,9 @@ protected override void BeginProcessing()
{
if (!Force.IsPresent)
{
- WriteError(
- DuplicatedEntryException
- .Create(entry, Destination)
- .ToDuplicatedEntryError());
+ WriteError(DuplicatedEntryException
+ .Create(entry, Destination)
+ .ToDuplicatedEntryError());
continue;
}
@@ -99,14 +94,7 @@ protected override void BeginProcessing()
Dbg.Assert(SourcePath is not null);
// Create Entries from file here
SourcePath = SourcePath.ResolvePath(this);
-
- if (!SourcePath.IsArchive())
- {
- ThrowTerminatingError(
- ExceptionHelper.NotArchivePath(
- SourcePath,
- nameof(SourcePath)));
- }
+ SourcePath.WriteErrorIfNotArchive(nameof(SourcePath), this, isTerminating: true);
using FileStream fileStream = File.Open(
path: SourcePath,
@@ -121,10 +109,9 @@ protected override void BeginProcessing()
{
if (!Force.IsPresent)
{
- WriteError(
- DuplicatedEntryException
- .Create(entry, Destination)
- .ToDuplicatedEntryError());
+ WriteError(DuplicatedEntryException
+ .Create(entry, Destination)
+ .ToDuplicatedEntryError());
continue;
}
@@ -200,10 +187,10 @@ protected override void EndProcessing()
}
}
- private IEnumerable GetResult()
+ private IEnumerable GetResult()
{
using ZipArchive zip = ZipFile.OpenRead(Destination);
- List _result = new(_entries.Count);
+ List _result = new(_entries.Count);
foreach (ZipArchiveEntry entry in _entries)
{
@@ -220,7 +207,7 @@ private IEnumerable GetResult()
Destination));
}
- return _result.ZipEntrySort();
+ return _result.ToEntrySort();
}
public void Dispose()
diff --git a/src/PSCompression/Commands/RemoveZipEntryCommand.cs b/src/PSCompression/Commands/RemoveZipEntryCommand.cs
index f952b1d..4caba19 100644
--- a/src/PSCompression/Commands/RemoveZipEntryCommand.cs
+++ b/src/PSCompression/Commands/RemoveZipEntryCommand.cs
@@ -1,12 +1,14 @@
using System;
using System.IO.Compression;
using System.Management.Automation;
+using PSCompression.Abstractions;
using PSCompression.Exceptions;
namespace PSCompression.Commands;
[Cmdlet(VerbsCommon.Remove, "ZipEntry", SupportsShouldProcess = true)]
[OutputType(typeof(void))]
+[Alias("ziprm")]
public sealed class RemoveZipEntryCommand : PSCmdlet, IDisposable
{
private readonly ZipArchiveCache _cache = new(ZipArchiveMode.Update);
diff --git a/src/PSCompression/Commands/RenameZipEntryCommand.cs b/src/PSCompression/Commands/RenameZipEntryCommand.cs
index 38683da..ef44eb2 100644
--- a/src/PSCompression/Commands/RenameZipEntryCommand.cs
+++ b/src/PSCompression/Commands/RenameZipEntryCommand.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO.Compression;
using System.Management.Automation;
+using PSCompression.Abstractions;
using PSCompression.Exceptions;
using PSCompression.Extensions;
@@ -9,6 +10,7 @@ namespace PSCompression.Commands;
[Cmdlet(VerbsCommon.Rename, "ZipEntry", SupportsShouldProcess = true)]
[OutputType(typeof(ZipEntryFile), typeof(ZipEntryDirectory))]
+[Alias("zipren")]
public sealed class RenameZipEntryCommand : PSCmdlet, IDisposable
{
private readonly ZipArchiveCache _zipArchiveCache = new(ZipArchiveMode.Update);
@@ -85,7 +87,7 @@ protected override void EndProcessing()
_zipEntryCache
.AddRange(_moveCache.GetPassThruMappings())
.GetEntries()
- .ZipEntrySort(),
+ .ToEntrySort(),
enumerateCollection: true);
}
diff --git a/src/PSCompression/Commands/SetZipEntryContentCommand.cs b/src/PSCompression/Commands/SetZipEntryContentCommand.cs
index ea9cc9f..5bc1bf7 100644
--- a/src/PSCompression/Commands/SetZipEntryContentCommand.cs
+++ b/src/PSCompression/Commands/SetZipEntryContentCommand.cs
@@ -7,6 +7,7 @@ namespace PSCompression.Commands;
[Cmdlet(VerbsCommon.Set, "ZipEntryContent", DefaultParameterSetName = "StringValue")]
[OutputType(typeof(ZipEntryFile))]
+[Alias("zipsc")]
public sealed class SetZipEntryContentCommand : PSCmdlet, IDisposable
{
private ZipContentWriter? _zipWriter;
diff --git a/src/PSCompression/Exceptions/ExceptionHelpers.cs b/src/PSCompression/Exceptions/ExceptionHelper.cs
similarity index 70%
rename from src/PSCompression/Exceptions/ExceptionHelpers.cs
rename to src/PSCompression/Exceptions/ExceptionHelper.cs
index 4e4a57a..8ab9059 100644
--- a/src/PSCompression/Exceptions/ExceptionHelpers.cs
+++ b/src/PSCompression/Exceptions/ExceptionHelper.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.IO.Compression;
using System.Management.Automation;
+using PSCompression.Abstractions;
using PSCompression.Extensions;
namespace PSCompression.Exceptions;
@@ -13,12 +14,32 @@ internal static class ExceptionHelper
private static readonly char[] s_InvalidPathChar = Path.GetInvalidPathChars();
- internal static ErrorRecord NotArchivePath(string path, string paramname) =>
- new(
- new ArgumentException(
- $"The specified path '{path}' does not exist or is a Directory.",
- paramname),
- "NotArchivePath", ErrorCategory.InvalidArgument, path);
+ internal static bool WriteErrorIfNotArchive(
+ this string path,
+ string paramname,
+ PSCmdlet cmdlet,
+ bool isTerminating = false)
+ {
+ if (File.Exists(path))
+ {
+ return false;
+ }
+
+ ArgumentException exception = new(
+ $"The specified path '{path}' does not exist or is a Directory.",
+ paramname);
+
+ ErrorRecord error = new(exception, "NotArchivePath", ErrorCategory.InvalidArgument, path);
+
+ if (isTerminating)
+ {
+ cmdlet.ThrowTerminatingError(error);
+ }
+
+ cmdlet.WriteError(error);
+ return true;
+ }
+
internal static ErrorRecord NotDirectoryPath(string path, string paramname) =>
new(
@@ -33,19 +54,16 @@ internal static ErrorRecord ToInvalidProviderError(this ProviderInfo provider, s
"NotFileSystemPath", ErrorCategory.InvalidArgument, path);
internal static ErrorRecord ToOpenError(this Exception exception, string path) =>
- new(exception, "ZipOpen", ErrorCategory.OpenError, path);
+ new(exception, "EntryOpen", ErrorCategory.OpenError, path);
internal static ErrorRecord ToResolvePathError(this Exception exception, string path) =>
new(exception, "ResolvePath", ErrorCategory.NotSpecified, path);
- internal static ErrorRecord ToExtractEntryError(this Exception exception, ZipEntryBase entry) =>
+ internal static ErrorRecord ToExtractEntryError(this Exception exception, object entry) =>
new(exception, "ExtractEntry", ErrorCategory.NotSpecified, entry);
- internal static ErrorRecord ToStreamOpenError(this Exception exception, ZipEntryBase entry) =>
- new(exception, "StreamOpen", ErrorCategory.NotSpecified, entry);
-
- internal static ErrorRecord ToStreamOpenError(this Exception exception, string path) =>
- new(exception, "StreamOpen", ErrorCategory.NotSpecified, path);
+ internal static ErrorRecord ToStreamOpenError(this Exception exception, object item) =>
+ new(exception, "StreamOpen", ErrorCategory.NotSpecified, item);
internal static ErrorRecord ToWriteError(this Exception exception, object? item) =>
new(exception, "WriteError", ErrorCategory.WriteError, item);
@@ -62,16 +80,25 @@ internal static ErrorRecord ToEntryNotFoundError(this EntryNotFoundException exc
internal static ErrorRecord ToEnumerationError(this Exception exception, object item) =>
new(exception, "EnumerationError", ErrorCategory.ReadError, item);
- internal static ErrorRecord ToInvalidZipArchive(this InvalidDataException exception) =>
- new(
- new InvalidDataException(
- "Specified path or stream is not a valid zip archive, " +
- "might be compressed using an unsupported method, " +
- "or could be corrupted.",
- exception),
- "InvalidZipArchive",
- ErrorCategory.InvalidData,
- null);
+ internal static ErrorRecord ToInvalidArchive(
+ this Exception exception,
+ ArchiveType type,
+ bool isStream = false)
+ {
+ string basemsg = $"Specified path or stream is not a valid {type} archive, " +
+ "might be compressed using an unsupported method, " +
+ "or could be corrupted.";
+
+ if (type is ArchiveType.tar && isStream)
+ {
+ basemsg += " When reading a tar archive from a stream, " +
+ "use the -Algorithm parameter to specify the compression type.";
+ }
+
+ return new ErrorRecord(
+ new InvalidDataException(basemsg, exception),
+ "InvalidArchive", ErrorCategory.InvalidData, null);
+ }
internal static void ThrowIfNotFound(
diff --git a/src/PSCompression/Extensions/CompressionExtensions.cs b/src/PSCompression/Extensions/CompressionExtensions.cs
new file mode 100644
index 0000000..a1fe7ca
--- /dev/null
+++ b/src/PSCompression/Extensions/CompressionExtensions.cs
@@ -0,0 +1,208 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.IO.Compression;
+using System.Management.Automation;
+using System.Text.RegularExpressions;
+using BrotliSharpLib;
+using ICSharpCode.SharpZipLib.BZip2;
+using ICSharpCode.SharpZipLib.Tar;
+using SharpCompress.Compressors.LZMA;
+using ZstdSharp;
+using SharpCompressors = SharpCompress.Compressors;
+
+namespace PSCompression.Extensions;
+
+internal static class CompressionExtensions
+{
+ private static readonly Regex s_reGetDirName = new(
+ @"[^/]+(?=/$)",
+ RegexOptions.Compiled | RegexOptions.RightToLeft);
+
+ private const string _directorySeparator = "/";
+
+ internal static string RelativeTo(this DirectoryInfo directory, int length) =>
+ (directory.FullName.Substring(length) + _directorySeparator).NormalizeEntryPath();
+
+ internal static string RelativeTo(this FileInfo file, int length) =>
+ file.FullName.Substring(length).NormalizeFileEntryPath();
+
+ internal static ZipArchiveEntry CreateEntryFromFile(
+ this ZipArchive zip,
+ string entry,
+ FileStream fileStream,
+ CompressionLevel compressionLevel)
+ {
+ if (entry.EndsWith("/") || entry.EndsWith("\\"))
+ {
+ return zip.CreateEntry(entry);
+ }
+
+ fileStream.Seek(0, SeekOrigin.Begin);
+ ZipArchiveEntry newentry = zip.CreateEntry(entry, compressionLevel);
+
+ using (Stream stream = newentry.Open())
+ {
+ fileStream.CopyTo(stream);
+ }
+
+ return newentry;
+ }
+
+ internal static bool TryGetEntry(
+ this ZipArchive zip,
+ string path,
+ [NotNullWhen(true)] out ZipArchiveEntry? entry)
+ => (entry = zip.GetEntry(path)) is not null;
+
+ internal static string ChangeName(
+ this ZipEntryFile file,
+ string newname)
+ {
+ string normalized = file.RelativePath.NormalizePath();
+
+ if (normalized.IndexOf(_directorySeparator) == -1)
+ {
+ return newname;
+ }
+
+ return string.Join(
+ _directorySeparator,
+ normalized.Substring(0, normalized.Length - file.Name.Length - 1),
+ newname);
+ }
+
+ internal static string ChangeName(
+ this ZipEntryDirectory directory,
+ string newname)
+ => s_reGetDirName.Replace(
+ directory.RelativePath.NormalizePath(),
+ newname);
+
+ internal static string GetDirectoryName(this ZipArchiveEntry entry)
+ => s_reGetDirName.Match(entry.FullName).Value;
+
+ internal static string GetDirectoryName(this TarEntry entry)
+ => s_reGetDirName.Match(entry.Name).Value;
+
+ internal static void WriteAllTextToPipeline(this StreamReader reader, PSCmdlet cmdlet)
+ => cmdlet.WriteObject(reader.ReadToEnd());
+
+ internal static void WriteLinesToPipeline(this StreamReader reader, PSCmdlet cmdlet)
+ {
+ string line;
+ while ((line = reader.ReadLine()) is not null)
+ {
+ cmdlet.WriteObject(line);
+ }
+ }
+
+ internal static void WriteLines(this StreamWriter writer, string[] lines)
+ {
+ foreach (string line in lines)
+ {
+ writer.WriteLine(line);
+ }
+ }
+
+ internal static void WriteContent(this StreamWriter writer, string[] lines)
+ {
+ foreach (string line in lines)
+ {
+ writer.Write(line);
+ }
+ }
+
+ internal static BrotliStream AsBrotliCompressedStream(
+ this Stream stream,
+ CompressionLevel compressionLevel)
+ {
+ BrotliStream brotli = new(stream, CompressionMode.Compress);
+ brotli.SetQuality(compressionLevel switch
+ {
+ CompressionLevel.NoCompression => 0,
+ CompressionLevel.Fastest => 1,
+ _ => 11
+ });
+
+ return brotli;
+ }
+
+ internal static BZip2OutputStream AsBZip2CompressedStream(
+ this Stream stream,
+ CompressionLevel compressionLevel)
+ {
+ int blockSize = compressionLevel switch
+ {
+ CompressionLevel.NoCompression => 1,
+ CompressionLevel.Fastest => 2,
+ _ => 9
+ };
+
+ return new BZip2OutputStream(stream, blockSize);
+ }
+
+ internal static CompressionStream AsZstCompressedStream(
+ this Stream stream,
+ CompressionLevel compressionLevel)
+ {
+ int level = compressionLevel switch
+ {
+ CompressionLevel.NoCompression => 1,
+ CompressionLevel.Fastest => 3,
+ _ => 19
+ };
+
+ return new CompressionStream(stream, level);
+ }
+
+ internal static LZipStream AsLzCompressedStream(this Stream outputStream) =>
+ new(outputStream, SharpCompressors.CompressionMode.Compress);
+
+ internal static Stream ToCompressedStream(
+ this Algorithm algorithm,
+ Stream stream,
+ CompressionLevel compressionLevel)
+ => algorithm switch
+ {
+ Algorithm.gz => new GZipStream(stream, compressionLevel),
+ Algorithm.zst => stream.AsZstCompressedStream(compressionLevel),
+ Algorithm.lz => stream.AsLzCompressedStream(),
+ Algorithm.bz2 => stream.AsBZip2CompressedStream(compressionLevel),
+ _ => stream
+ };
+
+ internal static Stream FromCompressedStream(
+ this Algorithm algorithm,
+ Stream stream)
+ => algorithm switch
+ {
+ Algorithm.gz => new GZipStream(stream, CompressionMode.Decompress),
+ Algorithm.zst => new DecompressionStream(stream),
+ Algorithm.lz => new LZipStream(stream, SharpCompressors.CompressionMode.Decompress),
+ Algorithm.bz2 => new BZip2InputStream(stream),
+ _ => stream
+ };
+
+ internal static void CreateTarEntry(
+ this TarOutputStream stream,
+ string entryName,
+ DateTime modTime,
+ long size)
+ {
+ TarEntry entry = TarEntry.CreateTarEntry(entryName);
+ entry.TarHeader.Size = size;
+ entry.TarHeader.ModTime = modTime;
+ stream.PutNextEntry(entry);
+ }
+
+ internal static IEnumerable EnumerateEntries(this TarInputStream tar)
+ {
+ TarEntry? entry;
+ while ((entry = tar.GetNextEntry()) is not null)
+ {
+ yield return entry;
+ }
+ }
+}
diff --git a/src/PSCompression/Extensions/PathExtensions.cs b/src/PSCompression/Extensions/PathExtensions.cs
index 1809627..0a2e5d6 100644
--- a/src/PSCompression/Extensions/PathExtensions.cs
+++ b/src/PSCompression/Extensions/PathExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Management.Automation;
+using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Microsoft.PowerShell.Commands;
using PSCompression.Exceptions;
@@ -13,12 +14,13 @@ public static class PathExtensions
@"(?:^[a-z]:)?[\\/]+|(?
+ path.EndsWith("/") || path.EndsWith("\\")
+ ? NormalizeEntryPath(path)
+ : NormalizeFileEntryPath(path);
+
internal static string ResolvePath(this string path, PSCmdlet cmdlet)
{
string resolved = cmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
@@ -52,10 +54,6 @@ internal static bool Validate(
return false;
}
- internal static bool IsArchive(this string path) => File.Exists(path);
-
- internal static string GetParent(this string path) => Path.GetDirectoryName(path);
-
internal static string AddExtensionIfMissing(this string path, string extension)
{
if (!path.EndsWith(extension, StringComparison.InvariantCultureIgnoreCase))
@@ -72,11 +70,32 @@ internal static string NormalizeEntryPath(this string path) =>
internal static string NormalizeFileEntryPath(this string path) =>
NormalizeEntryPath(path).TrimEnd('/');
- internal static bool IsDirectoryPath(this string path) =>
- s_reEntryDir.IsMatch(path);
+ internal static void Create(this DirectoryInfo dir, bool force)
+ {
+ if (force || !dir.Exists)
+ {
+ dir.Create();
+ return;
+ }
- public static string NormalizePath(this string path) =>
- s_reEntryDir.IsMatch(path)
- ? NormalizeEntryPath(path)
- : NormalizeFileEntryPath(path);
+ throw new IOException($"The directory '{dir.FullName}' already exists.");
+ }
+
+ internal static PSObject AppendPSProperties(this FileSystemInfo info)
+ {
+ string parent = info is DirectoryInfo dir
+ ? dir.Parent.FullName
+ : Unsafe.As(info).DirectoryName;
+
+ return info.AppendPSProperties(parent);
+ }
+
+ internal static PSObject AppendPSProperties(this FileSystemInfo info, string parent)
+ {
+ const string provider = @"Microsoft.PowerShell.Core\FileSystem::";
+ PSObject pso = PSObject.AsPSObject(info);
+ pso.Properties.Add(new PSNoteProperty("PSPath", $"{provider}{info.FullName}"));
+ pso.Properties.Add(new PSNoteProperty("PSParentPath", $"{provider}{parent}"));
+ return pso;
+ }
}
diff --git a/src/PSCompression/Extensions/ZipEntryExtensions.cs b/src/PSCompression/Extensions/ZipEntryExtensions.cs
deleted file mode 100644
index 3cb1be5..0000000
--- a/src/PSCompression/Extensions/ZipEntryExtensions.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.IO.Compression;
-using System.Text.RegularExpressions;
-
-namespace PSCompression.Extensions;
-
-internal static class ZipEntryExtensions
-{
- private static readonly Regex s_reGetDirName = new(
- @"[^/]+(?=/$)",
- RegexOptions.Compiled | RegexOptions.RightToLeft);
-
- private const string _directorySeparator = "/";
-
- internal static string RelativeTo(this DirectoryInfo directory, int length) =>
- (directory.FullName.Substring(length) + _directorySeparator).NormalizeEntryPath();
-
- internal static string RelativeTo(this FileInfo directory, int length) =>
- directory.FullName.Substring(length).NormalizeFileEntryPath();
-
- internal static ZipArchiveEntry CreateEntryFromFile(
- this ZipArchive zip,
- string entry,
- FileStream fileStream,
- CompressionLevel compressionLevel)
- {
- if (entry.IsDirectoryPath())
- {
- return zip.CreateEntry(entry);
- }
-
- fileStream.Seek(0, SeekOrigin.Begin);
- ZipArchiveEntry newentry = zip.CreateEntry(entry, compressionLevel);
-
- using (Stream stream = newentry.Open())
- {
- fileStream.CopyTo(stream);
- }
-
- return newentry;
- }
-
- internal static bool TryGetEntry(
- this ZipArchive zip,
- string path,
- [NotNullWhen(true)] out ZipArchiveEntry? entry) =>
- (entry = zip.GetEntry(path)) is not null;
-
- internal static (string, bool) ExtractTo(
- this ZipEntryBase entryBase,
- ZipArchive zip,
- string destination,
- bool overwrite)
- {
- destination = Path.GetFullPath(
- Path.Combine(destination, entryBase.RelativePath));
-
- if (entryBase.Type is ZipEntryType.Directory)
- {
- Directory.CreateDirectory(destination);
- return (destination, false);
- }
-
- string parent = Path.GetDirectoryName(destination);
-
- if (!Directory.Exists(parent))
- {
- Directory.CreateDirectory(parent);
- }
-
- ZipArchiveEntry entry = zip.GetEntry(entryBase.RelativePath);
- entry.ExtractToFile(destination, overwrite);
- return (destination, true);
- }
-
- internal static string ChangeName(
- this ZipEntryFile file,
- string newname)
- {
- string normalized = file.RelativePath.NormalizePath();
-
- if (normalized.IndexOf(_directorySeparator) == -1)
- {
- return newname;
- }
-
- return string.Join(
- _directorySeparator,
- normalized.Substring(0, normalized.Length - file.Name.Length - 1),
- newname);
- }
-
- internal static string ChangeName(
- this ZipEntryDirectory directory,
- string newname) =>
- s_reGetDirName.Replace(
- directory.RelativePath.NormalizePath(),
- newname);
-
- internal static string ChangePath(
- this ZipArchiveEntry entry,
- string oldPath,
- string newPath) =>
- string.Concat(newPath, entry.FullName.Remove(0, oldPath.Length));
-
- internal static string GetDirectoryName(this ZipArchiveEntry entry) =>
- s_reGetDirName.Match(entry.FullName).Value;
-}
diff --git a/src/PSCompression/GzipReaderOps.cs b/src/PSCompression/GzipReaderOps.cs
deleted file mode 100644
index 51a053b..0000000
--- a/src/PSCompression/GzipReaderOps.cs
+++ /dev/null
@@ -1,150 +0,0 @@
-using System.IO;
-using System.IO.Compression;
-using System.Management.Automation;
-using System.Runtime.Serialization;
-using System.Text;
-
-namespace PSCompression;
-
-internal static class GzipReaderOps
-{
- private static readonly byte[] gzipPreamble = [0x1f, 0x8b, 0x08];
-
- internal static void CopyTo(
- string path,
- bool isCoreCLR,
- FileStream destination)
- {
- if (isCoreCLR)
- {
- using FileStream fs = File.OpenRead(path);
- using GZipStream gzip = new(fs, CompressionMode.Decompress);
- gzip.CopyTo(destination);
- return;
- }
-
- using MemoryStream mem = GetFrameworkStream(path);
- mem.CopyTo(destination);
- }
-
- internal static void GetContent(
- string path,
- bool isCoreCLR,
- bool raw,
- Encoding encoding,
- PSCmdlet cmdlet)
- {
- if (isCoreCLR)
- {
- using FileStream fs = File.OpenRead(path);
- using GZipStream gzip = new(fs, CompressionMode.Decompress);
-
- if (raw)
- {
- ReadToEnd(gzip, encoding, cmdlet);
- return;
- }
-
- ReadLines(gzip, encoding, cmdlet);
- return;
- }
-
- using MemoryStream stream = GetFrameworkStream(path);
-
- if (raw)
- {
- ReadToEnd(stream, encoding, cmdlet);
- return;
- }
-
- ReadLines(stream, encoding, cmdlet);
- }
-
- private static void ReadLines(
- Stream stream,
- Encoding encoding,
- PSCmdlet cmdlet)
- {
- using StreamReader reader = new(stream, encoding);
-
- while (!reader.EndOfStream)
- {
- cmdlet.WriteObject(reader.ReadLine());
- }
- }
-
- private static void ReadToEnd(
- Stream stream,
- Encoding encoding,
- PSCmdlet cmdlet)
- {
- using StreamReader reader = new(stream, encoding);
- cmdlet.WriteObject(reader.ReadToEnd());
- }
-
- // this stuff is to make this work reading appended gzip streams in .net framework
- // i hate it :(
- private static MemoryStream GetFrameworkStream(string path)
- {
- int marker = 0;
- int b;
- using FileStream fs = File.OpenRead(path);
-
- byte[] preamble = new byte[3];
- fs.Read(preamble, 0, 3);
-
- for (int i = 0; i < 3; i++)
- {
- if(preamble[i] != gzipPreamble[i])
- {
- throw new InvalidDataContractException(
- "The archive entry was compressed using an unsupported compression method.");
- }
- }
-
- fs.Seek(0, SeekOrigin.Begin);
-
- MemoryStream outmem = new();
-
- while ((b = fs.ReadByte()) != -1)
- {
- if (marker == 0 && (byte)b == gzipPreamble[marker])
- {
- marker++;
- continue;
- }
-
- if (marker == 1)
- {
- if ((byte)b == gzipPreamble[marker])
- {
- marker++;
- continue;
- }
-
- marker = 0;
- }
-
- if (marker == 2)
- {
- if ((byte)b == gzipPreamble[marker])
- {
- CopyTo(path, outmem, fs.Position - 3);
- }
-
- marker = 0;
- }
- }
-
- outmem.Seek(0, SeekOrigin.Begin);
- return outmem;
- }
-
- private static void CopyTo(string path, MemoryStream outmem, long pos)
- {
- using FileStream substream = File.OpenRead(path);
- substream.Seek(pos, SeekOrigin.Begin);
- using GZipStream gzip = new(substream, CompressionMode.Decompress);
- gzip.CopyTo(outmem);
- }
-}
diff --git a/src/PSCompression/OnModuleImportAndRemove.cs b/src/PSCompression/OnModuleImportAndRemove.cs
new file mode 100644
index 0000000..e100d8b
--- /dev/null
+++ b/src/PSCompression/OnModuleImportAndRemove.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Management.Automation;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+///
+/// OnModuleImportAndRemove is a class that implements the IModuleAssemblyInitializer and IModuleAssemblyCleanup interfaces.
+/// This class is used to handle the assembly resolve event when the module is imported and removed.
+///
+[ExcludeFromCodeCoverage]
+public class OnModuleImportAndRemove : IModuleAssemblyInitializer, IModuleAssemblyCleanup
+{
+ ///
+ /// OnImport is called when the module is imported.
+ ///
+ public void OnImport()
+ {
+ if (IsNetFramework())
+ {
+ AppDomain.CurrentDomain.AssemblyResolve += MyResolveEventHandler;
+ }
+ }
+
+ ///
+ /// OnRemove is called when the module is removed.
+ ///
+ ///
+ public void OnRemove(PSModuleInfo module)
+ {
+ if (IsNetFramework())
+ {
+ AppDomain.CurrentDomain.AssemblyResolve -= MyResolveEventHandler;
+ }
+ }
+
+ ///
+ /// MyResolveEventHandler is a method that handles the AssemblyResolve event.
+ ///
+ ///
+ ///
+ ///
+ private static Assembly? MyResolveEventHandler(object? sender, ResolveEventArgs args)
+ {
+ string libDirectory = Path.GetDirectoryName(typeof(OnModuleImportAndRemove).Assembly.Location);
+ List directoriesToSearch = [];
+
+ if (!string.IsNullOrEmpty(libDirectory))
+ {
+ directoriesToSearch.Add(libDirectory);
+ if (Directory.Exists(libDirectory))
+ {
+ IEnumerable dirs = Directory.EnumerateDirectories(
+ libDirectory, "*", SearchOption.AllDirectories);
+
+ directoriesToSearch.AddRange(dirs);
+ }
+ }
+
+ string requestedAssemblyName = new AssemblyName(args.Name).Name + ".dll";
+
+ foreach (string directory in directoriesToSearch)
+ {
+ string assemblyPath = Path.Combine(directory, requestedAssemblyName);
+
+ if (File.Exists(assemblyPath))
+ {
+ try
+ {
+ return Assembly.LoadFrom(assemblyPath);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"Failed to load assembly from {assemblyPath}: {ex.Message}");
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Determine if the current runtime is .NET Framework
+ ///
+ ///
+ private bool IsNetFramework() => RuntimeInformation.FrameworkDescription
+ .StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase);
+}
diff --git a/src/PSCompression/PSCompression.csproj b/src/PSCompression/PSCompression.csproj
index e0fe9fd..4a12f39 100644
--- a/src/PSCompression/PSCompression.csproj
+++ b/src/PSCompression/PSCompression.csproj
@@ -9,6 +9,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/PSCompression/PSVersionHelper.cs b/src/PSCompression/PSVersionHelper.cs
deleted file mode 100644
index d6ad5c0..0000000
--- a/src/PSCompression/PSVersionHelper.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System;
-using System.Management.Automation;
-using System.Reflection;
-
-namespace PSCompression;
-
-internal static class PSVersionHelper
-{
- private static bool? _isCore;
-
- internal static bool IsCoreCLR => _isCore ??= IsCore();
-
- private static bool IsCore()
- {
- PropertyInfo property = typeof(PowerShell)
- .Assembly.GetType("System.Management.Automation.PSVersionInfo")
- .GetProperty(
- "PSVersion",
- BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
-
- return (Version)property.GetValue(property) is not { Major: 5, Minor: 1 };
- }
-}
diff --git a/src/PSCompression/Records.cs b/src/PSCompression/Records.cs
index 722c402..c2abbf6 100644
--- a/src/PSCompression/Records.cs
+++ b/src/PSCompression/Records.cs
@@ -1,5 +1,7 @@
+using PSCompression.Abstractions;
+
namespace PSCompression;
internal record struct EntryWithPath(ZipEntryBase ZipEntry, string Path);
-internal record struct PathWithType(string Path, ZipEntryType EntryType);
+internal record struct PathWithType(string Path, EntryType EntryType);
diff --git a/src/PSCompression/SortingOps.cs b/src/PSCompression/SortingOps.cs
index 127839a..b275207 100644
--- a/src/PSCompression/SortingOps.cs
+++ b/src/PSCompression/SortingOps.cs
@@ -1,27 +1,28 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using PSCompression.Abstractions;
using PSCompression.Extensions;
namespace PSCompression;
internal static class SortingOps
{
- private static string SortByParent(ZipEntryBase entry) =>
- Path.GetDirectoryName(entry.RelativePath)
- .NormalizeEntryPath();
+ private static string SortByParent(EntryBase entry) =>
+ Path.GetDirectoryName(entry.RelativePath).NormalizeEntryPath();
- private static int SortByLength(ZipEntryBase entry) =>
+ private static int SortByLength(EntryBase entry) =>
entry.RelativePath.Count(e => e == '/');
- private static string SortByName(ZipEntryBase entry) =>
+ private static string SortByName(EntryBase entry) =>
entry.Name;
- private static ZipEntryType SortByType(ZipEntryBase entry) =>
+ private static EntryType SortByType(EntryBase entry) =>
entry.Type;
- internal static IEnumerable ZipEntrySort(
- this IEnumerable zip) => zip
+ internal static IEnumerable ToEntrySort(
+ this IEnumerable entryCollection)
+ => entryCollection
.OrderBy(SortByParent)
.ThenBy(SortByType)
.ThenBy(SortByLength)
diff --git a/src/PSCompression/TarEntryDirectory.cs b/src/PSCompression/TarEntryDirectory.cs
new file mode 100644
index 0000000..986a86a
--- /dev/null
+++ b/src/PSCompression/TarEntryDirectory.cs
@@ -0,0 +1,25 @@
+using System.IO;
+using ICSharpCode.SharpZipLib.Tar;
+using PSCompression.Abstractions;
+using PSCompression.Extensions;
+
+namespace PSCompression;
+
+public sealed class TarEntryDirectory : TarEntryBase
+{
+ internal TarEntryDirectory(TarEntry entry, string source)
+ : base(entry, source)
+ {
+ Name = entry.GetDirectoryName();
+ }
+
+ internal TarEntryDirectory(TarEntry entry, Stream? stream)
+ : base(entry, stream)
+ {
+ Name = entry.GetDirectoryName();
+ }
+
+ public override EntryType Type => EntryType.Directory;
+
+ protected override string GetFormatDirectoryPath() => $"/{RelativePath.NormalizeEntryPath()}";
+}
diff --git a/src/PSCompression/TarEntryFile.cs b/src/PSCompression/TarEntryFile.cs
new file mode 100644
index 0000000..10773f7
--- /dev/null
+++ b/src/PSCompression/TarEntryFile.cs
@@ -0,0 +1,71 @@
+using System.IO;
+using System.Linq;
+using System.Text;
+using ICSharpCode.SharpZipLib.Tar;
+using PSCompression.Abstractions;
+using PSCompression.Extensions;
+
+namespace PSCompression;
+
+public sealed class TarEntryFile : TarEntryBase
+{
+ private readonly Algorithm _algorithm;
+
+ public string BaseName => Path.GetFileNameWithoutExtension(Name);
+
+ public string Extension => Path.GetExtension(RelativePath);
+
+ public override EntryType Type => EntryType.Archive;
+
+ internal TarEntryFile(TarEntry entry, string source, Algorithm algorithm)
+ : base(entry, source)
+ {
+ _algorithm = algorithm;
+ }
+
+ internal TarEntryFile(TarEntry entry, Stream? stream, Algorithm algorithm)
+ : base(entry, stream)
+ {
+ _algorithm = algorithm;
+ }
+
+ protected override string GetFormatDirectoryPath() =>
+ $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}";
+
+ internal bool GetContentStream(Stream destination)
+ {
+ Stream? sourceStream = null;
+ Stream? decompressedStream = null;
+ TarInputStream? tar = null;
+
+ try
+ {
+ sourceStream = _stream ?? File.OpenRead(Source);
+ sourceStream.Seek(0, SeekOrigin.Begin);
+ decompressedStream = _algorithm.FromCompressedStream(sourceStream);
+ tar = new(decompressedStream, Encoding.UTF8);
+
+ TarEntry? entry = tar
+ .EnumerateEntries()
+ .FirstOrDefault(e => e.Name == RelativePath);
+
+ if (entry is null or { Size: 0 })
+ {
+ return false;
+ }
+
+ tar.CopyTo(destination, (int)entry.Size);
+ destination.Seek(0, SeekOrigin.Begin);
+ return true;
+ }
+ finally
+ {
+ if (!FromStream)
+ {
+ tar?.Dispose();
+ decompressedStream?.Dispose();
+ sourceStream?.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/PSCompression/ZLibStream.cs b/src/PSCompression/ZLibStream.cs
new file mode 100644
index 0000000..968e8d4
--- /dev/null
+++ b/src/PSCompression/ZLibStream.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.IO.Compression;
+
+namespace PSCompression;
+
+// ain't nobody got time for that
+[ExcludeFromCodeCoverage]
+internal sealed class ZlibStream : Stream
+{
+ private readonly Stream _outputStream;
+
+ private readonly DeflateStream _deflateStream;
+
+ private readonly MemoryStream _uncompressedBuffer;
+
+ private bool _isDisposed;
+
+ internal ZlibStream(Stream outputStream, CompressionLevel compressionLevel)
+ {
+ _outputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream));
+ _uncompressedBuffer = new MemoryStream();
+
+ // Write zlib header (0x78 0x9C for default compatibility)
+ _outputStream.WriteByte(0x78);
+ _outputStream.WriteByte(0x9C);
+ _deflateStream = new DeflateStream(outputStream, compressionLevel, true);
+ }
+
+ public override bool CanRead => false;
+
+ public override bool CanSeek => false;
+
+ public override bool CanWrite => true;
+
+ public override long Length => throw new NotSupportedException();
+
+ public override long Position
+ {
+ get => throw new NotSupportedException();
+ set => throw new NotSupportedException();
+ }
+
+ public override void Flush()
+ {
+ _deflateStream.Flush();
+ _outputStream.Flush();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count) =>
+ throw new NotSupportedException("Reading is not supported.");
+
+ public override long Seek(long offset, SeekOrigin origin) =>
+ throw new NotSupportedException("Seeking is not supported.");
+
+ public override void SetLength(long value) =>
+ throw new NotSupportedException("Setting length is not supported.");
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ _uncompressedBuffer.Write(buffer, offset, count);
+ _deflateStream.Write(buffer, offset, count);
+ }
+
+ private uint ComputeAdler32()
+ {
+ const uint MOD_ADLER = 65521;
+ uint a = 1, b = 0;
+ byte[] data = _uncompressedBuffer.ToArray();
+
+ foreach (byte byt in data)
+ {
+ a = (a + byt) % MOD_ADLER;
+ b = (b + a) % MOD_ADLER;
+ }
+
+ return (b << 16) | a;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ _deflateStream.Dispose();
+
+ uint adler32 = ComputeAdler32();
+ byte[] checksum = BitConverter.GetBytes(adler32);
+ if (BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(checksum);
+ }
+
+ _outputStream.Write(checksum, 0, checksum.Length);
+ _uncompressedBuffer.Dispose();
+ }
+
+ _isDisposed = true;
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/PSCompression/ZipArchiveCache.cs b/src/PSCompression/ZipArchiveCache.cs
index 11ab3cf..4cfd7bb 100644
--- a/src/PSCompression/ZipArchiveCache.cs
+++ b/src/PSCompression/ZipArchiveCache.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
+using PSCompression.Abstractions;
namespace PSCompression;
diff --git a/src/PSCompression/ZipContentReader.cs b/src/PSCompression/ZipContentReader.cs
index 1dff6e2..500594a 100644
--- a/src/PSCompression/ZipContentReader.cs
+++ b/src/PSCompression/ZipContentReader.cs
@@ -1,53 +1,61 @@
-using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
+using System.Management.Automation;
using System.Text;
+using PSCompression.Abstractions;
+using PSCompression.Extensions;
namespace PSCompression;
internal sealed class ZipContentReader : ZipContentOpsBase
{
- internal ZipContentReader(ZipArchive zip) : base(zip) { }
+ internal ZipContentReader(ZipArchive zip) : base(zip)
+ { }
- internal IEnumerable StreamBytes(ZipEntryFile entry, int bufferSize)
+ internal void StreamBytes(
+ ZipEntryFile entry,
+ int bufferSize,
+ PSCmdlet cmdlet)
{
+ int bytes;
using Stream entryStream = entry.Open(_zip);
_buffer ??= new byte[bufferSize];
- int bytes;
while ((bytes = entryStream.Read(_buffer, 0, bufferSize)) > 0)
{
for (int i = 0; i < bytes; i++)
{
- yield return _buffer[i];
+ cmdlet.WriteObject(_buffer[i]);
}
}
}
- internal byte[] ReadAllBytes(ZipEntryFile entry)
+ internal void ReadAllBytes(ZipEntryFile entry, PSCmdlet cmdlet)
{
using Stream entryStream = entry.Open(_zip);
using MemoryStream mem = new();
entryStream.CopyTo(mem);
- return mem.ToArray();
+ cmdlet.WriteObject(mem.ToArray());
}
- internal IEnumerable StreamLines(ZipEntryFile entry, Encoding encoding)
+ internal void StreamLines(
+ ZipEntryFile entry,
+ Encoding encoding,
+ PSCmdlet cmdlet)
{
using Stream entryStream = entry.Open(_zip);
using StreamReader reader = new(entryStream, encoding);
-
- while (!reader.EndOfStream)
- {
- yield return reader.ReadLine();
- }
+ reader.WriteLinesToPipeline(cmdlet);
}
- internal string ReadToEnd(ZipEntryFile entry, Encoding encoding)
+ internal void ReadToEnd(
+ ZipEntryFile entry,
+ Encoding encoding,
+ PSCmdlet cmdlet)
{
using Stream entryStream = entry.Open(_zip);
using StreamReader reader = new(entryStream, encoding);
- return reader.ReadToEnd();
+ reader.WriteAllTextToPipeline(cmdlet);
}
}
diff --git a/src/PSCompression/ZipContentWriter.cs b/src/PSCompression/ZipContentWriter.cs
index 6945e20..9a418f7 100644
--- a/src/PSCompression/ZipContentWriter.cs
+++ b/src/PSCompression/ZipContentWriter.cs
@@ -1,6 +1,7 @@
using System.IO;
using System.IO.Compression;
using System.Text;
+using PSCompression.Abstractions;
namespace PSCompression;
@@ -12,8 +13,6 @@ internal sealed class ZipContentWriter : ZipContentOpsBase
private readonly Stream _stream;
- private bool _disposed;
-
internal ZipContentWriter(ZipEntryFile entry, bool append, int bufferSize)
: base(entry.OpenWrite())
{
@@ -122,7 +121,6 @@ protected override void Dispose(bool disposing)
{
_writer?.Dispose();
_stream.Dispose();
- _disposed = true;
base.Dispose(disposing);
}
}
diff --git a/src/PSCompression/ZipEntryCache.cs b/src/PSCompression/ZipEntryCache.cs
index e8c3a67..c167cf3 100644
--- a/src/PSCompression/ZipEntryCache.cs
+++ b/src/PSCompression/ZipEntryCache.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO.Compression;
+using PSCompression.Abstractions;
namespace PSCompression;
@@ -38,9 +39,9 @@ internal IEnumerable GetEntries()
foreach (var entry in _cache)
{
using ZipArchive zip = ZipFile.OpenRead(entry.Key);
- foreach ((string path, ZipEntryType type) in entry.Value)
+ foreach ((string path, EntryType type) in entry.Value)
{
- if (type is ZipEntryType.Archive)
+ if (type is EntryType.Archive)
{
yield return new ZipEntryFile(zip.GetEntry(path), entry.Key);
continue;
diff --git a/src/PSCompression/ZipEntryDirectory.cs b/src/PSCompression/ZipEntryDirectory.cs
index 86ca69f..d70b21d 100644
--- a/src/PSCompression/ZipEntryDirectory.cs
+++ b/src/PSCompression/ZipEntryDirectory.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.IO.Compression;
using System.Linq;
+using PSCompression.Abstractions;
using PSCompression.Extensions;
namespace PSCompression;
@@ -11,12 +12,7 @@ public sealed class ZipEntryDirectory : ZipEntryBase
{
private const StringComparison _comparer = StringComparison.InvariantCultureIgnoreCase;
- internal override string FormatDirectoryPath
- {
- get => _formatDirectoryPath ??= $"/{RelativePath.NormalizeEntryPath()}";
- }
-
- public override ZipEntryType Type => ZipEntryType.Directory;
+ public override EntryType Type => EntryType.Directory;
internal ZipEntryDirectory(ZipArchiveEntry entry, string source)
: base(entry, source)
@@ -26,10 +22,15 @@ internal ZipEntryDirectory(ZipArchiveEntry entry, string source)
internal ZipEntryDirectory(ZipArchiveEntry entry, Stream? stream)
: base(entry, stream)
- { }
+ {
+ Name = entry.GetDirectoryName();
+ }
internal IEnumerable GetChilds(ZipArchive zip) =>
zip.Entries.Where(e =>
!string.Equals(e.FullName, RelativePath, _comparer)
&& e.FullName.StartsWith(RelativePath, _comparer));
+
+ protected override string GetFormatDirectoryPath() =>
+ $"/{RelativePath.NormalizeEntryPath()}";
}
diff --git a/src/PSCompression/ZipEntryFile.cs b/src/PSCompression/ZipEntryFile.cs
index 3c76543..7d7a7b5 100644
--- a/src/PSCompression/ZipEntryFile.cs
+++ b/src/PSCompression/ZipEntryFile.cs
@@ -1,5 +1,6 @@
using System.IO;
using System.IO.Compression;
+using PSCompression.Abstractions;
using PSCompression.Exceptions;
using PSCompression.Extensions;
@@ -7,15 +8,9 @@ namespace PSCompression;
public sealed class ZipEntryFile : ZipEntryBase
{
- internal override string FormatDirectoryPath
- {
- get => _formatDirectoryPath ??=
- $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}";
- }
-
public string CompressionRatio => GetRatio(Length, CompressedLength);
- public override ZipEntryType Type => ZipEntryType.Archive;
+ public override EntryType Type => EntryType.Archive;
public string BaseName => Path.GetFileNameWithoutExtension(Name);
@@ -64,4 +59,7 @@ internal void Refresh(ZipArchive zip)
Length = entry.Length;
CompressedLength = entry.CompressedLength;
}
+
+ protected override string GetFormatDirectoryPath() =>
+ $"/{Path.GetDirectoryName(RelativePath).NormalizeEntryPath()}";
}
diff --git a/src/PSCompression/ZipEntryMoveCache.cs b/src/PSCompression/ZipEntryMoveCache.cs
index cd92190..158800b 100644
--- a/src/PSCompression/ZipEntryMoveCache.cs
+++ b/src/PSCompression/ZipEntryMoveCache.cs
@@ -3,6 +3,7 @@
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
+using PSCompression.Abstractions;
using PSCompression.Extensions;
namespace PSCompression;
@@ -31,7 +32,7 @@ private Dictionary WithSource(ZipEntryBase entry)
internal bool IsDirectoryEntry(string source, string path) =>
_cache[source].TryGetValue(path, out EntryWithPath entryWithPath)
- && entryWithPath.ZipEntry.Type is ZipEntryType.Directory;
+ && entryWithPath.ZipEntry.Type is EntryType.Directory;
internal void AddEntry(ZipEntryBase entry, string newname) =>
WithSource(entry).Add(entry.RelativePath, new(entry, newname));
@@ -72,7 +73,7 @@ private Dictionary GetChildMappings(
foreach (var pair in pathChanges.OrderByDescending(e => e.Key))
{
(ZipEntryBase entry, string newname) = pair.Value;
- if (entry.Type is ZipEntryType.Archive)
+ if (entry.Type is EntryType.Archive)
{
newpath = ((ZipEntryFile)entry).ChangeName(newname);
result[pair.Key] = newpath;
diff --git a/src/PSCompression/ZipEntryType.cs b/src/PSCompression/ZipEntryType.cs
index 0b875b6..112af46 100644
--- a/src/PSCompression/ZipEntryType.cs
+++ b/src/PSCompression/ZipEntryType.cs
@@ -1,6 +1,6 @@
namespace PSCompression;
-public enum ZipEntryType
+public enum EntryType
{
Directory = 0,
Archive = 1
diff --git a/src/PSCompression/internal/_Format.cs b/src/PSCompression/internal/_Format.cs
index d06d7c5..9a35d01 100644
--- a/src/PSCompression/internal/_Format.cs
+++ b/src/PSCompression/internal/_Format.cs
@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Globalization;
using System.Management.Automation;
+using PSCompression.Abstractions;
namespace PSCompression.Internal;
@@ -26,7 +27,7 @@ public static class _Format
];
[Hidden, EditorBrowsable(EditorBrowsableState.Never)]
- public static string GetDirectoryPath(ZipEntryBase entry) => entry.FormatDirectoryPath;
+ public static string? GetDirectoryPath(EntryBase entry) => entry.FormatDirectoryPath;
[Hidden, EditorBrowsable(EditorBrowsableState.Never)]
public static string GetFormattedDate(DateTime dateTime) =>
diff --git a/tests/ArchiveCompressionExpansionCommands.tests.ps1 b/tests/ArchiveCompressionExpansionCommands.tests.ps1
new file mode 100644
index 0000000..a79455e
--- /dev/null
+++ b/tests/ArchiveCompressionExpansionCommands.tests.ps1
@@ -0,0 +1,229 @@
+using namespace System.Collections.Generic
+using namespace System.IO
+using namespace System.IO.Compression
+using namespace System.Management.Automation
+
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+
+Import-Module $manifestPath
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
+
+Describe 'Archive Compression & Expansion Commands' -Tag 'Archive Compression & Expansion Commands' {
+ BeforeAll {
+ $algos = [PSCompression.Algorithm].GetEnumValues()
+ $sourceName = 'CompressArchiveTests'
+ $destName = 'CompressArchiveExtract'
+ $testpath = Join-Path $TestDrive $sourceName
+ $extractpath = Join-Path $TestDrive $destName
+ $itemCounts = Get-Structure | Build-Structure $testpath
+ $extractpath, $algos, $itemCounts | Out-Null
+ }
+
+ It 'Can compress a folder and all its child items' {
+ Compress-ZipArchive $testpath $extractpath -PassThru |
+ Should -BeOfType ([FileInfo])
+
+ $algos | ForEach-Object {
+ Compress-TarArchive $testpath $extractpath -PassThru -Algorithm $_ |
+ Should -BeOfType ([FileInfo])
+ }
+ }
+
+ It 'Should throw if the destination already exists' {
+ { Compress-ZipArchive $testpath $extractpath } |
+ Should -Throw -ExceptionType ([IOException])
+
+ $algos | ForEach-Object {
+ { Compress-TarArchive $testpath $extractpath -Algorithm $_ } |
+ Should -Throw -ExceptionType ([IOException])
+ }
+ }
+
+ It 'Should overwrite if using -Force parameter' {
+ { Compress-ZipArchive $testpath $extractpath -Force } |
+ Should -Not -Throw
+
+ foreach ($level in [CompressionLevel].GetEnumNames()) {
+ foreach ($algo in $algos) {
+ $compressTarArchiveSplat = @{
+ PassThru = $true
+ Algorithm = $algo
+ Destination = $extractpath
+ CompressionLevel = $level
+ Force = $true
+ WarningAction = 'Ignore'
+ }
+
+ { $testpath | Compress-TarArchive @compressTarArchiveSplat } |
+ Should -Not -Throw
+ }
+ }
+ }
+
+ It 'Extracted files should be exactly the same with the same structure' {
+ Get-ChildItem $TestDrive -File | ForEach-Object {
+ $destination = [Path]::Combine($TestDrive, "ExpandTest_$($_.Extension.TrimStart('.'))")
+
+ if ($_.Extension -eq '.zip') {
+ $_ | Expand-Archive -DestinationPath $destination
+ }
+ elseif ($_.Extension -match '\.tar.*') {
+ $_ | Expand-TarArchive -Destination $destination
+ }
+ else {
+ return
+ }
+
+ $expanded = Get-ChildItem $destination -Recurse
+ $files, $dirs = $expanded.Where({ $_ -is [FileInfo] }, 'Split')
+
+ $files | Should -HaveCount $itemCounts.File
+ $dirs | Should -HaveCount $itemCounts.Directory
+ }
+ }
+
+ It 'Can update entries if they exist' {
+ $destination = [Path]::Combine($TestDrive, 'UpdateTest', 'test.zip')
+ $destinationExtract = [Path]::Combine($TestDrive, 'UpdateTest')
+
+ 0..10 | ForEach-Object {
+ New-Item (Join-Path $TestDrive ('file{0:D2}.txt' -f $_)) -ItemType File -Value 'hello'
+ } | Compress-ZipArchive -Destination $destination
+
+ Get-ChildItem $TestDrive -Filter *.txt | ForEach-Object {
+ 'world!' | Add-Content -LiteralPath $_.FullName
+ $_
+ } | Compress-ZipArchive -Destination $destination -Update
+
+ Expand-Archive $destination $destinationExtract
+ Get-ChildItem $destinationExtract -Filter *.txt | ForEach-Object {
+ $_ | Get-Content | Should -BeExactly 'helloworld!'
+ }
+ }
+
+ It 'Should skip the entry if the source and destination are the same' {
+ Push-Location $TestDrive
+ $name = 'testskipitself'
+
+ { Compress-ZipArchive $pwd.Path $name } |
+ Should -Not -Throw
+
+ { Compress-ZipArchive $pwd.Path $name -Force } |
+ Should -Not -Throw
+
+ { Compress-ZipArchive $pwd.Path $name -Update } |
+ Should -Not -Throw
+
+ $destination = [guid]::NewGuid()
+ Expand-Archive "${name}.zip" -DestinationPath $destination
+
+ Get-ChildItem $destination -Recurse | ForEach-Object Name |
+ Should -Not -Contain "${name}.zip"
+
+ $archive = [Queue[FileInfo]]::new()
+ $algos | ForEach-Object {
+ $currentName = "${name}_${_}"
+ { Compress-TarArchive $pwd.Path $currentName -Algorithm $_ } |
+ Should -Not -Throw
+
+
+ {
+ $result = Compress-TarArchive $pwd.Path $currentName -Algorithm $_ -Force -PassThru
+ $archive.Enqueue($result)
+ } | Should -Not -Throw
+
+ $info = $archive.Dequeue()
+ $info | Should -BeOfType ([FileInfo])
+ Expand-TarArchive $info.FullName -Destination $currentName
+ Get-ChildItem $currentName -Recurse | ForEach-Object Name |
+ Should -Not -Contain $info.Name
+ }
+ }
+
+ It 'Should skip items that match the exclusion patterns' {
+ Get-ChildItem $TestDrive |
+ Where-Object Name -NE $sourceName |
+ Remove-Item -Recurse
+
+ $compressZipArchiveSplat = @{
+ Exclude = '*testfile00*', '*testfolder05*'
+ Path = $testpath
+ Destination = $extractpath
+ PassThru = $true
+ }
+
+ Compress-ZipArchive @compressZipArchiveSplat |
+ Expand-Archive -DestinationPath $extractpath
+
+ Get-ChildItem $extractpath -Recurse | ForEach-Object {
+ $_.FullName | Should -Not -BeLike *testfile00*
+ $_.FullName | Should -Not -BeLike *testfolder05*
+ }
+
+ Remove-Item $extractpath -Recurse
+
+ $compressTarArchiveSplat = @{
+ LiteralPath = $testpath
+ PassThru = $true
+ Exclude = '*testfile00*', '*testfolder05*'
+ Destination = $extractpath
+ }
+
+ $expanded = Compress-TarArchive @compressTarArchiveSplat |
+ Expand-TarArchive -Destination $extractpath -PassThru
+
+ $expanded | ForEach-Object {
+ $_.FullName | Should -Not -BeLike *testfile00*
+ $_.FullName | Should -Not -BeLike *testfolder05*
+ }
+ }
+
+ It 'CompressTarArchive outputs a warning when algorithm is lzip and CompressionLevel is used' {
+ $compressTarArchiveSplat = @{
+ CompressionLevel = 'Optimal'
+ Algorithm = 'lz'
+ Destination = 'shouldWarn'
+ }
+
+ Compress-TarArchive @compressTarArchiveSplat $testpath 3>&1 |
+ Should -BeOfType ([WarningRecord])
+ }
+
+ Context 'Expand-TarArchive' -Tag 'Expand-TarArchive' {
+ BeforeAll {
+ $destination = 'shouldThrowIfExists'
+ New-Item $destination -ItemType Directory | Out-Null
+ $compressed = Compress-TarArchive $testpath $destination -PassThru
+ $compressed | Out-Null
+ }
+
+ It 'Should throw if destination is an existing file' {
+ { $compressed | Expand-TarArchive -Destination "${destination}.tar.gz" } |
+ Should -Throw -ExceptionType ([ArgumentException])
+ }
+
+ It 'Can expand the archive using the current directory as Destination when not specified' {
+ try {
+ Push-Location $destination
+ { Expand-TarArchive $compressed } | Should -Not -Throw
+ Get-ChildItem | Should -Not -BeNullOrEmpty
+ }
+ finally {
+ Pop-Location
+ }
+ }
+
+ It 'Should overwrite files if already exist' {
+ $compressed | Expand-TarArchive -Destination testOverwrite
+
+ { $compressed | Expand-TarArchive -Destination testOverwrite } |
+ Should -Throw -ExceptionType ([IOException])
+
+ { $compressed | Expand-TarArchive -Destination testOverwrite -Force } |
+ Should -Not -Throw
+ }
+ }
+}
diff --git a/tests/ZipEntryCmdlets.tests.ps1 b/tests/ArchiveEntryManagementCommands.tests.ps1
similarity index 54%
rename from tests/ZipEntryCmdlets.tests.ps1
rename to tests/ArchiveEntryManagementCommands.tests.ps1
index e414608..614ce13 100644
--- a/tests/ZipEntryCmdlets.tests.ps1
+++ b/tests/ArchiveEntryManagementCommands.tests.ps1
@@ -1,17 +1,38 @@
-$ErrorActionPreference = 'Stop'
+using namespace System
+using namespace System.Collections
+using namespace System.IO
+using namespace System.Text
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
-Describe 'ZipEntry Cmdlets' {
+Describe 'Archive Entry Management Commands' {
BeforeAll {
$zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force
- $file = New-Item ([System.IO.Path]::Combine($TestDrive, 'someFile.txt')) -ItemType File -Value 'foo'
+ $file = New-Item ([Path]::Combine($TestDrive, 'someFile.txt')) -ItemType File -Value 'foo'
$uri = 'https://www.powershellgallery.com/api/v2/package/PSCompression'
- $zip, $file, $uri | Out-Null
+ $testTarName = 'TarEntryTests'
+ $testTarpath = Join-Path $TestDrive $testTarName
+ $itemCounts = Get-Structure | Build-Structure $testTarpath
+ $totalCount = $itemCounts.File + $itemCounts.Directory
+ $algos = [PSCompression.Algorithm].GetEnumValues()
+
+ $tarArchives = foreach ($algo in $algos) {
+ $compressTarArchiveSplat = @{
+ Algorithm = $algo
+ PassThru = $true
+ LiteralPath = $testTarpath
+ Destination = $testTarpath
+ }
+ Compress-TarArchive @compressTarArchiveSplat
+ }
+
+ $zip, $file, $uri, $tarArchives, $itemCounts, $totalCount | Out-Null
}
Context 'New-ZipEntry' -Tag 'New-ZipEntry' {
@@ -110,54 +131,65 @@ Describe 'ZipEntry Cmdlets' {
$entry.RelativePath |
Should -Be ([PSCompression.Extensions.PathExtensions]::NormalizePath($item.FullName))
}
+
+ It 'Ignores copying content to a ZipEntryDirectory from source file' {
+ $newZipEntrySplat = @{
+ SourcePath = (Join-Path $TestDrive helloworld.txt)
+ Destination = $zip.FullName
+ EntryPath = 'directoryEntry/', 'directoryEntry2\'
+ }
+
+ $entry = New-ZipEntry @newZipEntrySplat
+ $entry | ForEach-Object { $_.Length | Should -BeExactly 0 }
+ }
}
Context 'Get-ZipEntry' -Tag 'Get-ZipEntry' {
It 'Can list entries in a zip archive' {
$zip | Get-ZipEntry |
- Should -BeOfType ([PSCompression.ZipEntryBase])
+ Should -BeOfType ([PSCompression.Abstractions.ZipEntryBase])
}
It 'Can list entries from a Stream' {
Invoke-WebRequest $uri | Get-ZipEntry |
- Should -BeOfType ([PSCompression.ZipEntryBase])
+ Should -BeOfType ([PSCompression.Abstractions.ZipEntryBase])
Use-Object ($stream = $zip.OpenRead()) {
$stream | Get-ZipEntry |
- Should -BeOfType ([PSCompression.ZipEntryBase])
+ Should -BeOfType ([PSCompression.Abstractions.ZipEntryBase])
}
}
It 'Should throw when not targetting a FileSystem Provider Path' {
{ Get-ZipEntry function:\* } |
- Should -Throw -ExceptionType ([System.NotSupportedException])
+ Should -Throw -ExceptionType ([NotSupportedException])
{ Get-ZipEntry -LiteralPath function:\ } |
- Should -Throw -ExceptionType ([System.NotSupportedException])
+ Should -Throw -ExceptionType ([NotSupportedException])
}
- It 'Should throw when the path is not a Zip' {
+ It 'Should throw when the path is not a zip archive' {
{ Get-ZipEntry $file.FullName } |
- Should -Throw -ExceptionType ([System.IO.InvalidDataException])
+ Should -Throw -ExceptionType ([InvalidDataException])
}
- It 'Should throw when a Stream is not a Zip' {
+ It 'Should throw when a Stream is not a zip archive' {
{
Use-Object ($stream = $file.OpenRead()) {
Get-ZipEntry $stream
}
- } | Should -Throw -ExceptionType ([System.IO.InvalidDataException])
+ } | Should -Throw -ExceptionType ([InvalidDataException])
}
- It 'Should throw when a Stream is Disposed' {
+ It 'Should throw when a Stream is disposed' {
{
(Use-Object ($stream = (Invoke-WebRequest $uri).RawContentStream) { $stream }) | Get-ZipEntry
- } | Should -Throw -ExceptionType ([System.ObjectDisposedException])
+ } | Should -Throw -ExceptionType ([ObjectDisposedException])
}
It 'Should throw if the path is not a file' {
{ Get-ZipEntry $TestDrive } |
- Should -Throw -ExceptionType ([System.ArgumentException])
+ Should -Throw -ExceptionType ([ArgumentException])
}
It 'Can list zip file entries' {
@@ -182,6 +214,95 @@ Describe 'ZipEntry Cmdlets' {
}
}
+ Context 'Get-TarEntry' -Tag 'Get-TarEntry' {
+ It 'Can list entries in a tar archive' {
+ $tarArchives | Get-TarEntry |
+ Should -BeOfType ([PSCompression.Abstractions.TarEntryBase])
+ }
+
+ It 'Can list entries from a Stream' {
+ foreach ($archive in $tarArchives) {
+ if ($archive.Extension -eq '.tar') {
+ $algo = 'none'
+ }
+ else {
+ $algo = $archive.Extension.TrimStart('.')
+ }
+
+ Use-Object ($stream = $archive.OpenRead()) {
+ $stream | Get-TarEntry -Algorithm $algo |
+ Should -BeOfType ([PSCompression.Abstractions.TarEntryBase])
+ }
+ }
+ }
+
+ It 'Should throw when not targetting a FileSystem Provider Path' {
+ { Get-TarEntry function:\* } |
+ Should -Throw -ExceptionType ([NotSupportedException])
+
+ { Get-TarEntry -LiteralPath function:\ } |
+ Should -Throw -ExceptionType ([NotSupportedException])
+ }
+
+ It 'Should throw when the path is not a tar archive' {
+ { Get-TarEntry $file.FullName } |
+ Should -Throw -ExceptionType ([InvalidDataException])
+ }
+
+ It 'Should throw when a Stream is not a tar archive' {
+ {
+ Use-Object ($stream = $file.OpenRead()) {
+ Get-TarEntry $stream
+ }
+ } | Should -Throw -ExceptionType ([InvalidDataException])
+ }
+
+ It 'Should throw when a Stream is a tar archive with wrong algorithm' {
+ $testArchive = $tarArchives |
+ Where-Object { $_.Extension -ne '.tar' -and $_.Extension -notmatch $algos[0] } |
+ Select-Object -First 1
+
+ {
+ Use-Object ($stream = $testArchive.OpenRead()) {
+ $stream | Get-TarEntry -Algorithm $algos[0]
+ }
+ } | Should -Throw -ExceptionType ([InvalidDataException])
+ }
+
+ It 'Should throw when a Stream is disposed' {
+ {
+ $gz = $tarArchives | Where-Object Extension -EQ .gz
+ (Use-Object ($stream = $gz.OpenRead()) { $stream }) | Get-TarEntry
+ } | Should -Throw
+ }
+
+ It 'Should throw if the path is not a file' {
+ { Get-TarEntry $TestDrive } |
+ Should -Throw -ExceptionType ([ArgumentException])
+ }
+
+ It 'Can list tar file entries' {
+ $tarArchives | Get-TarEntry -Type Archive |
+ Should -BeOfType ([PSCompression.TarEntryFile])
+ }
+
+ It 'Can list tar directory entries' {
+ $tarArchives | Get-TarEntry -Type Directory |
+ Should -BeOfType ([PSCompression.TarEntryDirectory])
+ }
+
+ It 'Can list a specific entry with the -Include parameter' {
+ $tarArchives | Get-TarEntry -Include "${testTarName}/testfolder05/testfile00.txt" |
+ Should -BeOfType ([PSCompression.TarEntryFile])
+ }
+
+ It 'Can exclude entries using the -Exclude parameter' {
+ $tarArchives | Get-TarEntry -Exclude *.txt |
+ ForEach-Object Extension |
+ Should -Not -Be '.txt'
+ }
+ }
+
Context 'Get-ZipEntryContent' -Tag 'Get-ZipEntryContent' {
It 'Can read content from zip file entries' {
$zip | Get-ZipEntry -Type Archive |
@@ -193,7 +314,7 @@ Describe 'ZipEntry Cmdlets' {
Invoke-WebRequest $uri | Get-ZipEntry -Type Archive -Include *.psd1 |
Get-ZipEntryContent -Raw |
Invoke-Expression |
- Should -BeOfType ([System.Collections.IDictionary])
+ Should -BeOfType ([IDictionary])
}
It 'Should throw when a Stream is Diposed' {
@@ -202,7 +323,7 @@ Describe 'ZipEntry Cmdlets' {
$stream | Get-ZipEntry -Type Archive -Include *.psd1
}
$entry | Get-ZipEntryContent -Raw
- } | Should -Throw -ExceptionType ([System.ObjectDisposedException])
+ } | Should -Throw -ExceptionType ([ObjectDisposedException])
}
It 'Should not throw when an instance wrapped in PSObject is passed as Encoding argument' {
@@ -210,11 +331,11 @@ Describe 'ZipEntry Cmdlets' {
{ $zip | Get-ZipEntry -Type Archive | Get-ZipEntryContent -Encoding $enc } |
Should -Not -Throw
- $enc = [System.Text.Encoding]::UTF8 | Write-Output
+ $enc = [Encoding]::UTF8 | Write-Output
{ $zip | Get-ZipEntry -Type Archive | Get-ZipEntryContent -Encoding $enc } |
Should -Not -Throw
- $enc = [System.Text.Encoding]::UTF8.CodePage | Write-Output
+ $enc = [Encoding]::UTF8.CodePage | Write-Output
{ $zip | Get-ZipEntry -Type Archive | Get-ZipEntryContent -Encoding $enc } |
Should -Not -Throw
}
@@ -237,6 +358,81 @@ Describe 'ZipEntry Cmdlets' {
}
}
+ Context 'Get-TarEntryContent' -Tag 'Get-TarEntryContent' {
+ It 'Can read content from tar file entries' {
+ $tarArchives | Get-TarEntry -Type Archive |
+ Get-TarEntryContent |
+ Should -Match '^\d+$'
+
+ $tarArchives | Get-TarEntry -Type Archive |
+ Get-TarEntryContent -Raw |
+ Should -Match '^\d+$'
+ }
+
+ It 'Can read content from tar file entries created from input Stream' {
+ foreach ($archive in $tarArchives) {
+ if ($archive.Extension -eq '.tar') {
+ $algo = 'none'
+ }
+ else {
+ $algo = $archive.Extension.TrimStart('.')
+ }
+
+ Use-Object ($stream = $archive.OpenRead()) {
+ $stream | Get-TarEntry -Algorithm $algo -Type Archive |
+ Get-TarEntryContent |
+ Should -Match '^\d+$'
+ }
+ }
+ }
+
+ It 'Should throw when a Stream is Diposed' {
+ foreach ($archive in $tarArchives) {
+ if ($archive.Extension -eq '.tar') {
+ $algo = 'none'
+ }
+ else {
+ $algo = $archive.Extension.TrimStart('.')
+ }
+
+ $entry = Use-Object ($stream = $archive.OpenRead()) {
+ $stream | Get-TarEntry -Algorithm $algo -Type Archive
+ }
+
+ { $entry | Get-TarEntryContent } | Should -Throw
+ }
+ }
+
+ It 'Should not throw when an instance wrapped in PSObject is passed as Encoding argument' {
+ $enc = Write-Output utf8
+ { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -Encoding $enc } |
+ Should -Not -Throw
+
+ $enc = [Encoding]::UTF8 | Write-Output
+ { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -Encoding $enc } |
+ Should -Not -Throw
+
+ $enc = [Encoding]::UTF8.CodePage | Write-Output
+ { $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -Encoding $enc } |
+ Should -Not -Throw
+ }
+
+ It 'Can read bytes from tar file entries' {
+ $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -AsByteStream |
+ Should -BeOfType ([byte])
+ }
+
+ It 'Can output a byte array when using the -Raw switch' {
+ $tarArchives | Get-TarEntry -Type Archive | Get-TarEntryContent -AsByteStream -Raw |
+ Should -BeOfType ([byte[]])
+ }
+
+ It 'Should not attempt to read a directory entry' {
+ { $tarArchives | Get-TarEntry -Type Directory | Get-TarEntryContent } |
+ Should -Throw
+ }
+ }
+
Context 'Remove-ZipEntry' -Tag 'Remove-ZipEntry' {
It 'No entry is removed with -WhatIf' {
$zip | Get-ZipEntry | Remove-ZipEntry -WhatIf
@@ -252,7 +448,7 @@ Describe 'ZipEntry Cmdlets' {
It 'Should throw if trying to remove entries created from input Stream' {
{ Invoke-WebRequest $uri | Get-ZipEntry | Remove-ZipEntry } |
- Should -Throw -ExceptionType ([System.NotSupportedException])
+ Should -Throw -ExceptionType ([NotSupportedException])
}
It 'Can remove directory entries' {
@@ -274,9 +470,7 @@ Describe 'ZipEntry Cmdlets' {
Context 'Set-ZipEntryContent' -Tag 'Set-ZipEntryContent' {
BeforeAll {
$content = 'hello', 'world', '!'
- [byte[]] $bytes = $content | ForEach-Object {
- [System.Text.Encoding]::UTF8.GetBytes($_)
- }
+ [byte[]] $bytes = $content | ForEach-Object { [Encoding]::UTF8.GetBytes($_) }
$entry = New-ZipEntry $zip.FullName -EntryPath test\helloworld.txt
$entry, $content, $bytes | Out-Null
}
@@ -286,13 +480,13 @@ Describe 'ZipEntry Cmdlets' {
$entry | Get-ZipEntryContent | Should -BeExactly $content
$entry | Get-ZipEntryContent -Raw |
ForEach-Object TrimEnd |
- Should -BeExactly ($content -join [System.Environment]::NewLine)
+ Should -BeExactly ($content -join [Environment]::NewLine)
}
It 'Should throw if trying to write content to entries created from input Stream' {
$entry = Invoke-WebRequest $uri | Get-ZipEntry -Include *.psd1
{ 'test' | Set-ZipEntryContent $entry } |
- Should -Throw -ExceptionType ([System.NotSupportedException])
+ Should -Throw -ExceptionType ([NotSupportedException])
}
It 'Can append content to a zip file entry' {
@@ -301,7 +495,7 @@ Describe 'ZipEntry Cmdlets' {
$entry | Get-ZipEntryContent | Should -BeExactly $newContent
$entry | Get-ZipEntryContent -Raw |
ForEach-Object TrimEnd |
- Should -BeExactly ($newContent -join [System.Environment]::NewLine)
+ Should -BeExactly ($newContent -join [Environment]::NewLine)
}
It 'Can write raw bytes to a zip file entry' {
@@ -354,7 +548,7 @@ Describe 'ZipEntry Cmdlets' {
It 'Should throw if trying to rename entries created from input Stream' {
{ Invoke-WebRequest $uri | Get-ZipEntry | Rename-ZipEntry -NewName { 'test' + $_.Name } } |
- Should -Throw -ExceptionType ([System.NotSupportedException])
+ Should -Throw -ExceptionType ([NotSupportedException])
}
It 'Produces output with -PassThru' {
@@ -391,7 +585,7 @@ Describe 'ZipEntry Cmdlets' {
}
It 'Should throw if using invalid FileName chars' {
- $invalidChars = [System.IO.Path]::GetInvalidFileNameChars()
+ $invalidChars = [Path]::GetInvalidFileNameChars()
{ $zip | Get-ZipEntry -Type Archive |
Rename-ZipEntry -NewName { $_.Name + $invalidChars[0] } } |
@@ -429,10 +623,10 @@ Describe 'ZipEntry Cmdlets' {
It 'Can extract zip file entries created from input Stream' {
Invoke-WebRequest $uri | Get-ZipEntry -Type Archive -Include *.psd1 |
Expand-ZipEntry -Destination $destination -PassThru -OutVariable psd1 |
- Should -BeOfType ([System.IO.FileInfo])
+ Should -BeOfType ([FileInfo])
Get-Content $psd1.FullName -Raw | Invoke-Expression |
- Should -BeOfType ([System.Collections.IDictionary])
+ Should -BeOfType ([IDictionary])
$psd1.Delete()
}
@@ -443,7 +637,18 @@ Describe 'ZipEntry Cmdlets' {
$stream | Get-ZipEntry -Type Archive -Include *.psd1
}
$entry | Expand-ZipEntry -Destination $destination -Force
- } | Should -Throw -ExceptionType ([System.ObjectDisposedException])
+ } | Should -Throw -ExceptionType ([ObjectDisposedException])
+ }
+
+ It 'Should extract entries to the current directory when -Destination is not specified' {
+ try {
+ New-Item expandToCurrent -ItemType Directory | Push-Location
+ { $zip | Get-ZipEntry | Expand-ZipEntry } | Should -Not -Throw
+ Get-ChildItem | Should -Not -HaveCount 0
+ }
+ finally {
+ Pop-Location
+ }
}
It 'Should throw when -Destination is an invalid path' {
@@ -476,4 +681,102 @@ Describe 'ZipEntry Cmdlets' {
ForEach-Object { $_ | Get-Content | Should -BeExactly $content }
}
}
+
+ Context 'Expand-TarEntry' -Tag 'Expand-TarEntry' {
+ It 'Can extract entries to a destination directory' {
+ $tarArchives | ForEach-Object {
+ $destination = "extract_$($_.Extension)"
+
+ { $_ | Get-TarEntry | Expand-TarEntry -Destination $destination } |
+ Should -Not -Throw
+
+ Get-ChildItem $destination -Recurse |
+ Should -HaveCount $totalCount
+ }
+ }
+
+ It 'Can extract tar entries created from input Stream' {
+ foreach ($archive in $tarArchives) {
+ if ($archive.Extension -eq '.tar') {
+ $algo = 'none'
+ }
+ else {
+ $algo = $archive.Extension.TrimStart('.')
+ }
+
+ $destination = "extract_${algo}_fromStream"
+
+ $result = Use-Object ($stream = $archive.OpenRead()) {
+ $stream | Get-TarEntry -Algorithm $algo |
+ Expand-TarEntry -Destination $destination -PassThru
+ }
+
+ $result | Should -BeOfType ([FileSystemInfo])
+ $result | Should -HaveCount $totalCount
+ }
+ }
+
+ It 'Should throw when a Stream is Diposed' {
+ foreach ($archive in $tarArchives) {
+ if ($archive.Extension -eq '.tar') {
+ $algo = 'none'
+ }
+ else {
+ $algo = $archive.Extension.TrimStart('.')
+ }
+
+ $destination = "extract_$($_.Extension)_fromStream"
+
+ $entries = Use-Object ($stream = $archive.OpenRead()) {
+ $stream | Get-TarEntry -Algorithm $algo
+ }
+
+ { $entries | Expand-TarEntry -Destination $destination } |
+ Should -Throw
+ }
+ }
+
+ It 'Should throw when -Destination is an invalid path' {
+ { $tarArchives | Get-TarEntry | Expand-TarEntry -Destination function: } |
+ Should -Throw
+ }
+
+ It 'Should throw if the destination path argument belongs to a file' {
+ $tarArchive = $tarArchives[0]
+ { $tarArchive | Get-TarEntry | Expand-TarEntry -Destination $tarArchive.FullName } |
+ Should -Throw
+ }
+
+ It 'Should not overwrite files without -Force' {
+ $tarArchives | ForEach-Object {
+ $destination = "extract_$($_.Extension)"
+
+ { $_ | Get-TarEntry | Expand-TarEntry -Destination $destination } |
+ Should -Throw -ExceptionType ([IOException])
+ }
+ }
+
+ It 'Can overwrite files if using -Force' {
+ $tarArchives | ForEach-Object {
+ $destination = "extract_$($_.Extension)"
+
+ { $_ | Get-TarEntry | Expand-TarEntry -Destination $destination -Force } |
+ Should -Not -Throw
+ }
+ }
+
+ It 'Should extract entries to the current directory when -Destination is not specified' {
+ foreach ($archive in $tarArchives) {
+ try {
+ New-Item "expandToCurrent_$($archive.Extension)" -ItemType Directory | Push-Location
+ { $archive | Get-TarEntry | Expand-TarEntry } | Should -Not -Throw
+ Get-ChildItem | Should -Not -HaveCount 0
+ }
+ finally {
+ Pop-Location
+ }
+
+ }
+ }
+ }
}
diff --git a/tests/CompressZipArchive.tests.ps1 b/tests/CompressZipArchive.tests.ps1
deleted file mode 100644
index 67ad7e3..0000000
--- a/tests/CompressZipArchive.tests.ps1
+++ /dev/null
@@ -1,117 +0,0 @@
-$ErrorActionPreference = 'Stop'
-
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
-
-Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
-
-Describe 'Compress-ZipArchive' -Tag 'Compress-ZipArchive' {
- BeforeAll {
- $sourceName = 'CompressZipArchiveTests'
- $destName = 'CompressZipArchiveExtract'
- $testpath = Join-Path $TestDrive $sourceName
- $extractpath = Join-Path $TestDrive $destName
-
- $structure = Get-Structure | ForEach-Object {
- $path = Join-Path $testpath $_
- if ($_.EndsWith('.txt')) {
- New-Item $path -ItemType File -Value (Get-Random) -Force
- return
- }
- New-Item $path -ItemType Directory -Force
- }
-
- $structure, $extractpath | Out-Null
- }
-
- It 'Can compress a folder and all its child items' {
- Compress-ZipArchive $testpath $extractpath -PassThru |
- Should -BeOfType ([System.IO.FileInfo])
- }
-
- It 'Should throw if the destination already exists' {
- { Compress-ZipArchive $testpath $extractpath } |
- Should -Throw
- }
-
- It 'Should overwrite if using -Force' {
- { Compress-ZipArchive $testpath $extractpath -Force } |
- Should -Not -Throw
- }
-
- It 'Extracted files should be exactly the same with the same structure' {
- BeforeAll {
- $map = @{}
- Get-ChildItem $testpath -Recurse | ForEach-Object {
- $relative = $_.FullName.Substring($testpath.Length)
- if ($_ -is [System.IO.FileInfo]) {
- $map[$relative] = ($_ | Get-FileHash -Algorithm MD5).Hash
- return
- }
- $map[$relative] = $null
- }
-
- Expand-Archive "$extractpath.zip" $extractpath
-
- $extractpath = Join-Path $extractpath $sourceName
- Get-ChildItem $extractpath -Recurse | ForEach-Object {
- $relative = $_.FullName.Substring($extractpath.Length)
- $map.ContainsKey($relative) | Should -BeTrue
-
- if ($_ -is [System.IO.FileInfo]) {
- $thishash = ($_ | Get-FileHash -Algorithm MD5).Hash
- $map[$relative] | Should -BeExactly $thishash
- }
- }
- }
- }
-
- It 'Can update entries if they exist' {
- $destination = [IO.Path]::Combine($TestDrive, 'UpdateTest', 'test.zip')
- $destinationExtract = [IO.Path]::Combine($TestDrive, 'UpdateTest')
-
- 0..10 | ForEach-Object {
- New-Item (Join-Path $TestDrive ('file{0:D2}.txt' -f $_)) -ItemType File -Value 'hello'
- } | Compress-ZipArchive -Destination $destination
-
- Get-ChildItem $TestDrive -Filter *.txt | ForEach-Object {
- 'world!' | Add-Content -LiteralPath $_.FullName
- $_
- } | Compress-ZipArchive -Destination $destination -Update
-
- Expand-Archive $destination $destinationExtract
- Get-ChildItem $destinationExtract -Filter *.txt | ForEach-Object {
- $_ | Get-Content | Should -BeExactly 'helloworld!'
- }
- }
-
- It 'Should skip the entry if the source and destination are the same' {
- Push-Location $TestDrive
- $zipname = 'testskipitself.zip'
-
- { Compress-ZipArchive $pwd.Path $zipname } |
- Should -Not -Throw
-
- { Compress-ZipArchive $pwd.Path $zipname -Force } |
- Should -Not -Throw
-
- { Compress-ZipArchive $pwd.Path $zipname -Update } |
- Should -Not -Throw
-
- Expand-Archive $zipname -DestinationPath skipitself
-
- Get-ChildItem skipitself -Recurse | ForEach-Object Name |
- Should -Not -Contain $zipname
- }
-
- It 'Should skip items that match the exclusion patterns' {
- Remove-Item "$extractpath.zip" -Force
- Compress-ZipArchive $testpath $extractpath -Exclude *testfile00*, *testfolder05*
- Expand-Archive "$extractpath.zip" $extractpath
- Get-ChildItem $extractpath -Recurse | ForEach-Object {
- $_.FullName | Should -Not -BeLike *testfile00*
- $_.FullName | Should -Not -BeLike *testfolder05*
- }
- }
-}
diff --git a/tests/DirectoryEntryTypes.tests.ps1 b/tests/DirectoryEntryTypes.tests.ps1
new file mode 100644
index 0000000..115e0da
--- /dev/null
+++ b/tests/DirectoryEntryTypes.tests.ps1
@@ -0,0 +1,23 @@
+using namespace System.IO
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+
+Import-Module $manifestPath
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
+
+Describe 'Directory Entry Types' {
+ BeforeAll {
+ $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force
+ New-ZipEntry $zip.FullName -EntryPath afolder/
+ $tarArchive = New-Item (Join-Path $TestDrive afolder) -ItemType Directory -Force |
+ Compress-TarArchive -Destination 'testTarFile' -PassThru
+
+ $tarArchive | Out-Null
+ }
+
+ It 'Should be of type Directory' {
+ ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Directory)
+ ($tarArchive | Get-TarEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Directory)
+ }
+}
diff --git a/tests/EncodingCompleter.tests.ps1 b/tests/EncodingCompleter.tests.ps1
index 25e413d..cfcb621 100644
--- a/tests/EncodingCompleter.tests.ps1
+++ b/tests/EncodingCompleter.tests.ps1
@@ -1,32 +1,34 @@
-$ErrorActionPreference = 'Stop'
+using namespace System.IO
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+$ErrorActionPreference = 'Stop'
-Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
-
-BeforeAll {
- $encodingSet = @(
- 'ascii'
- 'bigendianUtf32'
- 'unicode'
- 'utf8'
- 'utf8NoBOM'
- 'bigendianUnicode'
- 'oem'
- 'utf8BOM'
- 'utf32'
-
- if ($osIsWindows) {
- 'ansi'
- }
- )
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
- $encodingSet | Out-Null
-}
+Import-Module $manifestPath
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
Describe 'EncodingCompleter Class' {
+ BeforeAll {
+ $encodingSet = @(
+ 'ascii'
+ 'bigendianUtf32'
+ 'unicode'
+ 'utf8'
+ 'utf8NoBOM'
+ 'bigendianUnicode'
+ 'oem'
+ 'utf8BOM'
+ 'utf32'
+
+ if ($osIsWindows) {
+ 'ansi'
+ }
+ )
+
+ $encodingSet | Out-Null
+ }
+
It 'Completes results from a completion set' {
(Complete 'Test-Completer ').CompletionText |
Should -BeExactly $encodingSet
@@ -39,10 +41,10 @@ Describe 'EncodingCompleter Class' {
It 'Should not offer ansi as a completion result if the OS is not Windows' {
if ($osIsWindows) {
+ (Complete 'Test-Completer ansi').CompletionText | Should -Not -BeNullOrEmpty
return
}
- (Complete 'Test-Completer ansi').CompletionText |
- Should -BeNullOrEmpty
+ (Complete 'Test-Completer ansi').CompletionText | Should -BeNullOrEmpty
}
}
diff --git a/tests/EncodingTransformation.tests.ps1 b/tests/EncodingTransformation.tests.ps1
index ec9e4fe..b144c89 100644
--- a/tests/EncodingTransformation.tests.ps1
+++ b/tests/EncodingTransformation.tests.ps1
@@ -1,10 +1,13 @@
-$ErrorActionPreference = 'Stop'
+using namespace System.IO
+using namespace System.Text
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
Describe 'EncodingTransformation Class' {
BeforeAll {
@@ -13,23 +16,22 @@ Describe 'EncodingTransformation Class' {
{
[System.Runtime.InteropServices.DllImport("Kernel32.dll")]
public static extern int GetACP();
- }
- '
+ }'
$encodings = @{
- 'ascii' = [System.Text.ASCIIEncoding]::new()
- 'bigendianunicode' = [System.Text.UnicodeEncoding]::new($true, $true)
- 'bigendianutf32' = [System.Text.UTF32Encoding]::new($true, $true)
+ 'ascii' = [ASCIIEncoding]::new()
+ 'bigendianunicode' = [UnicodeEncoding]::new($true, $true)
+ 'bigendianutf32' = [UTF32Encoding]::new($true, $true)
'oem' = [Console]::OutputEncoding
- 'unicode' = [System.Text.UnicodeEncoding]::new()
- 'utf8' = [System.Text.UTF8Encoding]::new($false)
- 'utf8bom' = [System.Text.UTF8Encoding]::new($true)
- 'utf8nobom' = [System.Text.UTF8Encoding]::new($false)
- 'utf32' = [System.Text.UTF32Encoding]::new()
+ 'unicode' = [UnicodeEncoding]::new()
+ 'utf8' = [UTF8Encoding]::new($false)
+ 'utf8bom' = [UTF8Encoding]::new($true)
+ 'utf8nobom' = [UTF8Encoding]::new($false)
+ 'utf32' = [UTF32Encoding]::new()
}
if ($osIsWindows) {
- $encodings['ansi'] = [System.Text.Encoding]::GetEncoding([Acp]::GetACP())
+ $encodings['ansi'] = [Encoding]::GetEncoding([Acp]::GetACP())
}
$transform = [PSCompression.EncodingTransformation]::new()
@@ -37,8 +39,8 @@ Describe 'EncodingTransformation Class' {
}
It 'Transforms Encoding to Encoding' {
- $transform.Transform($ExecutionContext, [System.Text.Encoding]::UTF8) |
- Should -BeExactly ([System.Text.Encoding]::UTF8)
+ $transform.Transform($ExecutionContext, [Encoding]::UTF8) |
+ Should -BeExactly ([Encoding]::UTF8)
}
It 'Transforms a completion set to their Encoding Representations' {
diff --git a/tests/FileEntryTypes.tests.ps1 b/tests/FileEntryTypes.tests.ps1
new file mode 100644
index 0000000..e28eca7
--- /dev/null
+++ b/tests/FileEntryTypes.tests.ps1
@@ -0,0 +1,50 @@
+using namespace System.IO
+using namespace System.IO.Compression
+
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+
+Import-Module $manifestPath
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
+
+Describe 'File Entry Types' {
+ BeforeAll {
+ $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force
+ 'hello world!' | New-ZipEntry $zip.FullName -EntryPath helloworld.txt
+
+ $tarArchive = New-Item (Join-Path $TestDrive helloworld.txt) -ItemType File -Force |
+ Compress-TarArchive -Destination 'testTarDirectory' -PassThru
+
+ $tarArchive | Out-Null
+ }
+
+ It 'Should be of type Archive' {
+ ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Archive)
+
+ ($tarArchive | Get-TarEntry).Type | Should -BeExactly ([PSCompression.EntryType]::Archive)
+ }
+
+ It 'Should Have a BaseName Property' {
+ ($zip | Get-ZipEntry).BaseName | Should -BeOfType ([string])
+ ($zip | Get-ZipEntry).BaseName | Should -BeExactly helloworld
+
+ ($tarArchive | Get-TarEntry).BaseName | Should -BeOfType ([string])
+ ($tarArchive | Get-TarEntry).BaseName | Should -BeExactly helloworld
+ }
+
+ It 'Should Have an Extension Property' {
+ ($zip | Get-ZipEntry).Extension | Should -BeOfType ([string])
+ ($zip | Get-ZipEntry).Extension | Should -BeExactly .txt
+
+ ($tarArchive | Get-TarEntry).Extension | Should -BeOfType ([string])
+ ($tarArchive | Get-TarEntry).Extension | Should -BeExactly .txt
+ }
+
+ It 'Should Open the source zip' {
+ Use-Object ($stream = ($zip | Get-ZipEntry).OpenRead()) {
+ $stream | Should -BeOfType ([ZipArchive])
+ }
+ }
+}
diff --git a/tests/FormattingInternals.tests.ps1 b/tests/FormattingInternals.tests.ps1
index 24ebe04..f9c54c9 100644
--- a/tests/FormattingInternals.tests.ps1
+++ b/tests/FormattingInternals.tests.ps1
@@ -1,16 +1,23 @@
-$ErrorActionPreference = 'Stop'
+using namespace System.IO
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
Describe 'Formatting internals' {
BeforeAll {
$zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force
'hello world!' | New-ZipEntry $zip.FullName -EntryPath helloworld.txt
New-ZipEntry $zip.FullName -EntryPath afolder/
+ $testTarName = 'formattingTarTest'
+ $testTarpath = Join-Path $TestDrive $testTarName
+ Get-Structure | Build-Structure $testTarpath
+ $tarArchive = Compress-TarArchive $testTarpath -Destination $testTarName -PassThru
+ $tarArchive | Out-Null
}
It 'Converts Length to their friendly representation' {
@@ -22,10 +29,17 @@ Describe 'Formatting internals' {
$zip | Get-ZipEntry | ForEach-Object {
[PSCompression.Internal._Format]::GetDirectoryPath($_)
} | Should -BeOfType ([string])
+
+ $tarArchive | Get-TarEntry | ForEach-Object {
+ [PSCompression.Internal._Format]::GetDirectoryPath($_)
+ } | Should -BeOfType ([string])
}
It 'Formats datetime instances' {
[PSCompression.Internal._Format]::GetFormattedDate([datetime]::Now) |
- Should -BeExactly ([string]::Format([CultureInfo]::CurrentCulture,'{0,10:d} {0,8:t}', [datetime]::Now))
+ Should -BeExactly ([string]::Format(
+ [CultureInfo]::CurrentCulture,
+ '{0,10:d} {0,8:t}',
+ [datetime]::Now))
}
}
diff --git a/tests/GzipCmdlets.tests.ps1 b/tests/GzipCmdlets.tests.ps1
deleted file mode 100644
index a965ce4..0000000
--- a/tests/GzipCmdlets.tests.ps1
+++ /dev/null
@@ -1,216 +0,0 @@
-$ErrorActionPreference = 'Stop'
-
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
-
-Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
-
-Describe 'Gzip Cmdlets' {
- Context 'ConvertFrom-GzipString' -Tag 'ConvertFrom-GzipString' {
- It 'Should throw on a non b64 encoded input' {
- { 'foo' | ConvertFrom-GzipString } |
- Should -Throw
- }
- }
-
- Context 'ConvertTo & ConvertFrom GzipString' -Tag 'ConvertTo & ConvertFrom GzipString' {
- BeforeAll {
- $content = 'hello', 'world', '!'
- $content | Out-Null
- }
-
- It 'Can compress strings to gzip b64 strings from pipeline' {
- $encoded = { $content | ConvertTo-GzipString } |
- Should -Not -Throw -PassThru
-
- $encoded | ConvertFrom-GzipString |
- Should -BeExactly $contet
- }
-
- It 'Can compress strings to gzip b64 strings positionally' {
- $encoded = { ConvertTo-GzipString -InputObject $content } |
- Should -Not -Throw -PassThru
-
- $encoded | ConvertFrom-GzipString |
- Should -BeExactly $contet
- }
-
- It 'Can compress strings to gzip and output raw bytes' {
- [byte[]] $bytes = $content | ConvertTo-GzipString -AsByteStream
- $result = Decode $bytes
- $result.TrimEnd() | Should -BeExactly ($content -join [System.Environment]::NewLine)
- }
-
- It 'Can convert gzip b64 compressed string and output multi-line string' {
- $content | ConvertTo-GzipString |
- ConvertFrom-GzipString -Raw |
- ForEach-Object TrimEnd |
- Should -BeExactly ($content -join [System.Environment]::NewLine)
- }
-
- It 'Concatenates strings when the -NoNewLine switch is used' {
- $content | ConvertTo-GzipString -NoNewLine |
- ConvertFrom-GzipString |
- Should -BeExactly (-join $content)
- }
- }
-
- Context 'Compress & Expand GzipArchive' -Tag 'Compress & Expand GzipArchive' {
- BeforeAll {
- $testString = 'hello world!'
- $content = $testString | New-Item (Join-Path $TestDrive content.txt)
- $appendedContent = 'this is appended content...' | New-Item (Join-Path $TestDrive appendedContent.txt)
- $destination = Join-Path $TestDrive -ChildPath test.gz
- $testString, $content, $appendedContent, $destination | Out-Null
- }
-
- It 'Can create a Gzip compressed file from a specified path' {
- $content |
- Compress-GzipArchive -DestinationPath $destination -PassThru |
- Expand-GzipArchive |
- Should -BeExactly ($content | Get-Content)
- }
-
- It 'Outputs a single multiline string when using the -Raw switch' {
- Expand-GzipArchive $destination -Raw |
- Should -BeExactly ($content | Get-Content -Raw)
- }
-
- It 'Can append content to a Gzip compressed file from a specified path' {
- $appendedContent |
- Compress-GzipArchive -DestinationPath $destination -PassThru -Update |
- Expand-GzipArchive |
- Should -BeExactly (-join @($content, $appendedContent | Get-Content))
- }
-
- It 'Can expand Gzip files with appended content to a destination file' {
- $expandGzipArchiveSplat = @{
- LiteralPath = $destination
- DestinationPath = (Join-Path $TestDrive extractappended.txt)
- PassThru = $true
- }
-
- Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName |
- Should -BeExactly (-join @($content, $appendedContent | Get-Content))
- }
-
- It 'Should not overwrite an existing Gzip file without -Force' {
- { $content | Compress-GzipArchive -DestinationPath $destination } |
- Should -Throw
- }
-
- It 'Can overwrite an existing Gzip file with -Force' {
- { $content | Compress-GzipArchive -DestinationPath $destination -Force } |
- Should -Not -Throw
-
- Expand-GzipArchive -LiteralPath $destination |
- Should -BeExactly ($content | Get-Content)
- }
-
- It 'Can expand Gzip files to a destination file' {
- $expandGzipArchiveSplat = @{
- LiteralPath = $destination
- DestinationPath = (Join-Path $TestDrive extract.txt)
- PassThru = $true
- }
-
- Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName |
- Should -BeExactly ($content | Get-Content)
- }
-
- It 'Should throw if expanding to an existing file' {
- $expandGzipArchiveSplat = @{
- LiteralPath = $destination
- DestinationPath = (Join-Path $TestDrive extract.txt)
- }
-
- { Expand-GzipArchive @expandGzipArchiveSplat } |
- Should -Throw
- }
-
- It 'Can append content with -Update' {
- $expandGzipArchiveSplat = @{
- LiteralPath = $destination
- DestinationPath = (Join-Path $TestDrive extract.txt)
- PassThru = $true
- Update = $true
- }
-
- $appendedContent = $testString + $testString
- Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName |
- Should -BeExactly $appendedContent
- }
-
- It 'Can overwrite the destination file with -Force' {
- $expandGzipArchiveSplat = @{
- LiteralPath = $destination
- DestinationPath = (Join-Path $TestDrive extract.txt)
- PassThru = $true
- Force = $true
- }
-
- Get-Content -LiteralPath (Expand-GzipArchive @expandGzipArchiveSplat).FullName |
- Should -BeExactly $testString
- }
-
- It 'Should throw if the Path is not an Archive' {
- { Expand-GzipArchive $pwd } | Should -Throw
- }
-
- It 'Should throw if the Path is not a Gzip Archive' {
- { Expand-GzipArchive -LiteralPath (Join-Path $TestDrive content.txt) } |
- Should -Throw
- }
-
- It 'Can create folders when the destination directory does not exist' {
- $expandGzipArchiveSplat = @{
- LiteralPath = $destination
- DestinationPath = [IO.Path]::Combine($TestDrive, 'does', 'not', 'exist', 'extract.txt')
- PassThru = $true
- Force = $true
- }
-
- { Expand-GzipArchive @expandGzipArchiveSplat } |
- Should -Not -Throw
- }
-
- It 'Should throw if trying to compress a directory' {
- $folders = 0..5 | ForEach-Object {
- New-Item (Join-Path $TestDrive "folder $_") -ItemType Directory
- }
-
- { Compress-GzipArchive $folders.FullName -Destination $destination -Force } |
- Should -Throw
- }
-
- Context 'Compress-GzipArchive with InputBytes' {
- BeforeAll {
- $path = [IO.Path]::Combine($TestDrive, 'this', 'is', 'atest', 'file')
- $testStrings = 'hello', 'world', '!'
- $path, $testStrings | Out-Null
- }
-
- It 'Can create compressed files from input bytes' {
- $testStrings | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -DestinationPath $path -PassThru |
- Expand-GzipArchive |
- Should -BeExactly $testStrings
- }
-
- It 'Can append to compressed files from input bytes' {
- $testStrings | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -DestinationPath $path -PassThru -Update |
- Expand-GzipArchive |
- Should -BeExactly ($testStrings + $testStrings)
- }
-
- It 'Can overwrite a compressed file from input bytes' {
- $testStrings | ConvertTo-GzipString -AsByteStream |
- Compress-GzipArchive -DestinationPath $path -PassThru -Force |
- Expand-GzipArchive |
- Should -BeExactly $testStrings
- }
- }
- }
-}
diff --git a/tests/PSVersionHelper.tests.ps1 b/tests/PSVersionHelper.tests.ps1
deleted file mode 100644
index 42807da..0000000
--- a/tests/PSVersionHelper.tests.ps1
+++ /dev/null
@@ -1,19 +0,0 @@
-$ErrorActionPreference = 'Stop'
-
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
-
-Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
-
-Describe 'PSVersionHelper Class' {
- It 'Should have the same value as $IsCoreCLR' {
- $property = [PSCompression.ZipEntryBase].Assembly.
- GetType('PSCompression.PSVersionHelper').
- GetProperty(
- 'IsCoreCLR',
- [System.Reflection.BindingFlags] 'NonPublic, Static')
-
- $property.GetValue($property) | Should -BeExactly ([bool] $IsCoreCLR)
- }
-}
diff --git a/tests/StringCompressionExpansionCommands.tests.ps1 b/tests/StringCompressionExpansionCommands.tests.ps1
new file mode 100644
index 0000000..acec54b
--- /dev/null
+++ b/tests/StringCompressionExpansionCommands.tests.ps1
@@ -0,0 +1,71 @@
+using namespace System.IO
+using namespace System.IO.Compression
+
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+
+Import-Module $manifestPath
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
+
+Describe 'String Compression & Expansion Commands' {
+ BeforeAll {
+ $conversionCommands = @{
+ 'ConvertFrom-BrotliString' = 'ConvertTo-BrotliString'
+ 'ConvertFrom-DeflateString' = 'ConvertTo-DeflateString'
+ 'ConvertFrom-GzipString' = 'ConvertTo-GzipString'
+ 'ConvertFrom-ZlibString' = 'ConvertTo-ZlibString'
+ }
+
+ $conversionCommands | Out-Null
+ }
+
+ Context 'ConvertFrom Commands' -Tag 'ConvertFrom Commands' {
+ It 'Should throw on a non b64 encoded input' {
+ $conversionCommands.Keys | ForEach-Object {
+ { 'foo' | & $_ } | Should -Throw -ExceptionType ([FormatException])
+ }
+ }
+ }
+
+ Context 'ConvertTo & ConvertFrom General Usage' -Tag 'ConvertTo & ConvertFrom General Usage' {
+ BeforeAll {
+ $content = 'hello', 'world', '!'
+ $content | Out-Null
+ }
+
+ It 'Can compress strings and expand strings' {
+ foreach ($level in [CompressionLevel].GetEnumNames()) {
+ $conversionCommands.Keys | ForEach-Object {
+ $encoded = $content | & $conversionCommands[$_] -CompressionLevel $level
+ $encoded | & $_ | Should -BeExactly $content
+ }
+ }
+ }
+
+ It 'Can compress strings outputting raw bytes' {
+ $conversionCommands.Keys | ForEach-Object {
+ [byte[]] $bytes = $content | & $conversionCommands[$_] -AsByteStream
+ $result = [Convert]::ToBase64String($bytes) | & $_
+ $result | Should -BeExactly $content
+ }
+ }
+
+ It 'Can expand b64 compressed strings and output a multi-line string' {
+ $contentAsString = $content -join [Environment]::NewLine
+ $conversionCommands.Keys | ForEach-Object {
+ $content | & $conversionCommands[$_] | & $_ -Raw |
+ ForEach-Object TrimEnd | Should -BeExactly $contentAsString
+ }
+ }
+
+ It 'Concatenates strings when the -NoNewLine switch is used' {
+ $contentAsString = -join $content
+ $conversionCommands.Keys | ForEach-Object {
+ $content | & $conversionCommands[$_] -NoNewLine | & $_ |
+ Should -BeExactly $contentAsString
+ }
+ }
+ }
+}
diff --git a/tests/ZipEntryBase.tests.ps1 b/tests/ZipEntryBase.tests.ps1
index 43afcad..c23aac3 100644
--- a/tests/ZipEntryBase.tests.ps1
+++ b/tests/ZipEntryBase.tests.ps1
@@ -1,10 +1,13 @@
-$ErrorActionPreference = 'Stop'
+using namespace System.IO
+using namespace System.IO.Compression
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
+$ErrorActionPreference = 'Stop'
+
+$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
+$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
+Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
Describe 'ZipEntryBase Class' {
BeforeAll {
@@ -15,37 +18,35 @@ Describe 'ZipEntryBase Class' {
It 'Can extract an entry' {
($zip | Get-ZipEntry -Type Archive).ExtractTo($TestDrive, $false) |
- Should -BeOfType ([System.IO.FileInfo])
+ Should -BeOfType ([FileInfo])
}
It 'Can overwrite a file when extracting' {
($zip | Get-ZipEntry -Type Archive).ExtractTo($TestDrive, $true) |
- Should -BeOfType ([System.IO.FileInfo])
+ Should -BeOfType ([FileInfo])
}
It 'Can extract a file from entries created from input Stream' {
Use-Object ($stream = $zip.OpenRead()) {
($stream | Get-ZipEntry -Type Archive).ExtractTo($TestDrive, $true)
- } | Should -BeOfType ([System.IO.FileInfo])
+ } | Should -BeOfType ([FileInfo])
}
It 'Can create a new folder in the destination path when extracting' {
$entry = $zip | Get-ZipEntry -Type Archive
- $file = $entry.ExtractTo(
- [System.IO.Path]::Combine($TestDrive, 'myTestFolder'),
- $false)
+ $file = $entry.ExtractTo([Path]::Combine($TestDrive, 'myTestFolder'), $false)
- $file.FullName | Should -BeExactly ([System.IO.Path]::Combine($TestDrive, 'myTestFolder', $entry.Name))
+ $file.FullName | Should -BeExactly ([Path]::Combine($TestDrive, 'myTestFolder', $entry.Name))
}
It 'Can extract folders' {
($zip | Get-ZipEntry -Type Directory).ExtractTo($TestDrive, $false) |
- Should -BeOfType ([System.IO.DirectoryInfo])
+ Should -BeOfType ([DirectoryInfo])
}
It 'Can overwrite folders when extracting' {
($zip | Get-ZipEntry -Type Directory).ExtractTo($TestDrive, $true) |
- Should -BeOfType ([System.IO.DirectoryInfo])
+ Should -BeOfType ([DirectoryInfo])
}
It 'Has a LastWriteTime Property' {
@@ -76,17 +77,17 @@ Describe 'ZipEntryBase Class' {
It 'Opens a ZipArchive on OpenRead() and OpenWrite()' {
Use-Object ($archive = ($zip | Get-ZipEntry).OpenRead()) {
- $archive | Should -BeOfType ([System.IO.Compression.ZipArchive])
+ $archive | Should -BeOfType ([ZipArchive])
}
Use-Object ($stream = $zip.OpenRead()) {
Use-Object ($archive = ($stream | Get-ZipEntry).OpenRead()) {
- $archive | Should -BeOfType ([System.IO.Compression.ZipArchive])
+ $archive | Should -BeOfType ([ZipArchive])
}
}
Use-Object ($archive = ($zip | Get-ZipEntry).OpenWrite()) {
- $archive | Should -BeOfType ([System.IO.Compression.ZipArchive])
+ $archive | Should -BeOfType ([ZipArchive])
}
}
diff --git a/tests/ZipEntryDirectory.tests.ps1 b/tests/ZipEntryDirectory.tests.ps1
deleted file mode 100644
index 85414ef..0000000
--- a/tests/ZipEntryDirectory.tests.ps1
+++ /dev/null
@@ -1,16 +0,0 @@
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
-
-Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
-
-Describe 'ZipEntryDirectory Class' {
- BeforeAll {
- $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force
- New-ZipEntry $zip.FullName -EntryPath afolder/
- }
-
- It 'Should be of type Directory' {
- ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.ZipEntryType]::Directory)
- }
-}
diff --git a/tests/ZipEntryFile.tests.ps1 b/tests/ZipEntryFile.tests.ps1
deleted file mode 100644
index ff5ad4f..0000000
--- a/tests/ZipEntryFile.tests.ps1
+++ /dev/null
@@ -1,34 +0,0 @@
-$ErrorActionPreference = 'Stop'
-
-$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
-$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
-
-Import-Module $manifestPath
-Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
-
-Describe 'ZipEntryFile Class' {
- BeforeAll {
- $zip = New-Item (Join-Path $TestDrive test.zip) -ItemType File -Force
- 'hello world!' | New-ZipEntry $zip.FullName -EntryPath helloworld.txt
- }
-
- It 'Should be of type Archive' {
- ($zip | Get-ZipEntry).Type | Should -BeExactly ([PSCompression.ZipEntryType]::Archive)
- }
-
- It 'Should Have a BaseName Property' {
- ($zip | Get-ZipEntry).BaseName | Should -BeOfType ([string])
- ($zip | Get-ZipEntry).BaseName | Should -BeExactly helloworld
- }
-
- It 'Should Have an Extension Property' {
- ($zip | Get-ZipEntry).Extension | Should -BeOfType ([string])
- ($zip | Get-ZipEntry).Extension | Should -BeExactly .txt
- }
-
- It 'Should Open the source zip' {
- Use-Object ($stream = ($zip | Get-ZipEntry).OpenRead()) {
- $stream | Should -BeOfType ([System.IO.Compression.ZipArchive])
- }
- }
-}
diff --git a/tests/shared.psm1 b/tests/shared.psm1
index 1d1d53e..d2fd5b3 100644
--- a/tests/shared.psm1
+++ b/tests/shared.psm1
@@ -1,47 +1,16 @@
-function Complete {
- [OutputType([System.Management.Automation.CompletionResult])]
- param([string] $Expression)
-
- end {
- [System.Management.Automation.CommandCompletion]::CompleteInput(
- $Expression,
- $Expression.Length,
- $null).CompletionMatches
- }
-}
-
-function Decode {
- param([byte[]] $bytes)
-
- end {
- try {
- $gzip = [System.IO.Compression.GZipStream]::new(
- ($mem = [System.IO.MemoryStream]::new($bytes)),
- [System.IO.Compression.CompressionMode]::Decompress)
+using namespace System.Management.Automation
+using namespace System.Runtime.InteropServices
+using namespace PSCompression
- $out = [System.IO.MemoryStream]::new()
- $gzip.CopyTo($out)
- }
- finally {
- if ($gzip -is [System.IDisposable]) {
- $gzip.Dispose()
- }
-
- if ($mem -is [System.IDisposable]) {
- $mem.Dispose()
- }
+function Complete {
+ param([string] $Expression)
- if ($out -is [System.IDisposable]) {
- $out.Dispose()
- [System.Text.UTF8Encoding]::new().GetString($out.ToArray())
- }
- }
- }
+ [CommandCompletion]::CompleteInput($Expression, $Expression.Length, $null).CompletionMatches
}
function Test-Completer {
param(
- [ArgumentCompleter([PSCompression.EncodingCompleter])]
+ [ArgumentCompleter([EncodingCompleter])]
[string] $Test
)
}
@@ -56,14 +25,52 @@ function Get-Structure {
}
}
-$osIsWindows = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform(
- [System.Runtime.InteropServices.OSPlatform]::Windows)
+function Build-Structure {
+ param(
+ [Parameter(ValueFromPipeline, Mandatory)]
+ [string] $Item,
+
+ [Parameter(Mandatory, Position = 0)]
+ [string] $Path)
+
+ begin {
+ $fileCount = $dirCount = 0
+ }
+ process {
+ $isFile = $Item.EndsWith('.txt')
+
+ $newItemSplat = @{
+ ItemType = ('Directory', 'File')[$isFile]
+ Value = Get-Random
+ Force = $true
+ Path = Join-Path $Path $Item
+ }
+
+ $null = New-Item @newItemSplat
+
+ if ($isFile) {
+ $fileCount++
+ return
+ }
+
+ $dirCount++
+ }
+ end {
+ $dirCount++ # Includes the folder itself
+
+ [pscustomobject]@{
+ File = $fileCount
+ Directory = $dirCount
+ }
+ }
+}
+$osIsWindows = [RuntimeInformation]::IsOSPlatform([OSPlatform]::Windows)
$osIsWindows | Out-Null
$exportModuleMemberSplat = @{
Variable = 'moduleName', 'manifestPath', 'osIsWindows'
- Function = 'Decode', 'Complete', 'Test-Completer', 'Get-Structure'
+ Function = 'Decode', 'Complete', 'Test-Completer', 'Get-Structure', 'Build-Structure'
}
Export-ModuleMember @exportModuleMemberSplat
diff --git a/tools/InvokeBuild.ps1 b/tools/InvokeBuild.ps1
index 4ece54d..8a2e74f 100644
--- a/tools/InvokeBuild.ps1
+++ b/tools/InvokeBuild.ps1
@@ -19,6 +19,10 @@ task BuildManaged {
try {
foreach ($framework in $ProjectInfo.Project.TargetFrameworks) {
+ if ($framework -match '^net4' -and -not $IsWindows) {
+ continue
+ }
+
Write-Host "Compiling for $framework"
dotnet @arguments --framework $framework
diff --git a/tools/ProjectBuilder/Pester.cs b/tools/ProjectBuilder/Pester.cs
index fccaac1..901ddeb 100644
--- a/tools/ProjectBuilder/Pester.cs
+++ b/tools/ProjectBuilder/Pester.cs
@@ -66,7 +66,7 @@ public string[] GetTestArgs(Version version)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- arguments.AddRange([ "-ExecutionPolicy", "Bypass" ]);
+ arguments.AddRange(["-ExecutionPolicy", "Bypass"]);
}
arguments.AddRange([
@@ -99,12 +99,12 @@ public string[] GetTestArgs(Version version)
if (File.Exists(unitCoveragePath))
{
- arguments.AddRange([ "--merge-with", unitCoveragePath ]);
+ arguments.AddRange(["--merge-with", unitCoveragePath]);
}
- if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") is "true")
+ if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true")
{
- arguments.AddRange([ "--source-mapping-file", sourceMappingFile ]);
+ arguments.AddRange(["--source-mapping-file", sourceMappingFile]);
File.WriteAllText(
sourceMappingFile,
$"|{_info.Root.FullName}{Path.DirectorySeparatorChar}=/_/");
diff --git a/tools/ProjectBuilder/Project.cs b/tools/ProjectBuilder/Project.cs
index 831c292..9afcd3a 100644
--- a/tools/ProjectBuilder/Project.cs
+++ b/tools/ProjectBuilder/Project.cs
@@ -2,6 +2,7 @@
using System.Collections;
using System.IO;
using System.Linq;
+using System.Text.RegularExpressions;
namespace ProjectBuilder;
@@ -15,7 +16,16 @@ public sealed class Project
public string[]? TargetFrameworks { get; internal set; }
- public string? TestFramework { get => TargetFrameworks.FirstOrDefault(); }
+ public string? TestFramework
+ {
+ get => _info.PowerShellVersion is { Major: 5, Minor: 1 }
+ ? TargetFrameworks
+ .Where(e => Regex.Match(e, "^net(?:4|standard)").Success)
+ .FirstOrDefault()
+ : TargetFrameworks
+ .Where(e => !e.StartsWith("net4"))
+ .FirstOrDefault();
+ }
private Configuration Configuration { get => _info.Configuration; }
diff --git a/tools/ProjectBuilder/ProjectInfo.cs b/tools/ProjectBuilder/ProjectInfo.cs
index b1f7b2c..044cce9 100644
--- a/tools/ProjectBuilder/ProjectInfo.cs
+++ b/tools/ProjectBuilder/ProjectInfo.cs
@@ -22,6 +22,8 @@ public sealed class ProjectInfo
public Pester Pester { get; }
+ public Version PowerShellVersion { get; }
+
public string? AnalyzerPath
{
get
@@ -41,8 +43,9 @@ public string? AnalyzerPath
private string? _analyzerPath;
- private ProjectInfo(string path)
+ private ProjectInfo(string path, Version psVersion)
{
+ PowerShellVersion = psVersion;
Root = AssertDirectory(path);
Module = new Module(
@@ -60,12 +63,14 @@ private ProjectInfo(string path)
public static ProjectInfo Create(
string path,
- Configuration configuration)
+ Configuration configuration,
+ Version psVersion)
{
- ProjectInfo builder = new(path)
+ ProjectInfo builder = new(path, psVersion)
{
Configuration = configuration
};
+
builder.Module.Manifest = GetManifest(builder);
builder.Module.Version = GetManifestVersion(builder);
builder.Project.Release = GetReleasePath(
@@ -167,14 +172,18 @@ private static string GetProjectFile(ProjectInfo builder) =>
private static Version? GetManifestVersion(ProjectInfo builder)
{
using PowerShell powershell = PowerShell.Create(RunspaceMode.CurrentRunspace);
- Hashtable? moduleInfo = powershell
- .AddCommand("Import-PowerShellDataFile")
- .AddArgument(builder.Module.Manifest?.FullName)
- .Invoke()
+ PSModuleInfo? moduleInfo = powershell
+ .AddCommand("Test-ModuleManifest")
+ .AddParameters(new Dictionary()
+ {
+ ["Path"] = builder.Module.Manifest?.FullName,
+ ["ErrorAction"] = "Ignore"
+ })
+ .Invoke()
.FirstOrDefault();
- return powershell.HadErrors
+ return powershell.Streams.Error is { Count: > 0 }
? throw powershell.Streams.Error.First().Exception
- : LanguagePrimitives.ConvertTo(moduleInfo?["ModuleVersion"]);
+ : moduleInfo.Version;
}
}