首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何使用超过2GB的powershell压缩文件?

如何使用超过2GB的powershell压缩文件?
EN

Stack Overflow用户
提问于 2022-06-13 19:07:57
回答 1查看 1.6K关注 0票数 6

我正在进行一个压缩文件的项目,这些文件从几个mb到几个gb的大小不等,我正在尝试使用powershell将它们压缩到一个.zip中。我遇到的主要问题是,使用有一个2GB的上限,以单个文件大小,我想知道是否有另一种方法来压缩文件。

编辑:

因此,对于这个项目,我们希望实现一个系统,从outlook获取.pst文件,并将它们压缩到.zip中并上传到服务器上。一旦它们被上传,它们将被从一个新设备中取出,并再次解压缩到一个.pst文件中。

EN

回答 1

Stack Overflow用户

发布于 2022-06-14 03:18:43

注意事项

对此函数的进一步更新将发布给正式的GitHub回购以及PowerShell画廊。这个答案中的代码将不再维护

欢迎投稿,如果你想要投稿,叉回购,并提交一个拉请求与更改。

解释PowerShell Docs上为 压缩档案命名的限制

Compress-Archive cmdlet使用微软.NET API System.IO.Compression.ZipArchive压缩文件。最大文件大小为2GB,因为底层API有限制。

之所以会发生这种情况,是因为此cmdlet使用内存流将字节保存在内存中,然后将它们写入文件。检查cmdlet产生的InnerException,我们可以看到:

代码语言:javascript
运行
复制
System.IO.IOException: Stream was too long.
   at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   at CallSite.Target(Closure , CallSite , Object , Object , Int32 , Object )

如果我们尝试从大于2gb的文件中读取所有字节,我们也会看到类似的问题。

代码语言:javascript
运行
复制
Exception calling "ReadAllBytes" with "1" argument(s): "The file is too long.
This operation is currently limited to supporting files less than 2 gigabytes in size."

巧合的是,我们看到了与System.Array相同的限制

仅限.NET框架:默认情况下,数组的最大大小是2G (GB)。

这个问题中还指出了另一个限制,如果另一个进程对文件有句柄,则Compress-Archive无法压缩。

如何繁殖?

代码语言:javascript
运行
复制
# cd to a temporary folder and
# start a Job which will write to a file
$job = Start-Job {
    0..1000 | ForEach-Object {
        "Iteration ${_}:" + ('A' * 1kb)
        Start-Sleep -Milliseconds 200
    } | Set-Content .\temp\test.txt
}

Start-Sleep -Seconds 1
# attempt to compress
Compress-Archive .\temp\test.txt -DestinationPath test.zip
# Exception:
# The process cannot access the file '..\test.txt' because it is being used by another process.
$job | Stop-Job -PassThru | Remove-Job
Remove-Item .\temp -Recurse

为了克服这个问题,并模拟资源管理器在压缩其他进程使用的文件时的行为,在打开一个[FileShare] 'ReadWrite,Delete‘时,下面发布的函数将默认为FileStream

为了解决这个问题,有两个解决办法:

  • 简单的解决办法是使用方法。使用这种静态方法有三个限制:
    1. 源必须是目录,不能压缩单个文件。
    2. 源文件夹上的所有文件(递归)都将被压缩为,我们不能选择/过滤要压缩的文件。
    3. 不可能更新现有Zip的条目。

值得注意的是,如果您需要在Windows PowerShell (.NET Framework)中使用.NET,则必须有对System.IO.Compression.FileSystem的引用。参见内联注释。

代码语言:javascript
运行
复制
# Only needed if using Windows PowerShell (.NET Framework):
Add-Type -AssemblyName System.IO.Compression.FileSystem

[IO.Compression.ZipFile]::CreateFromDirectory($sourceDirectory, $destinationArchive)
  • 它自己解决的代码,将使用一个函数来完成创建ZipArchive和相应的ZipEntries的所有手动过程。

该函数应该能够处理与ZipFile.CreateFromDirectory方法相同的压缩,但也允许过滤文件和文件夹压缩,同时保持文件/文件夹结构不受影响的。

这里.可以找到文档和用法示例。

代码语言:javascript
运行
复制
using namespace System.IO
using namespace System.IO.Compression
using namespace System.Collections.Generic

