'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-Pathshould 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. UsingConvert-Pathwe convert to absolute paths so the current directory of .NET is no longer relevant..Whereand.ForEachare PowerShell intrinsic methods that are available on all objects. They are similar to theWhere-ObjectandForEach-Objectcommands but more efficient. Passing'First'as the 2nd argument to.Wherestops searching as soon as we have found the file.- Note that
.Wherealways outputs a collection, even if only a single element matches. This is contrary toWhere-Objectwhich returns a single object if only a single element matches. So we have to write$foundFile[ 0 ]when passing it to functionExtractToFile, instead of just$foundFilewhich 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 |
