嵌套的 zip 内容列表

Nested zip contents listing

我一直在做一个小项目,列出压缩在嵌套 zip 文件中的文件。 我编写了一个脚本来执行此操作,但前提是已知 zip 文件的深度。 在下面的示例中,zip 文件中有额外的 zip,然后在其中一个中有 anthoer。

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
}

我要求你帮我修改它以容纳未知深度的内部 zip 文件。这可能吗?

这里有一个递归的小例子,基本上,你遍历 ZipFile.Entries 属性 并检查每个项目的扩展名是否是 .zip,如果是,则将该条目传递给您的函数。

编辑: Un-deleting 这个答案主要是为了展示如何使用 递归函数 ,我之前的答案是不准确的。我正在使用 [ZipFile]::OpenRead(..) 读取嵌套的 .zip 文件,这些文件似乎在 Linux (.NET Core) 上可以正常工作,但在使用 Windows PowerShell 时显然不起作用。正确的方法是使用 [ZipArchive]::new($nestedZip.Open()),如 所示。

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

下面是一个关于如何使用 Queue 对象执行此操作的示例,它允许您一次性递归遍历 zip 文件的所有深度。

根据要求,这里有一些评论来解释发生了什么。

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"

# All records will be stored here
$Output = [System.Collections.Generic.List[PSObject]]::new()

# Main logic. Used when looking at the root zip and any zip entries.
# ScriptBlock is used to prevent code duplication.
$ProcessEntries = {
    Param($Entries)
    $Entries | % {
        # Put all zip in the queue for future processing
        if ([System.IO.Path]::GetExtension($entry) -eq '.zip') { $Queue.Enqueue($_) }
        # Add a Source Zip property with the parent zip since we want this informations in the csv export and it is not available otherwise.
        $_ | Add-Member -MemberType NoteProperty -Name 'Source Zip' -Value $zip.name
        # Every entries, zip or not, need to be part of the output
        $output.Add($_) 
    }
}

# Your initial Get-ChildItem to find zip file implicate there could be multiple root zip files, so a loop is required.
Foreach ($zip in $zipFiles) {
    $archive = [System.IO.Compression.zipfile]::OpenRead($zip.fullname)
    # The $ProcessEntries scriptblock is invoked to fill the Queue and the output.
    . $ProcessEntries $archive.Entries
    
    # Should the Zip file have no zip entries, this loop will never be entered.
    # Otherwise, the loop will continue as long as zip entries are detected while processing any child zip.
    while ($Queue.Count -gt 0) {
        # Removing item from the queue to avoid reprocessing it again.
        $Item = $Queue.Dequeue()
        $archive = New-object System.IO.Compression.ZipArchive ($Item.open())
        # We call the main scriptblock again to fill the queue and the output.
        . $ProcessEntries $archive.Entries
        
    }

}

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

参考资料

Queue