Reputation: 3009
I need to archive a folder without some subfolders and files using PowerShell. My file/folder exclusions can occur on any level of hierarchy. To explain, here is a simple example for a WinForms VS project. If we open it in VS and build, VS creates the bin/obj subfolders with executable contents, the hidden .vs folder with user settings, and maybe *.user files for the projects included into the solution. I want to archive such a VS solution folder without all those file and folder items that can be recreated the next time when we build the solution.
It is done very easily with 7-Zip using its -x! command line switch:
"C:\Program Files\7-Zip\7z.exe" a -tzip "D:\Temp\WindowsFormsApp1.zip" "D:\Temp\WindowsFormsApp1\" -r -x!bin -x!obj -x!.vs -x!*.suo -x!*.user
However, I couldn't build an equivalent PowerShell script. The best thing I got was something like this:
$exclude = "bin", "obj", ".vs", "*.suo", "*.user"
$files = Get-ChildItem -Path $path -Exclude $exclude -Force
Compress-Archive -Path $files -DestinationPath $dest -Force
If I execute this script, the exclusion list works only for the subfolders of the first hierarchy level. If I add the -Recurse switch to the Get-ChildItem cmdlet in my script or try to filter the files/folders using Where-Object, I lose the folder hierarchy in the archive.
Is there a solution to my problem? I need to solve the problem using solely PowerShell without any external tools.
Upvotes: 3
Views: 2379
Reputation: 2384
This is a similar problem to how-to-compress-log-files-older-than-30-days-in-windows.
The ArchiveOldLogs.ps1 script will preserve folder structure without the need for intermediate copying.
You can change the -Filter
parameter to exclude certain files by name rather than date:
$filter = {($_.Name -notlike '*.vs') -and ($_.Name -notlike '*.suo') -and ($_.Name -notlike '*.user') -and ($_.FullName -notlike '*bin\*') -and ($_.FullName -notlike '*obj\*')}
.\ArchiveOldLogs.ps1 -FileSpecs @('*.*') -Filter $filter -DeleteAfterArchiving:$false
Here's a minimal example that doesn't include the fancy progress bar, doesn't prevent duplicates within the archive, and doesn't delete archived files:
$ParentFolder = 'C:\projects\Code\' #files will be stored with a path relative to this folder
$ZipPath = 'c:\temp\projects.zip' #the zip file should not be under $ParentFolder or an exception will be raised
$filter = {($_.Name -notlike '*.vs') -and ($_.Name -notlike '*.suo') -and ($_.Name -notlike '*.user') -and ($_.FullName -notlike '*bin\*') -and ($_.FullName -notlike '*obj\*')}
@( 'System.IO.Compression','System.IO.Compression.FileSystem') | % { [void][Reflection.Assembly]::LoadWithPartialName($_) }
Push-Location $ParentFolder #change to the parent folder so we can get $RelativePath
$FileList = (Get-ChildItem '*.*' -File -Recurse | Where-Object $Filter) #use the -File argument because empty folders can't be stored
Try{
$WriteArchive = [IO.Compression.ZipFile]::Open( $ZipPath,'Update')
ForEach ($File in $FileList){
$RelativePath = (Resolve-Path -LiteralPath "$($File.FullName)" -Relative) -replace '^.\\' #trim leading .\ from path
Try{
[IO.Compression.ZipFileExtensions]::CreateEntryFromFile($WriteArchive, $File.FullName, $RelativePath, 'Optimal').FullName
}Catch{ #Single file failed - usually inaccessible or in use
Write-Warning "$($File.FullName) could not be archived. `n $($_.Exception.Message)"
}
}
}Catch [Exception]{ #failure to open the zip file
Write-Error $_.Exception
}Finally{
$WriteArchive.Dispose() #always close the zip file so it can be read later
}
Pop-Location
Upvotes: 2