td1*_*126 8 powershell zip system.io.compression compress-archive system.io.compression.zipfile
我正在开发一个项目来压缩从几个 mb 到几个 GB 的文件,我正在尝试使用 powershell 将它们压缩成 .zip。我遇到的主要问题是使用压缩存档对单个文件大小有 2 GB 上限,我想知道是否有另一种方法来压缩文件。
编辑:
因此,对于这个项目,我们希望实现一个系统,从 Outlook 获取 .pst 文件并将其压缩为 .zip 并将其上传到服务器。上传后,它们将从新设备中拉出并再次提取到 .pst 文件中。
笔记
此功能的进一步更新将发布到官方GitHub 存储库以及PowerShell Gallery中。此答案中的代码将不再维护。
我们非常欢迎贡献,如果您想贡献,请分叉存储库并提交包含更改的拉取请求。
解释PowerShell 文档Compress-Archive中指定的限制:
该
Compress-Archivecmdlet 使用Microsoft .NET APISystem.IO.Compression.ZipArchive来压缩文件。由于底层 API 的限制,最大文件大小为 2 GB。
发生这种情况的原因可能是(因为 5.1 版本是闭源的),此 cmdlet在将 zip 存档写入文件之前使用内存流将所有 zip 存档条目保存在内存中。检查cmdlet 生成的InnerException我们可以看到:
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 )
Run Code Online (Sandbox Code Playgroud)
如果我们尝试从大于 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."
Run Code Online (Sandbox Code Playgroud)
巧合的是,我们看到了同样的限制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
Run Code Online (Sandbox Code Playgroud)
为了克服这个问题,并在压缩另一个进程使用的文件时模拟资源管理器的行为,下面发布的函数将默认在[FileShare] 'ReadWrite, Delete'打开FileStream.
要解决此问题,有两种解决方法:
ZipFile.CreateFromDirectoryMethod。使用此静态方法有 3 个限制:
值得注意的是,如果您需要在 Windows PowerShell (.NET Framework) 中使用该类ZipFile,则必须引用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)
Run Code Online (Sandbox Code Playgroud)
ZipArchive和相应的ZipEntries.此函数应该能够像ZipFile.CreateFromDirectory方法一样处理压缩,但也允许过滤要压缩的文件和文件夹,同时保持文件/文件夹结构不变。
文档以及使用示例可以在此处找到。
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]
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
8296 次 |
| 最近记录: |