ILSpy PowerShell 模块实战指南:用 Get-Decompiler 系列 Cmdlet 在脚本中反编译 .NET 程序集
【免费下载链接】ILSpy.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!项目地址: https://gitcode.com/gh_mirrors/il/ILSpy
导读
ILSpy 不仅是图形化反编译工具,其核心引擎ICSharpCode.Decompiler也被封装成了 PowerShell 二进制模块,供脚本与自动化流水线直接调用。本文以 ICSharpCode.Decompiler.PowerShell/README.md 为骨架,结合模块源码逐一向你介绍 6 个核心 Cmdlet(Get-Decompiler、Get-DecompiledSource、Get-DecompiledTypes、Get-DecompiledProject、Get-DecompilerVersion、Get-TargetFramework)的参数、行为与底层实现,并给出可复制的端到端示例。读完本文,你将能够在 Windows PowerShell 5.1 与 PowerShell 7+(Windows/macOS)环境中完成"加载程序集 → 枚举类型 → 输出源码 → 生成完整工程"的完整反编译工作流。
模块概览:PowerShell 前端的定位与适用场景
ICSharpCode.Decompiler.PowerShell是 ILSpy 生态中的 PowerShell 前端模块,README 明确其构建参考了 PowerShell 官方仓库的 command-line-simple-example 指南,测试覆盖环境为Windows 上的 PowerShell 5.1,以及 Windows 和 Mac 上的 PowerShell 7+。它没有 GUI,也不依赖 ILSpy 桌面程序,而是直接复用ICSharpCode.Decompiler(C# 反编译引擎)与ICSharpCode.ILSpyX(PDB 解析等辅助设施)的类库能力,让反编译能力可以被 PowerShell 脚本"管道化"地消费。
从 ICSharpCode.Decompiler.PowerShell.csproj 可以看到其工程形态:
- 目标框架为
netstandard2.0,因此模块可被 Windows PowerShell 5.1(.NET Framework)与 PowerShell 7+(.NET Core/.NET 5+)加载; - 通过
PowerShellStandard.Library包编写二进制 Cmdlet,并显式引入Mono.Cecil与System.Memory统一依赖版本; - 直接
ProjectReference引用..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.csproj,并以内联Compile方式把ICSharpCode.ILSpyX/PdbProvider下的MonoCecilDebugInfoProvider.cs、PortableDebugInfoProvider.cs、DebugInfoUtils.cs三个文件编入本程序集(这正是Get-Decompiler支持自动定位 PDB 的来源); - 构建后的 PostBuild 步骤会(Windows 用
powershell、非 Windows 用pwsh)把 manifest.psd1 复制到输出目录并重命名为与 DLL 同名,从而形成一个标准 PowerShell 模块目录。
换句话说:模块 = 二进制 Cmdlet(DLL)+ 模块清单(psd1),编译完成后即可被Import-Module直接加载。
模块清单解析:manifest.psd1 导出的命令面
manifest.psd1 是模块的"身份证",关键字段如下:
| 字段 | 值 | 说明 |
|---|---|---|
RootModule | ICSharpCode.Decompiler.PowerShell.dll | 二进制模块入口程序集 |
ModuleVersion | 8.0.0.0 | 模块版本号(与仓库当前版本线对应) |
GUID | 198b4312-cbe7-417e-81a7-1aaff467ef06 | 模块唯一标识 |
Author/CompanyName | ILSpy Contributors/ic#code | 作者与厂商信息 |
Description | PowerShell front-end for ILSpy | 模块用途描述 |
CmdletsToExport | 6 个 Get-* Cmdlet(见下表) | 显式导出的命令清单 |
FunctionsToExport/AliasesToExport | 空数组 | 不导出函数与别名 |
清单显式导出的 Cmdlet 与对应源码文件:
| Cmdlet | 源码文件 | 输出类型 |
|---|---|---|
Get-Decompiler | GetDecompilerCmdlet.cs | CSharpDecompiler |
Get-DecompiledSource | GetDecompiledSourceCmdlet.cs | string |
Get-DecompiledTypes | GetDecompiledTypesCmdlet.cs | ITypeDefinition[] |
Get-DecompiledProject | GetDecompiledProjectCmdlet.cs | string |
Get-DecompilerVersion | GetDecompilerVersion.cs | string |
Get-TargetFramework | GetTargetFramework.cs | string |
核心 Cmdlet 逐一拆解:参数、行为与底层实现
1. Get-Decompiler:创建反编译器实例(一切工作的起点)
源码 GetDecompilerCmdlet.cs 定义的参数:
| 参数 | 位置 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
LiteralPath | 0 | 是 | — | 待反编译程序集路径,支持PSPath别名,ValidateNotNullOrEmpty校验非空 |
LanguageVersion | — | 否 | Latest | 反编译器使用的 C# 语言版本 |
RemoveDeadStores | — | 否 | $false | 是否移除死存储(dead stores) |
RemoveDeadCode | — | 否 | $false | 是否移除死代码(dead code) |
PDBFilePath | — | 否 | $null | 指定 PDB 文件路径;不传时自动探测 |
其内部实现值得注意的调用链:
- 以
PEFile打开目标程序集(PEStreamOptions.Default,只读打开); - 调用
DebugInfoUtils.FromFile(module, PDBFilePath)尝试从程序集旁/指定位置解析调试符号(该工具类来自ICSharpCode.ILSpyX/PdbProvider,同时支持 Mono 与 Portable PDB 两种格式); - 构造
CSharpDecompiler,并把DecompilerSettings的关键开关写死为:ThrowOnAssemblyResolveErrors = false(程序集解析失败不抛异常,便于批处理容错)、UseDebugSymbols与ShowDebugInfo跟随 PDB 是否存在而联动开启; - 将解析出的
debugInfo赋给decompiler.DebugInfoProvider。
因此,只要把程序集路径传给Get-Decompiler,返回的CSharpDecompiler对象就同时携带了"源码反编译能力"与(若能找到的)"调试符号信息",可直接作为后续三个 Cmdlet 的管道输入。
2. Get-DecompiledTypes:按类型种类筛选枚举程序集内的类型
源码 GetDecompiledTypesCmdlet.cs 要求两个参数:
Decompiler(位置 0,必填):Get-Decompiler产出的CSharpDecompiler;Types(必填,字符串数组):要筛选的类型种类。
Types参数由 TypesParser.cs 解析,支持两种写法:
- 完整关键字:
class、struct、interface、enum、delegate(不区分大小写,且允许前缀缩写,如cla可匹配class); - 字母缩写组合:当只传入一个不以任何关键字开头的值时,按字母解释——
c=class、i=interface、s=struct、d=delegate、e=enum,例如-Types cise等价于同时选择 class、interface、struct、enum。
实现上,Cmdlet 遍历Decompiler.TypeSystem.MainModule.TypeDefinitions,只保留TypeKind命中筛选集合的类型,并以ITypeDefinition[]数组写出,方便后续用ForEach-Object或Select-Object继续处理(例如读取FullName、FullTypeName等元数据)。
3. Get-DecompiledSource:输出 C# 源码文本
源码 GetDecompiledSourceCmdlet.cs:
| 参数 | 位置 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
Decompiler | 0 | 是 | — | CSharpDecompiler实例 |
TypeName | — | 否 | string.Empty | 要反编译的类型的完整名称 |
行为分支:
- 不传
TypeName:调用Decompiler.DecompileWholeModuleAsString(),把整个程序集的所有类型反编译为一段 C# 源码字符串; - 传
TypeName:用FullTypeName解析该名称,调用Decompiler.DecompileTypeAsString(name),仅反编译指定类型。
两种情况下,源码都会以string通过WriteObject输出到管道,可直接重定向到文件:Get-DecompiledSource $d | Set-Content out.cs。反编译过程中产生的成员级异常会被 DecompilationErrorReporting.cs 以"每个成员一条非终止错误"的方式上报(错误 ID 为DecompilationFailed,即ErrorIds.DecompilationFailed = "2"),同时源码照常输出——这样脚本不会把"已知反编译失败"误当作"干净成功"。
4. Get-DecompiledProject:一键生成完整 .NET 工程
源码 GetDecompiledProjectCmdlet.cs 是全模块中"最重"的一个 Cmdlet:
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
Decompiler | 0 | 是 | CSharpDecompiler实例 |
LiteralPath | 1 | 是 | 输出目录,支持PSPath、OutputPath两个别名 |
关键行为与实现细节:
- 输出目录必须预先存在:源码先校验
Directory.Exists(path),否则直接输出提示字符串Destination directory must exist prior to decompilation并返回——这是使用上最容易踩的坑,务必先New-Item -ItemType Directory; - 底层调用
WholeProjectDecompiler:从Decompiler.TypeSystem.MainModule.MetadataFile取出元数据,构造UniversalAssemblyResolver(false表示不启用多目标框架解析,目标框架 ID 通过DetectTargetFrameworkId()探测),再执行decompiler.DecompileProject(module, path),生成.csproj、.sln与全部源码文件; - 进度报告:Cmdlet 实现了
IProgress<DecompilationProgress>接口,通过后台Task执行反编译、主线程轮询进度,并调用WriteProgress输出ProgressRecord(包含已完成单元数、总单元数与百分比),反编译结束时写一条Completed记录; - 结束后通过
decompiler.Errors收集DecompilerException列表,逐个以非终止错误上报。
5. Get-DecompilerVersion:输出引擎版本
源码 GetDecompilerVersion.cs 实现极简:读取FullTypeName类型所在程序集(即ICSharpCode.Decompiler.dll)的版本号并以字符串输出。适合在脚本开头做版本断言或写入日志。
6. Get-TargetFramework:探测目标框架标识
源码 GetTargetFramework.cs 接受一个必填的Decompiler参数,从Decompiler.TypeSystem.MainModule.MetadataFile调用module.Metadata.DetectTargetFrameworkId(),返回该程序集的 Target Framework 标识(TFM,形如.NETCoreApp,Version=v6.0)。结合Get-DecompiledProject使用,可在生成工程前判断目标程序集的框架形态。
完整工作流示例:从 DLL 到源码与工程
README 给出了官方示例脚本 Demo.ps1,它展示了完整的端到端用法。核心流程整理如下(命令已按可复制的形式给出):
# 1. 加载模块(假设已按下文"构建与加载"一节编译) $modulePath = "$PSScriptRoot\bin\Debug\netstandard2.0\ICSharpCode.Decompiler.PowerShell.dll" Import-Module $modulePath # 2. 输出引擎版本 $version = Get-DecompilerVersion Write-Output $version # 3. 创建反编译器实例 $asm = "$PSScriptRoot\bin\Debug\netstandard2.0\ICSharpCode.Decompiler.PowerShell.dll" $decompiler = Get-Decompiler $asm # 4. 枚举程序集中的 class 类型并打印全名 $classes = Get-DecompiledTypes $decompiler -Types class $classes.Count foreach ($c in $classes) { Write-Output $c.FullName } # 5. 反编译单个类型的源码 Get-DecompiledSource $decompiler -TypeName ICSharpCode.Decompiler.PowerShell.GetDecompilerCmdlet # 6. 反编译整个工程(输出目录需先创建) Get-DecompiledProject $decompiler -OutputPath .\decomptest要点解读:
- 第 3~4 步构成"反编译器 → 类型筛选"管道,
Types参数可换成class,struct,interface,enum,delegate任意组合,或缩写串cise; - 第 5 步
-TypeName必须使用类型的完整名称(含命名空间,如ICSharpCode.Decompiler.PowerShell.GetDecompilerCmdlet);省略该参数则输出整个模块源码; - 第 6 步的
.\decomptest目录必须先存在,否则 Cmdlet 只输出提示字符串而不执行反编译。
若要反编译全部类型而非仅 class,可将第 4 步替换为:
Get-DecompiledTypes $decompiler -Types class,struct,interface,enum,delegate | ForEach-Object { $_.FullName }构建与加载:三步跑通模块
模块没有发布到 PowerShell Gallery(README 中明确列为待办事项),因此目前的标准用法是从源码构建后本地加载:
- 编译模块:在仓库根目录用
dotnet build ICSharpCode.Decompiler.PowerShell/ICSharpCode.Decompiler.PowerShell.csproj(或dotnet build ILSpy.sln)构建。构建产物位于bin/Debug/netstandard2.0/,PostBuild 步骤会自动把manifest.psd1复制到该目录; - 确认模块目录结构:输出目录应同时包含
ICSharpCode.Decompiler.PowerShell.dll、ICSharpCode.Decompiler.PowerShell.psd1,以及依赖的ICSharpCode.Decompiler.dll等(csproj 设置了CopyLocalLockFileAssemblies=true); - 加载与验证:
Import-Module <输出目录>\ICSharpCode.Decompiler.PowerShell.dll后执行Get-Command -Module ICSharpCode.Decompiler.PowerShell,应能看到 6 个Get-*命令。
注意两点前置条件:模块目标为netstandard2.0,Windows 上需 PowerShell 5.1 或 7+,macOS 上需 PowerShell 7+(与 README 的测试声明一致);另需本机具备 .NET SDK 才能完成编译。
错误处理与容错设计
模块在两个环节有显式的容错设计,值得在脚本中配合使用:
- 程序集加载失败(
Get-Decompiler):写入ErrorRecord,错误 ID 为ErrorIds.AssemblyLoadFailed(即"1"),类别OperationStopped,并WriteVerbose输出完整异常——可在脚本中通过$Error[0].FullyQualifiedErrorId判断; - 反编译过程中的成员级失败:以非终止错误逐个上报(错误 ID
DecompilationFailed,即"2"),源码仍照常产出,脚本可用-ErrorAction SilentlyContinue选择性忽略后继续收集输出; Get-DecompiledProject若输出目录不存在,返回的是普通字符串而非异常,脚本应显式判断返回值(或先自行创建目录)。
已知限制与后续方向
README 中明确标注的唯一未完成事项是发布到 PowerShell Gallery(当前仓库版本尚未提供Publish-Module产物),因此使用者目前只能走"源码构建 + 本地Import-Module"的路径;同时 README 附带的开发参考(官方简单 Cmdlet 编写指南、Cmdlet 动词白名单、二进制模块测试方法等)可供扩展模块时对照遵循。若需要为模块补充新的 Cmdlet,可参考 Demo.ps1 中的双 netstandard 程序集测试方式,并在 manifest.psd1 的CmdletsToExport中登记新命令名。
【免费下载链接】ILSpy.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!项目地址: https://gitcode.com/gh_mirrors/il/ILSpy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考