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

2023-11-22

我正在开发一个项目来压缩从几个 mb 到几个 GB 大小的文件,我正在尝试使用 powershell 将它们压缩成 .zip。我遇到的主要问题是使用压缩存档对单个文件大小有 2 GB 上限,我想知道是否有另一种方法来压缩文件。

Edit:

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


NOTE

此功能的进一步更新将发布至官方GitHub 仓库以及PowerShell 画廊。这个答案中的代码将不再维护.

我们非常欢迎贡献,如果您想贡献,请分叉存储库并提交包含更改的拉取请求。


解释上面提到的限制PowerShell 文档用于Compress-Archive:

The Compress-Archivecmdlet 使用 Microsoft .NET APISystem.IO.Compression.ZipArchive来压缩文件。由于底层 API 的限制,最大文件大小为 2 GB。

发生这种情况是因为,大概(因为5.1版本是闭源的),此 cmdlet 使用内存流 to 将所有 zip 存档条目保存在内存中在将 zip 存档写入文件之前。检查内部异常由cmdlet生成我们可以看到:

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 )

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

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 Framework:默认情况下,数组的最大大小为 2 GB。


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

如何重现?

# 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.


要解决此问题,有两种解决方法:

  • The easy workaround is to use the ZipFile.CreateFromDirectory Method. There are 3 limitations while using this static method:
    1. 来源必须是一个目录,单个文件无法压缩。
    2. 源文件夹中的所有文件(递归)将被压缩,我们无法选择/过滤要压缩的文件。
    3. 这是不可能的Update现有 Zip 存档的条目。

值得注意的是,如果您需要使用ZipFile Class在 Windows PowerShell (.NET Framework) 中,必须引用System.IO.Compression.FileSystem。请参阅内嵌评论。

# 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方法还允许过滤文件和文件夹进行压缩,同时保留文件/文件夹结构未受影响.

可以找到文档以及使用示例here.

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]
        }
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 powershell 压缩超过 2 GB 的文件? 的相关文章

随机推荐

  • 在主线程中运行代码

    这与许多问题相似 但不是rly 我需要类似的东西BeginInvoke适用于 Winforms 但不仅仅适用于 winforms 所以我需要单一方法 适用于任何类型的应用程序 所以我打电话 void ExecuteInMainContext
  • cpp 空数组声明

    你好 我有以下测试代码 我对 cpp 感到困惑 如果你在library h中声明一个带有空元素子句的数组 编译器会选择什么 它也没有抱怨 我使用 Cygwin 在library cpp中 我将值分配给两个元素 编译器是否假设一个包含一个元素
  • Nullable DateTimes 和 AddDays() 扩展

    我有一个日期时间变量 可以为 null 或日期时间 我认为可以为 null 的 DateTime 类型可以工作 但我收到一条错误消息 告诉我说 Nullable
  • 为整个应用程序应用圆角

    如何实现应用于整个视图的圆角 如屏幕截图所示 请注意 导航栏和键盘角都是圆角的 我尝试过设置cornerRadius 10 and masksToBounds YES对彼此而言window layer and window rootView
  • Django - 导入错误:没有名为 *.urls 的模块

    我正在努力通过官方 Django 教程并使用 Django 版本 1 6 1 Python 2 7 6 稍微调整以满足我自己的需要 我现在正在映射 URL 但当很明显有一个模块内有一个适当命名的文件时 我总是收到 没有名为customers
  • 使用带有回调函数的 array_filter 删除空数组元素

    我正在尝试使用函数 array filter 删除数组中的空元素 当我使用这样的外部回调时 function callback a return empty a arr array abc ghi res array filter arr
  • 将实时摄像头视图嵌入 UIView 中

    我一直在尝试找出如何在 iPhone 中嵌入一个小型实时摄像头视图UIView无济于事 我什至无法在网上找到任何样本 我知道这是可行的 但我什至不知道从哪里开始 似乎有很多例子表明其他人已将视图添加到相机的完整屏幕视图中 但反之则不然 看起
  • 在 WPF 中制作图表的最佳方法是什么? (或者一般来说这也适用于 WPF)

    所以我正在 WPF 中制作一个软件 我希望能够根据我存储的数据制作图表 只是带有 x 和 y 轴的线图 理想情况下 我希望它们具有优雅 简洁的风格 我知道 WPF 没有附带图表 并且我在 WinForms 项目中尝试过 ZedGraph 但
  • 如何向现有 CSS 类添加新规则

    在下面的代码中 我已经说明了我想要实现的目标 通过向现有 CSS 类添加新规则来更改它 h4 hello h4 这是针对在不同尺寸的屏幕上运行的站点的预处理
  • Fortran 中等级(形状)未知的子例程参数

    我想知道如何在 Fortran 中最好地处理采用未知等级参数的子例程 例如 Real 8 array1 2 2 array2 2 2 3 call mysubroutine array1 call mysubroutine array2 就
  • 如何使用命令提示符运行C程序[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 如果 c 文件的位置与默认位置不同 如何在安装了代码块
  • 从函数返回未来值

    我最近开始学习 Rust 我不确定如何从应该返回 Result 的函数返回未来值 当我尝试仅返回响应变量并删除结果输出时 出现错误 不能使用 返回的函数中的运算符std string String tokio main async fn d
  • 如何从 Lucene TokenStream 获取 Token?

    我正在尝试使用 Apache Lucene 进行标记化 但我对从某个地方获取标记的过程感到困惑TokenStream 最糟糕的是我正在查看 JavaDocs 中解决我的问题的评论 http lucene apache org java 3
  • 如何通过 SQL 访问雪花查询配置文件概览统计信息?

    在 Snowflake SnowSight UI 的 查询配置文件 视图中 有一个名为 配置文件概述 的部分 您可以在其中查看总执行时间的细分 它包含处理 本地磁盘 I O 远程磁盘 I O 同步等统计信息 完整列表在这里https doc
  • 在 MongoDB 中存储日期的最佳方式是什么?

    我刚刚开始学习MongoDB 希望慢慢从MySQL迁移 在 MySQL 中 有两种不同的数据类型 DATE 0000 00 00 and DATETIME 0000 00 00 00 00 00 在我的 MySQL 中 我使用DATE类型
  • jQuery UI 手风琴激活

    我不知道如何做到这一点 或者我是否可以做到这一点 我有一个 jQuery UI Accordion 多个部分 每个部分包含多个锚标记 每个锚标记都有一个唯一的字符串 id 我希望能够将手风琴打开到具有给定 id 的特定元素所在的位置 就像说
  • 将图标放置在带有文本溢出省略号的行之后

    我遇到文本溢出问题 省略号 我想在 3 个点之后放置一个图标 但该图标总是出现在下一行 因为 display block 属性 有什么办法可以显示这样的线吗 我的例子fiddle和CSS title text overflow ellips
  • Android 处理程序消息和 ListView

    这是我的错误 Uncaught remote exception Exceptions are not yet supported across processes android util AndroidRuntimeException
  • 编写一个 __init__ 函数在 django 模型中使用

    我正在尝试写一个 init 我的模型之一的函数 以便我可以通过执行以下操作来创建对象 p User name email 当我编写模型时 我有 def init self name email house id password model
  • 如何使用 powershell 压缩超过 2 GB 的文件?

    我正在开发一个项目来压缩从几个 mb 到几个 GB 大小的文件 我正在尝试使用 powershell 将它们压缩成 zip 我遇到的主要问题是使用压缩存档对单个文件大小有 2 GB 上限 我想知道是否有另一种方法来压缩文件 Edit 因此