0

In other words, does PowerShell's Expand-Archive have an equivalent to unzip's -j command-line argument? If not, are there alternatives on Windows?

I have tried Expand-Archive -Path thing.zip -DestinationPath "somepath" -Force, which just puts the directory structure in another folder called somepath.

SRSR333
  • 187
  • 4
  • 15
  • so you want everything in the same destination folder? how do you deal with possible file collision? – Santiago Squarzon Nov 30 '22 at 03:45
  • can you show us how the structure is listed in the archive or zip file and then also list what is your outcome or expected result? This will help us identify your needs. – KojTug Nov 30 '22 at 04:48

2 Answers2

1

This function will do what you want, obviously handling of possible file collision is not implemented, up to you how you want to implement that. Currently, if a file already exists with the same name it will give you an error and skip it. The function is a simplified version of the one from this answer which does actually keep the folder structure.

If no argument is passed to the -DestinationPath parameter, the zip entries will be extracted to the current location.

using namespace System.IO
using namespace System.IO.Compression

function Expand-ZipArchive {
    [CmdletBinding(DefaultParameterSetName = 'Path')]
    param(
        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [string] $Path,

        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('PSPath')]
        [string] $LiteralPath,

        [Parameter()]
        [string] $DestinationPath
    )

    begin {
        Add-Type -AssemblyName System.IO.Compression
        $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)
    }
    process {
        $arguments = switch($PSCmdlet.ParameterSetName) {
            Path { $Path, $false, $false }
            LiteralPath { $LiteralPath, $false, $true }
        }

        $null = [Directory]::CreateDirectory($DestinationPath)

        foreach($item in $ExecutionContext.InvokeProvider.Item.Get.Invoke($arguments)) {
            try {
                $fileStream = $item.Open([FileMode]::Open)
                $zipArchive = [ZipArchive]::new($fileStream, [ZipArchiveMode]::Read)

                foreach($entry in $zipArchive.Entries) {
                    try {
                        # if it's a folder, exclude it
                        if(-not $entry.Name) {
                            continue
                        }

                        $path = [Path]::Combine($DestinationPath, $entry.Name)
                        # will throw if a file with same name exists, intended
                        # error handling should be implemented in `catch` block
                        $fs   = [FileStream]::new($path, [FileMode]::CreateNew)
                        $wrappedStream = $entry.Open()
                        $wrappedStream.CopyTo($fs)
                    }
                    catch {
                        $PSCmdlet.WriteError($_)
                    }
                    finally {
                        $fs, $wrappedStream | ForEach-Object Dispose
                    }
                }
            }
            catch {
                $PSCmdlet.WriteError($_)
            }
            finally {
                $zipArchive, $fileStream | ForEach-Object Dispose
            }
        }
    }
}

Expand-ZipArchive .\myZip.zip
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37
0

As a PowerShell-only way you could extract the archive to a temporary directory and then move the files to the final location, discarding directory structure.

$archiveName = 'test.zip'
$destination = 'test'

# Create temp path as a sub directory of actual destination path, so the files don't 
# need to be moved (potentially) across drives.
$destinationTemp = Join-Path $destination "~$((New-Guid).ToString('n'))"

# Create temp directory
$null = New-Item $destinationTemp -ItemType Directory

# Extract to temp dir
Expand-Archive $archiveName -DestinationPath $destinationTemp

# Move files from temp dir to actual destination, discarding directory structure
Get-ChildItem $destinationTemp -File -Recurse | Move-Item -Destination $destination

# Remove temp dir
Remove-Item $destinationTemp -Recurse -Force

With PowerShell 7+, you could even move each file immediately after extraction, using the new -PassThru switch of Expand-Archive:

$archiveName = 'test.zip'
$destination = 'test'

# Create temp path as a sub directory of actual destination path, so the files don't 
# need to be moved (potentially) across drives.
$destinationTemp = Join-Path $destination "~$((New-Guid).ToString('n'))"

# Create temp directory
$null = New-Item $destinationTemp -ItemType Directory

# Expand to temp dir and move to final destination, discarding directory structure
Expand-Archive $archiveName -DestinationPath $destinationTemp -PassThru | 
    Where-Object -not PSIsContainer | Move-Item -Destination $destination

# Remove temp dir
Remove-Item $destinationTemp -Recurse -Force
zett42
  • 25,437
  • 3
  • 35
  • 72