Home > OS >  Nested zip contents listing
Nested zip contents listing

Time:02-18

I've been working on a little side project of listing files compressed in nested zip files. I've cooked up a script that does just that, but only if the depth of zip files is known. In in example below the zip file has additional zips in it and then anthoer in one of them.

Add-Type -AssemblyName System.IO.Compression.Filesystem
$path = "PATH"
$CSV_Path = "CSV_PATH"
$zipFile = Get-ChildItem $path -recurse -Filter "*.zip"
$rootArchive = [System.IO.Compression.zipfile]::OpenRead($zipFile.fullname)
$rootArchive.Entries | Select @{l = 'Source Zip'; e = {} }, @{l = "FullName"; e = { $_.FullName.Substring(0, $rootArchive.Fullname.Lastindexof('\')) } }, Name | Export-csv $CSV_Path -notypeinformation

$archivesLevel2 = $rootArchive.Entries | Where { $_.Name -like "*.zip" } 
foreach ($archive in $archivesLevel2)
{
    (New-object System.IO.Compression.ZipArchive ($archive.Open())).Entries | Select @{l = 'Source Zip'; e = { $archive.name } }, @{l = "FullName"; e = { $archive.FullName.Substring(0, $_.Fullname.Lastindexof('\')) } }, Name | Export-Csv $CSV_Path -NoTypeInformation -append;
    New-object System.IO.Compression.ZipArchive($archive.Open()) -OutVariable  lastArchiveLevel2
}

$archivesLevel3 = $lastArchiveLevel2.entries | Where { $_.Name -like "*.zip" }
foreach ($archive in $archivesLevel3) 
{
    (New-Object System.IO.Compression.ZipArchive ($archive.Open())).Entries | Select @{l = 'Source Zip'; e = { $archive.name } }, @{l = "FullName"; e = { $archive.FullName.Substring(0, $_.Fullname.Lastindexof('\')) } }, Name | Export-Csv $CSV_Path -NoTypeInformation -append
}

What I ask of you is to help me modify this to accomodate an unknown depth of inner zip files. Is that even possible?

CodePudding user response:

Here's an example on how to do it using a Queue object, which allow you to recursively go through all depths of your zip file in one go.

Add-Type -AssemblyName System.IO.Compression.Filesystem
$path = "PATH"
$CSV_Path = "CSV_PATH"
$Queue = [System.Collections.Queue]::New()
$zipFiles = Get-ChildItem $path -recurse -Filter "*.zip"

$Output = [System.Collections.Generic.List[PSObject]]::new()

$ProcessEntries = {
    Param($Entries)
    $Entries | % {
        if ([System.IO.Path]::GetExtension($entry) -eq '.zip') { $Queue.Enqueue($_) }
        $_ | Add-Member -MemberType NoteProperty -Name 'Source Zip' -Value $zip.name
        $output.Add($_) 
    }
}


Foreach ($zip in $zipFiles) {
    $archive = [System.IO.Compression.zipfile]::OpenRead($zip.fullname)
    . $ProcessEntries $archive.Entries
    
    while ($Queue.Count -gt 0) {
        $Item = $Queue.Dequeue()
        $archive = New-object System.IO.Compression.ZipArchive ($Item.open())
        
        . $ProcessEntries $archive.Entries
        
    }

}

$Output | Select 'Source Zip', FullName, Name | Export-Csv $CSV_Path -NoTypeInformation

CodePudding user response:

Here you have a little example of how recursion would look like, basically, you loop over the .Entries property of ZipFile and check if the extension of each item is .zip, if it is, then you pass that entry to your function.

EDIT: Un-deleting this answer mainly to show how this could be approached using a recursive function, my previous answer was inaccurate. I was using [ZipFile]::OpenRead(..) to read the nested .zip files which seemed to work correctly on Linux (.NET Core) however it clearly does not work when using Windows PowerShell. The correct approach would be to use [ZipArchive]::new($nestedZip.Open()) as Sage Pourpre's helpful answer shows.

using namespace System.IO
using namespace System.IO.Compression

function Get-ZipFile {
[cmdletbinding()]
param(
    [parameter(ValueFromPipeline)]
    [object]$Path,
    [parameter(DontShow)]
    [int]$Nesting = -1
)
    begin { $Nesting   }
    process {
        try
        {
            $zip = if(-not $Nesting) {
                [ZipFile]::OpenRead($Path)
            }
            else {
                [ZipArchive]::new($Path.Open())
            }
            foreach($entry in $zip.Entries) {
                [pscustomobject]@{
                    Nesting = $Nesting
                    Parent = $Path.Name
                    Contents = $entry.FullName
                }
                if([Path]::GetExtension($entry) -eq '.zip') {
                    Get-ZipFile -Path $entry -Nesting $Nesting
                }
            }
        }
        catch
        {
            $PSCmdlet.WriteError($_)
        }
        finally
        {
            if($null -ne $zip) {
                $zip.Dispose()
            }
        }
    }
}

Get-ChildItem *.zip | Get-ZipFile
  • Related