Add-Type -AssemblyName System.IO.Compression

function Compress-ZipArchive {
    [CmdletBinding(DefaultParameterSetName = 'Path')]
    [Alias('zip', 'ziparchive')]
    param(
        [Parameter(ParameterSetName = 'PathWithUpdate', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'PathWithForce', Mandatory, Position = 0, ValueFromPipeline)]
        [Parameter(ParameterSetName = 'Path', Mandatory, Position = 0, ValueFromPipeline)]
        [string[]] $Path,

        [Parameter(ParameterSetName = 'LiteralPathWithUpdate', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPathWithForce', Mandatory, ValueFromPipelineByPropertyName)]
        [Parameter(ParameterSetName = 'LiteralPath', Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('PSPath')]
        [string[]] $LiteralPath,

        [Parameter(Position = 1, Mandatory)]
        [string] $DestinationPath,

        [Parameter()]
        [CompressionLevel] $CompressionLevel = [CompressionLevel]::Optimal,

        [Parameter(ParameterSetName = 'PathWithUpdate', Mandatory)]
        [Parameter(ParameterSetName = 'LiteralPathWithUpdate', Mandatory)]
        [switch] $Update,

        [Parameter(ParameterSetName = 'PathWithForce', Mandatory)]
        [Parameter(ParameterSetName = 'LiteralPathWithForce', Mandatory)]
        [switch] $Force,

        [Parameter()]
        [switch] $PassThru
    )

    begin {
        $DestinationPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($DestinationPath)
        if([Path]::GetExtension($DestinationPath) -ne '.zip') {
            $DestinationPath = $DestinationPath + '.zip'
        }

        if($Force.IsPresent) {
            $fsMode = [FileMode]::Create
        }
        elseif($Update.IsPresent) {
            $fsMode = [FileMode]::OpenOrCreate
        }
        else {
            $fsMode = [FileMode]::CreateNew
        }

        $ExpectingInput = $null
    }
    process {
        $isLiteral  = $false
        $targetPath = $Path

        if($PSBoundParameters.ContainsKey('LiteralPath')) {
            $isLiteral  = $true
            $targetPath = $LiteralPath
        }

        if(-not $ExpectingInput) {
            try {
                $destfs = [File]::Open($DestinationPath, $fsMode)
                $zip    = [ZipArchive]::new($destfs, [ZipArchiveMode]::Update)
                $ExpectingInput = $true
            }
            catch {
                $zip, $destfs | ForEach-Object Dispose
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }

        $queue = [Queue[FileSystemInfo]]::new()

        foreach($item in $ExecutionContext.InvokeProvider.Item.Get($targetPath, $true, $isLiteral)) {
            $queue.Enqueue($item)

            $here = $item.Parent.FullName
            if($item -is [FileInfo]) {
                $here = $item.Directory.FullName
            }

            while($queue.Count) {
                try {
                    $current = $queue.Dequeue()
                    if($current -is [DirectoryInfo]) {
                        $current = $current.EnumerateFileSystemInfos()
                    }
                }
                catch {
                    $PSCmdlet.WriteError($_)
                    continue
                }

                foreach($item in $current) {
                    try {
                        if($item.FullName -eq $DestinationPath) {
                            continue
                        }

                        $relative = $item.FullName.Substring($here.Length + 1)
                        $entry    = $zip.GetEntry($relative)

                        if($item -is [DirectoryInfo]) {
                            $queue.Enqueue($item)
                            if(-not $entry) {
                                $entry = $zip.CreateEntry($relative + '\', $CompressionLevel)
                            }
                            continue
                        }

                        if(-not $entry) {
                            $entry = $zip.CreateEntry($relative, $CompressionLevel)
                        }

                        $sourcefs = $item.Open([FileMode]::Open, [FileAccess]::Read, [FileShare] 'ReadWrite, Delete')
                        $entryfs  = $entry.Open()
                        $sourcefs.CopyTo($entryfs)
                    }
                    catch {
                        $PSCmdlet.WriteError($_)
                    }
                    finally {
                        $entryfs, $sourcefs | ForEach-Object Dispose
                    }
                }
            }
        }
    }
    end {
        $zip, $destfs | ForEach-Object Dispose

        if($PassThru.IsPresent) {
            $DestinationPath -as [FileInfo]
        }
    }
}
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72607926

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档