PowerShell 解压 PSF 格式的更新包

PowerShell 解压 PSF 格式的更新包
狂犬主子发现 W10UI 中有一段 PowerShell 代码,能够解压 PSF 格式的更新包,支持最新 PA31,通过原生 C# + Win32 方法实现,无需 PSFExtractor.exe。提取出来发现可以直接使用,特此分享
使用方法
-
准备文件:
Windows11.0-KB5046732-x64.wim
、Windows11.0-KB5046732-x64.psf
- 获取方式:
- UUPDUMP 下载
- 解压 MSU 格式更新包得到
- 两个文件名相同,此处KB号仅供参考
- 第一个文件之前为 CAB 格式,现在改成了 WIM 格式
- 获取方式:
-
打开 PowerShell
-
导入函数:将下方 函数内容 中的代码复制粘贴进去,然后回车
-
(可选)如果不想看见满屏报错,PowerShell 中执行以下命令:
1
$ErrorActionPreference = 'SilentlyContinue'
-
解压
Windows11.0-KB5046732-x64.wim
或者 CAB 格式的补丁到Windows11.0-KB5046732-x64
文件夹- 文件夹要与更新包位于同一目录
- 可以直接右键
Windows11.0-KB5046732-x64.wim
=>7-Zip
=>解压到 "Windows11.0-KB5046732-x64\"
进行解压,报错“有效数据外包含额外数据”为正常现象
-
PowerShell 中调用函数
P
解压 PSF(建议输入完整路径):1
P "D:\TEMP\Windows11.0-KB5046732-x64.psf"
-
通过解压后的文件夹大小判断是否解压正确
- 正常 1 GB 以上,如果只有几百兆肯定有问题
-
此时可使用 DISM 添加包
Windows11.0-KB5046732-x64\update.mum
来更新系统
函数内容
方法来自:https://forums.mydigitallife.net/posts/1216064
BatUtil/W10UI/W10UI.cmd at master · abbodi1406/BatUtil
1 | function Native($DllFile) { $Lib = [IO.Path]::GetFileName($DllFile) $Marshal = [System.Runtime.InteropServices.Marshal] $Module = [AppDomain]::CurrentDomain.DefineDynamicAssembly((Get-Random), 'Run').DefineDynamicModule((Get-Random)) $Struct = $Module.DefineType('DI', 1048841, [ValueType], 0) [void]$Struct.DefineField('lpStart', [IntPtr], 6) [void]$Struct.DefineField('uSize', [UIntPtr], 6) [void]$Struct.DefineField('Editable', [Boolean], 6) $DELTA_INPUT = $Struct.CreateType() $Struct = $Module.DefineType('DO', 1048841, [ValueType], 0) [void]$Struct.DefineField('lpStart', [IntPtr], 6) [void]$Struct.DefineField('uSize', [UIntPtr], 6) $DELTA_OUTPUT = $Struct.CreateType() $Class = $Module.DefineType('PSFE', 1048961, [Object], 0) [void]$Class.DefinePInvokeMethod('LoadLibraryW', 'kernel32.dll', 22, 1, [IntPtr], @([String]), 1, 3).SetImplementationFlags(128) [void]$Class.DefinePInvokeMethod('ApplyDeltaB', $Lib, 22, 1, [Int32], @([Int64], [Type]$DELTA_INPUT, [Type]$DELTA_INPUT, [Type]$DELTA_OUTPUT.MakeByRefType()), 1, 3) [void]$Class.DefinePInvokeMethod('DeltaFree', $Lib, 22, 1, [Int32], @([IntPtr]), 1, 3) $Win32 = $Class.CreateType() } function ApplyDelta($dBuffer, $dFile) { $trg = [Activator]::CreateInstance($DELTA_OUTPUT) $src = [Activator]::CreateInstance($DELTA_INPUT) $dlt = [Activator]::CreateInstance($DELTA_INPUT) $dlt.lpStart = $Marshal::AllocHGlobal($dBuffer.Length) $dlt.uSize = [Activator]::CreateInstance([UIntPtr], @([UInt32]$dBuffer.Length)) $dlt.Editable = $true $Marshal::Copy($dBuffer, 0, $dlt.lpStart, $dBuffer.Length) [void]$Win32::ApplyDeltaB(0, $src, $dlt, [ref]$trg) if ($trg.lpStart -eq [IntPtr]::Zero) { return } $out = New-Object byte[] $trg.uSize.ToUInt32() $Marshal::Copy($trg.lpStart, $out, 0, $out.Length) [IO.File]::WriteAllBytes($dFile, $out) if ($dlt.lpStart -ne [IntPtr]::Zero) { $Marshal::FreeHGlobal($dlt.lpStart) } if ($trg.lpStart -ne [IntPtr]::Zero) { [void]$Win32::DeltaFree($trg.lpStart) } } function G($DirectoryName) { $DeltaList = [ordered] @{} $doc = New-Object xml $doc.Load($DirectoryName + "\express.psf.cix.xml") $child = $doc.FirstChild.NextSibling.FirstChild while (!$child.LocalName.Equals("Files")) { $child = $child.NextSibling } $FileList = $child.ChildNodes foreach ($file in $FileList) { $fileChild = $file.FirstChild while (!$fileChild.LocalName.Equals("Delta")) { $fileChild = $fileChild.NextSibling } $deltaChild = $fileChild.FirstChild while (!$deltaChild.LocalName.Equals("Source")) { $deltaChild = $deltaChild.NextSibling } $DeltaList[$($file.id)] = @{name = $file.name; time = $file.time; stype = $deltaChild.type; offset = $deltaChild.offset; length = $deltaChild.length }; } return $DeltaList } function P($CabFile, $DllFile = 'msdelta.dll') { if ($DllFile -eq 'msdelta.dll' -and (Test-Path "$env:SystemRoot\System32\UpdateCompression.dll")) { $DllFile = "$env:SystemRoot\System32\UpdateCompression.dll" } . Native($DllFile) [void]$Win32::LoadLibraryW($DllFile) $DirectoryName = $CabFile.Substring(0, $CabFile.LastIndexOf('.')) $PSFFile = $DirectoryName + ".psf" $null = [IO.Directory]::CreateDirectory($DirectoryName) $DeltaList = G $DirectoryName $PSFFileStream = [IO.File]::OpenRead([IO.Path]::GetFullPath($PSFFile)) $cwd = [IO.Path]::GetFullPath($DirectoryName) [Environment]::CurrentDirectory = $cwd $null = [IO.Directory]::CreateDirectory("000") foreach ($DeltaFile in $DeltaList.Values) { $FullFileName = $DeltaFile.name if (Test-Path $FullFileName) { continue } $ShortFold = [IO.Path]::GetDirectoryName($FullFileName) $ShortFile = [IO.Path]::GetFileName($FullFileName) [bool]$UseRobo = (($cwd + '\' + $FullFileName).Length -gt 255) -or (($cwd + '\' + $ShortFold).Length -gt 248) if ($UseRobo -eq 0 -and $ShortFold.IndexOf("_") -ne -1) { $null = [IO.Directory]::CreateDirectory($ShortFold) } if ($UseRobo -eq 0) { $WhereFile = $FullFileName } Else { $WhereFile = "000\" + $ShortFile } try { [void]$PSFFileStream.Seek($DeltaFile.offset, 0) } catch {} $Buffer = New-Object byte[] $DeltaFile.length try { [void]$PSFFileStream.Read($Buffer, 0, $DeltaFile.length) } catch {} $OutputFileStream = [IO.File]::Create($WhereFile) try { [void]$OutputFileStream.Write($Buffer, 0, $DeltaFile.length) } catch {} [void]$OutputFileStream.Close() if ($DeltaFile.stype -eq "PA30" -or $DeltaFile.stype -eq "PA31") { ApplyDelta $Buffer $WhereFile } $null = [IO.File]::SetLastWriteTimeUtc($WhereFile, [DateTime]::FromFileTimeUtc($DeltaFile.time)) if ($UseRobo -eq 0) { continue } Start-Process robocopy.exe -NoNewWindow -Wait -ArgumentList ('"' + $cwd + '\000' + '"' + ' ' + '"' + $cwd + '\' + $ShortFold + '"' + ' ' + $ShortFile + ' /MOV /R:1 /W:1 /NS /NC /NFL /NDL /NP /NJH /NJS') } [void]$PSFFileStream.Close() $null = [IO.Directory]::Delete("000", $True) } |
功能解释
(以下内容由AI生成)这段PowerShell脚本的主要功能是解析和应用增量更新文件(delta files)。具体功能如下:
- Native($DllFile):定义了一些动态类型和P/Invoke方法,用于加载动态链接库(如
msdelta.dll
)并调用其中的方法。- 定义了动态类型
DI
和DO
,分别用于表示输入和输出结构体。 - 定义了一个类
PSFE
,并在其中定义了P/Invoke方法LoadLibraryW
、ApplyDeltaB
和DeltaFree
,用于加载动态链接库和调用其方法。
- 定义了动态类型
- ApplyDelta($dBuffer, $dFile):应用增量更新数据到目标文件。首先创建输入和输出结构体,然后调用
ApplyDeltaB
方法进行更新,最后释放资源。- 创建输入和输出结构体实例。
- 分配内存并将增量数据复制到内存中。
- 调用
ApplyDeltaB
方法应用增量更新。 - 将结果写入目标文件,并释放分配的内存。
- G($DirectoryName):解析XML文件中的增量更新信息,生成一个包含文件名、时间戳、源类型等信息的字典。
- 加载XML文件并解析其中的增量更新信息。
- 生成一个字典,包含每个文件的名称、时间戳、源类型等信息。
- P($CabFile, $DllFile = ‘msdelta.dll’):主函数,负责解压CAB文件,解析增量更新信息,应用增量更新,并设置文件的时间戳。
- 检查并加载动态链接库。
- 解压CAB文件,解析增量更新信息。
- 遍历增量更新列表,对每个文件进行处理:
- 检查文件是否存在,如果存在则跳过。
- 创建必要的目录。
- 计算路径长度,如果超过限制则使用RoboCopy复制文件,否则直接写入文件。
- 应用增量更新。
- 设置文件的时间戳。
- 关闭PSF文件并删除临时目录。
flowchart TD A[开始] --> B[解析XML文件] B --> C[获取增量更新信息] C --> D[创建目录] D --> E[打开PSF文件] E --> F[遍历增量更新列表] F --> G{是否已存在文件?} G -->|是| H[跳过当前文件] G -->|否| I[创建目录] I --> J[计算路径长度] J --> K{路径长度超过限制?} K -->|是| L[使用RoboCopy复制文件] K -->|否| M[写入文件] M --> N[应用增量更新] N --> O[设置文件时间戳] O --> P[继续下一个文件] P --> F H --> P L --> P P --> Q[关闭PSF文件] Q --> R[删除临时目录] R --> S[结束]
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果