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