PowerShell 读取 MSZIP 压缩的 mszyml

PowerShell 读取 MSZIP 压缩的 mszyml

由于本人在研究 winget-cli 的过程中发现了一种格式(.mszymlapplication/x-ms-zip-yaml),显而易见为 MSZIP 算法压缩的 YAML 格式。

经过查询资料,MSZIP 的压缩块与 RFC1951(DEFLATE)类似,并且这背后貌似有一些早在DOS时代的历史原因导致没解决方案,不过目前这个问题早就得以解决,可以使用 .NET System.IO.Compression 命名空间的 DeflateStream 类来实现,除了C++直接调用 Win32 API 外,其它编程语言也有通过 Zlib 来实现。

PowerShell 暂时没有合适的解压代码,这里就让能工智人参考着搓了一个,并且根据实际需要做了一些修改。代码可能没处理周到,对输入内容的要求会有点高。

本文编写时的环境如下,不保证支持老版本的 PowerShell:

1
2
3
4
5
6
7
8
9
10
11
12
13
PS D:\> $PSVersionTable

Name Value
---- -----
PSVersion 7.4.5
PSEdition Core
GitCommitId 7.4.5
OS Microsoft Windows 10.0.22631
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0

函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 错误强制停止
$ErrorActionPreference = "Stop"

Add-Type -AssemblyName System.IO.Compression.FileSystem

function ConvertFrom-MSZIP {
param (
[Parameter(ValueFromPipeline=$true)]
[byte[]]$buffer
)

begin {
# 在循环开始前初始化任何变量
$magicHeader = [byte[]](0, 0, 0x43, 0x4b)
$decompressed = [System.IO.MemoryStream]::new()
}

process {
Add-Type -AssemblyName System.IO.Compression.FileSystem
# 检查文件头是否为 MSZIP 格式
if (-not ($buffer[26..29] -join ',') -eq ($magicHeader -join ',')) {
throw "Invalid MSZIP format"
}

# 从文件头开始搜索
$chunkIndex = 26

# 使用提供的缓冲区创建内存流
$bufferStream = [System.IO.MemoryStream]::new($buffer)

# 循环搜索并解压缩每个块
while ($chunkIndex -lt $buffer.Length) {
$chunkIndex += $magicHeader.Length
$bufferStream.Position = $chunkIndex
try {
$decompressedChunk = New-Object System.IO.Compression.DeflateStream($bufferStream, [System.IO.Compression.CompressionMode]::Decompress)
$decompressedChunk.CopyTo($decompressed)
} catch {
# Write-Warning "解压错误: chunkIndex: $chunkIndex, $_"
break
}
$chunkIndex++
}
}

end {
# 在所有输入处理完毕后执行清理工作
$decompressed.Position = 0
$reader = [System.IO.StreamReader]::new($decompressed)
$reader.ReadToEnd()
}
}

示例

例1

1
2
3
$buffer = [System.IO.File]::ReadAllBytes(".\versionData.mszyml")
$decompressedData = ConvertFrom-MSZIP -buffer $buffer
$decompressedData

例2

1
2
3
4
5
$buffer = (Invoke-WebRequest "https://cdn.winget.microsoft.com/cache/packages/Seewo.EasiNote/3e5b908d/versionData.mszyml").Content
$versionData = (ConvertFrom-MSZIP -buffer $buffer | ConvertFrom-Yaml).vD[0]
Write-Host "RelativePath: " $versionData.rP
Write-Host "Version: " $versionData.v
Write-Host "Sha256Hash: " $versionData.s256H

参考

https://learn.microsoft.com/zh-cn/windows/win32/cmpapi/using-the-compression-api

https://learn.microsoft.com/zh-cn/windows/win32/api/compressapi/nf-compressapi-createcompressor

https://learn.microsoft.com/zh-cn/dotnet/api/system.io.compression.deflatestream

https://github.com/frereit/pymszip

https://github.com/masihyeganeh/mszip

https://github.com/ustclug/ustcmirror-images/blob/master/winget-source/utilities.js

https://stackoverflow.com/questions/19370304/how-to-decompress-mszip-files-with-c