Tecman
Tecman

Reputation: 3009

Archive folder without some subfolders and files using PowerShell

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

Answers (1)

Rich Moss
Rich Moss

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

Related Questions