'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. UsingConvert-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 theWhere-Object
andForEach-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 toWhere-Object
which 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$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 |