'Extract a single file from a ZIP archive

I'm using the next code to download some zip archive:

$client = new-object System.Net.WebClient
$client.DownloadFile("https://chromedriver.storage.googleapis.com/$LatestChromeRelease/chromedriver_win32.zip","D:\MyFolder.zip")

As the result I get the ZIP archive "MyFolder.zip" that contains a required file (lets imagine 'test.txt').

How I can extract this particular file from the ZIP archive into a given folder?



Solution 1:[1]

PowerShell 4+ has an Expand-Archive command but as of PS 7.2.3 it can only extract the archive completely. So extract it to a temporary directory and copy the file you are interested in.

If you have PS 5.1+ available, scroll down for a more efficient solution that uses .NET classes.

$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'

# Relative path of file in ZIP to extract. 
$fileToExtract = 'test.txt'

# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force

# Create a unique temporary directory
$tempDir = Join-Path ([IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString('n'))
$null = New-Item $tempDir -ItemType Directory

try {
    # Extract archive to temp dir
    Expand-Archive -LiteralPath $archivePath -DestinationPath $tempDir

    # Copy the file we are interested in
    $tempFilePath = Join-Path $tempDir $fileToExtract
    Copy-Item $tempFilePath $destinationDir 
}
finally {
    # Remove the temp dir
    if( Test-Path $tempDir ) { 
        Remove-Item $tempDir -Recurse -Force -EA Continue 
    }
}

With PS 5.1+ you can use .NET classes to directly extract a single file (without having to extract the whole archive):

# Load required .NET assemblies. Not necessary on PS Core 7+.
Add-Type -Assembly System.IO.Compression.FileSystem

$archivePath = 'D:\MyFolder.zip'
$destinationDir = 'D:\MyFolder'

# Relative path of file in ZIP to extract.
# Use FORWARD slashes as directory separator, e. g. 'subdir/test.txt'
$fileToExtract = 'test.txt'

# Create destination dir if not exist.
$null = New-Item $destinationDir -ItemType Directory -Force

# Convert (possibly relative) paths for safe use with .NET APIs
$resolvedArchivePath    = Convert-Path -LiteralPath $archivePath
$resolvedDestinationDir = Convert-Path -LiteralPath $destinationDir

$archive = [IO.Compression.ZipFile]::OpenRead( $resolvedArchivePath )
try {
    # Locate the desired file in the ZIP archive.
    # Replace $_.Fullname by $_.Name if file shall be found in any sub directory.
    if( $foundFile = $archive.Entries.Where({ $_.FullName -eq $fileToExtract }, 'First') ) {
    
        # Combine destination dir path and name of file in ZIP
        $destinationFile = Join-Path $resolvedDestinationDir $foundFile.Name
        
        # Extract the file.
        [IO.Compression.ZipFileExtensions]::ExtractToFile( $foundFile[ 0 ], $destinationFile )
    }
    else {
        Write-Error "File not found in ZIP: $fileToExtract"
    }
}
finally {
    # Close the archive so the file will be unlocked again.
    if( $archive ) {
        $archive.Close()
        $archive.Dispose()
    }
}

Notes:

  • Convert-Path should be used when passing PowerShell paths that might be relative paths, to .NET APIs. The .NET framework has its own current directory, which doesn't necessarily match PowerShell's. Using Convert-Path we convert to absolute paths so the current directory of .NET is no longer relevant.
  • .Where and .ForEach are PowerShell intrinsic methods that are available on all objects. They are similar to the Where-Object and ForEach-Object commands but more efficient. Passing 'First' as the 2nd argument to .Where stops searching as soon as we have found the file.
  • Note that .Where always outputs a collection, even if only a single element matches. This is contrary to Where-Object which returns a single object if only a single element matches. So we have to write $foundFile[ 0 ] when passing it to function ExtractToFile, instead of just $foundFile which would be an array.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1