compress-archive and preserve relative paths compress-archive and preserve relative paths powershell powershell

compress-archive and preserve relative paths


It appears that Compress-Archive (as of Windows PowerShell v5.1) doesn't support what you want:

Targeting a folder recursively adds that folder's subtree to the archive, but only by the target folder's name (which becomes a child folder inside the archive), not its path.

Specifically,

Compress-Archive -Path scripts\module2 -DestinationPath tmp.zip

will (recursively) store the contents of scripts\module2 in tmp.zip, but not with archive-internal path .\scripts\module2, just with .\module2 - the target folder's name (the last input path component).

The implication is that you'd have to pass folder scripts instead to get the desired archive-internal path, but that would invariably include the entire subtree of scripts, given that Compress-Archive offers no inclusion/exclusion mechanism.


One - cumbersome - option is to recreate the desired hierarchy in, say, the $env:TEMP folder, copy the target folder there, run Compress-Archive against the root of the recreated hierarchy, and then clean up:

New-Item -Force -ItemType Directory $env:TEMP/scriptsCopy-Item -Recurse -Force scripts/module2 $env:TEMP/scriptsCompress-Archive -LiteralPath $env:TEMP/scripts -DestinationPath tmp.zipRemove-Item $env:TEMP/Scripts -Recurse -Whatif

Otherwise, you may be able to find a solution:

  • by using the .NET v4.5+ [System.IO.Compression.ZipFile] class directly; you can load it into your session with Add-Type -Assembly System.IO.Compression.FileSystem (not necessary in PowerShell Core).

  • by using external programs such as 7-Zip,


I wanted to do this without having to copy the full structure to a temp directory.

#build list of files to compress$files = @(Get-ChildItem -Path .\procedimentos -Recurse | Where-Object -Property Name -EQ procedimentos.xlsx);$files += @(Get-ChildItem -Path .\procedimentos -Recurse | Where-Object -Property Name -CLike procedimento_*_fs_*_d_*.xml);$files += @(Get-ChildItem -Path .\procedimentos -Recurse | Where-Object -Property FullName -CLike *\documentos_*_fs_*_d_*);# exclude directory entries and generate fullpath list$filesFullPath = $files | Where-Object -Property Attributes -CContains Archive | ForEach-Object -Process {Write-Output -InputObject $_.FullName}#create zip file$zipFileName = 'procedimentos.zip'$zip = [System.IO.Compression.ZipFile]::Open((Join-Path -Path $(Resolve-Path -Path ".") -ChildPath $zipFileName), [System.IO.Compression.ZipArchiveMode]::Create)#write entries with relative paths as namesforeach ($fname in $filesFullPath) {    $rname = $(Resolve-Path -Path $fname -Relative) -replace '\.\\',''    echo $rname    $zentry = $zip.CreateEntry($rname)    $zentryWriter = New-Object -TypeName System.IO.BinaryWriter $zentry.Open()    $zentryWriter.Write([System.IO.File]::ReadAllBytes($fname))    $zentryWriter.Flush()    $zentryWriter.Close()}# clean upGet-Variable -exclude Runspace | Where-Object {$_.Value -is [System.IDisposable]} | Foreach-Object {$_.Value.Dispose(); Remove-Variable $_.Name};


The cumbersome technique mklement0 mentioned worked for me. Below is the script I created to support a list of various files mixed with folders.

# Compress LFS based files into a zip# To use#  1. place this script in the root folder#  2. modify the contents of $lfsAssetFiles to point to files relative to this root folder#  3. modify $zipDestination to be where you want the resultant zip to be placed# based off of https://stackoverflow.com/a/51394271# this should match files being .gitignored$lfsAssetFiles = "\Assets\Project\Plugins\x32","\Assets\Project\Plugins\x64\HugePlugin.dll"# This is where the contents of the zip file will be structured, because placing them inside of a specific folder of the zip is difficult otherwise$zipStruct = $PSScriptRoot + "\zipStruct"# the actual zip file that will be created$zipDestination = "C:\Dropbox\GitLfsZip\ProjectNameLfs.zip"# remove files from previous runs of this scriptIf(Test-path $zipStruct) {Remove-item $zipStruct -Recurse}If(Test-path $zipDestination) {Remove-item $zipDestination}Foreach ($entry in $lfsAssetFiles){  # form absolute path to source each file to be included in the zip  $sourcePath = $PSScriptRoot + $entry;  # get the parent directories of the path. If the entry itself is a directory, we still only need the parent as the directory will be created when it is copied over.  $entryPath = Split-Path -Parent $entry  # form what the path will look like in the destination  $entryPath = $zipStruct + $entryPath  # ensure the folders to the entry path exist  $createdPath = New-Item -Force -ItemType Directory $entryPath  # copy the file or directory  Copy-Item -Recurse -Force $sourcePath $createdPath}# create a zip file https://blogs.technet.microsoft.com/heyscriptingguy/2015/page/59/Add-Type -AssemblyName "system.io.compression.filesystem"[io.compression.zipfile]::CreateFromDirectory($zipStruct, $zipDestination)# Compress-Archive doesn't work here because it includes the "zipStruct" folder: Compress-Archive -Path $zipStruct -DestinationPath $zipDestination