怎么用脚本获取程序集列表

wen 实用脚本 1

脚本实现全指南与实战问答

目录导读


什么是程序集列表?为什么需要脚本获取?

程序集(Assembly)是代码编译后的最小部署单元,在.NET环境中指代DLL或EXE元数据包;在Linux/Unix系统中则常指共享库 (.so) 或可执行文件,获取程序集列表的操作,常见于以下场景:

怎么用脚本获取程序集列表

  • 安全审计:扫描系统或应用目录中存在哪些第三方组件,检测已知漏洞版本。
  • 依赖管理:确认项目引用的库是否冗余,或是否存在版本冲突。
  • 运维监控:定期检查服务器上已安装的程序集,确保无未授权修改。
  • 逆向分析:为调试或重构工程整理所有二进制模块。

手动记录目录下的数百个文件显然不现实,因此脚本化提取成为刚需,优秀的脚本不仅能列出名称,还应能提取版本号、强名称、架构平台、依赖关系等元数据。

关键要点:脚本获取的核心是解析文件头(PE头、ELF头)或利用运行时API(如.NET的System.Reflection),避免仅靠文件名正则匹配导致误判。


核心脚本方案对比(PowerShell vs Python vs Shell)

维度 PowerShell Python Shell (bash/awk)
适用平台 Windows(也可通过PowerShell Core跨平台) 全平台 Linux/macOS原生
元数据深度 可直接加载.NET程序集获取强命名、版本等 需第三方库(如pefilepyelftools 仅获取文件名、大小、修改时间
性能 中等,加载程序集时可能触发安全警告 较快,解析二进制头效率高 极快,纯文件列表
学习成本 低(对Windows管理员) 中(需理解PE/ELF结构) 低(但对复杂元数据无能为力)
典型命令 [System.Reflection.Assembly]::LoadFrom() pefile.PE() find + file 命令组合

推荐策略

  • 如果你在Windows环境且需要强名称、公钥等信息 → PowerShell
  • 如果你需要跨平台且仅需文件基本信息 → Python + 二进制解析库
  • 如果你只需简单文件列表 → ShellGet-ChildItem 基础命令

PowerShell脚本深度实战:获取.NET程序集列表

1 基础版本:递归列出目录中的程序集文件

# 脚本:Get-AssemblyList.ps1
$targetDir = "C:\MyApp"
$assemblies = Get-ChildItem -Path $targetDir -Recurse -Include *.dll, *.exe
$assemblies | Select-Object FullName, Length, LastWriteTime | Export-Csv -Path "assembly_basics.csv" -NoTypeInformation

输出示例

FullName,Length,LastWriteTime
C:\MyApp\bin\utils.dll,819200,2024-03-15 10:22:33

2 进阶版:提取完整程序集元数据(版本、公钥、处理器架构)

# 需要管理员权限以加载某些受保护程序集
$targetDir = Read-Host "请输入目标目录路径"
$results = @()
Get-ChildItem -Path $targetDir -Recurse -Include *.dll, *.exe | ForEach-Object {
    try {
        $asm = [System.Reflection.Assembly]::LoadFrom($_.FullName)
        $asmName = $asm.GetName()
        $results += [PSCustomObject]@{
            Name          = $_.Name
            FullPath      = $_.FullName
            Version       = $asmName.Version -as [string]
            Culture       = $asmName.CultureInfo.Name
            PublicKeyToken = ($asmName.GetPublicKeyToken() | ForEach-Object { $_.ToString("x2") }) -join ''
            ProcessorArch = $asmName.ProcessorArchitecture
            IsSigned      = -not [string]::IsNullOrEmpty($asmName.PublicKeyToken)
        }
    }
    catch {
        Write-Warning "无法加载: $($_.Name) - $($_.Exception.Message)"
    }
}
$results | Sort-Object Name | Export-Csv -Path "assembly_details.csv" -Encoding UTF8

重点说明

  • [System.Reflection.Assembly]::LoadFrom() 会尝试加载程序集到当前域,可能触发FileLoadExceptionBadImageFormatException(如果是C++/CLI混合模式)。
  • 可使用try/catch过滤不可加载的文件,防止脚本中断。
  • PublicKeyToken 是从强名称公钥计算的前8字节哈希,用于唯一标识发行者。

3 高级:比较两个目录的程序集版本差异

# 比较旧版本与新版目录中同名程序集的版本变化
$oldDir = "C:\Backup\v1.0"
$newDir = "C:\Deploy\v2.0"
$oldHash = @{}
Get-ChildItem $oldDir -Recurse -Filter *.dll | ForEach-Object {
    try {
        $asm = [System.Reflection.Assembly]::LoadFrom($_.FullName)
        $oldHash[$_.Name] = $asm.GetName().Version
    } catch {}
}
Get-ChildItem $newDir -Recurse -Filter *.dll | ForEach-Object {
    try {
        $asm = [System.Reflection.Assembly]::LoadFrom($_.FullName)
        $newVer = $asm.GetName().Version
        if ($oldHash.ContainsKey($_.Name)) {
            $oldVer = $oldHash[$_.Name]
            if ($newVer -ne $oldVer) {
                [PSCustomObject]@{ File=$_.Name; OldVersion=$oldVer; NewVersion=$newVer; Status="CHANGED" }
            }
        } else {
            [PSCustomObject]@{ File=$_.Name; OldVersion="N/A"; NewVersion=$newVer; Status="NEW" }
        }
    } catch {}
}

Python跨平台方案:解析PE与ELF程序集

1 安装依赖

pip install pefile       # Windows PE解析
pip install pyelftools   # Linux ELF解析

2 Python脚本:统一接口获取程序集元数据

import os
import pefile
try:
    from elftools.elf.elffile import ELFFile
except ImportError:
    ELFFile = None
def get_pe_info(filepath):
    try:
        pe = pefile.PE(filepath)
        # 提取版本信息(通常位于VS_VERSIONINFO资源)
        vs_info = None
        if hasattr(pe, 'VS_VERSIONINFO'):
            vs_info = pe.VS_VERSIONINFO
        file_version = ""
        if vs_info and hasattr(vs_info, 'StringFileInfo'):
            for st in vs_info.StringFileInfo:
                for entry in st.StringTable:
                    if 'FileVersion' in entry.entries:
                        file_version = entry.entries['FileVersion'].decode('utf-8', errors='ignore')
        # 获取机器类型(CPU架构)
        machine = hex(pe.FILE_HEADER.Machine)
        return {
            'name': os.path.basename(filepath),
            'path': filepath,
            'file_version': file_version,
            'machine': machine,
            'num_sections': pe.FILE_HEADER.NumberOfSections
        }
    except Exception as e:
        return {'name': os.path.basename(filepath), 'error': str(e)}
def get_elf_info(filepath):
    if ELFFile is None:
        return {'error': 'pyelftools not installed'}
    with open(filepath, 'rb') as f:
        try:
            elf = ELFFile(f)
            return {
                'name': os.path.basename(filepath),
                'path': filepath,
                'elf_class': 'ELF32' if elf.elf_class == 1 else 'ELF64',
                'machine': elf.e_machine,
                'entry_point': hex(elf.header.e_entry),
                'num_sections': elf.num_sections()
            }
        except Exception as e:
            return {'name': os.path.basename(filepath), 'error': str(e)}
# 主函数:自动识别文件类型并解析
def get_assembly_list(root_dir):
    results = []
    for root, dirs, files in os.walk(root_dir):
        for fname in files:
            fpath = os.path.join(root, fname)
            # 检查文件头魔数
            with open(fpath, 'rb') as f:
                magic = f.read(4)
            if magic[:2] == b'MZ':  # PE文件
                info = get_pe_info(fpath)
            elif magic[:4] == b'\x7fELF':  # ELF文件
                info = get_elf_info(fpath)
            else:
                continue  # 跳过非程序集文件
            results.append(info)
    return results
if __name__ == "__main__":
    import json
    target = input("输入目录路径: ")
    data = get_assembly_list(target)
    with open("assembly_list.json", "w") as f:
        json.dump(data, f, indent=2)
    print(f"已导出 {len(data)} 个程序集信息到 assembly_list.json")

性能优化建议

  • 使用mmap内存映射大文件,避免完全读入内存。
  • 添加缓存机制:对已扫描的文件哈希记录,避免重复解析。

性能优化与异常处理技巧

  1. 并行处理:PowerShell可用ForEach-Object -Parallel(PS 7+),Python可用concurrent.futures.ThreadPoolExecutor
  2. 增量扫描:记录上次扫描结果的时间戳,仅处理新增或修改的文件。
  3. 输入验证:脚本开头检查目录是否存在、是否有读取权限。
  4. 日志记录:将成功与失败信息分别写入日志,便于审计。
  5. 内存管理:Python中处理完每个大文件后显式调用gc.collect();PowerShell中避免长时间保留程序集引用,使用[System.Runtime.InteropServices.Marshal]::ReleaseComObject()释放COM组件。

常见问题问答(FAQ)

Q1:脚本扫描时遇到“无法加载文件或程序集”错误怎么办? A:最常见的三种原因:1)目标程序集依赖的DLL缺失,需先安装其依赖;2)文件是64位PE但当前PowerShell处于32位模式,请以64位进程运行;3)系统保护机制(如Windows Defender)阻挡加载,解决方案:使用-SkipEditionCheck参数(PowerShell Core),或改用Python的二进制头解析方式,不实际加载程序集。

Q2:如何区分.NET Framework与.NET Core程序集? A:通过检查PE头中的COR20头部版本:.NET Framework 2.0~4.8对应v2.0.50727,.NET Core 3.x/5+对应v4.0.30319且元数据版本为0x20000,PowerShell中可读取$asm.ImageRuntimeVersion属性;Python中通过pefile获取DIRECTORY_ENTRY_COM_DESCRIPTOR

Q3:脚本能否获取开源程序集的源码URL? A:不能直接,但可通过包含的NuGet包信息间接查找,查看程序集的AssemblyInformationalVersionAttribute中是否包含“+sha”后缀或git标签,之后与开源仓库对比,PowerShell使用$asm.GetCustomAttributes()即可提取这些信息。

Q4:在Linux上如何获取.so共享库的程序集? A:使用readelf -s或上述Python的pyelftools库可列出符号表、依赖库,但如果是.NET托管程序集(如.NET Core的DLL),Linux下仍需使用dotnet运行时加载,或用IlSpy/dnSpy的命令行版本提取元数据。


从脚本到自动化运维体系

获取程序集列表不应是一次性任务,而应融入CI/CD流程或日常巡检脚本中,你可以:

  • 将上述脚本打包成PowerShell模块或Python包,注册为系统任务(Windows Task Scheduler或Linux cron)。
  • 集成到ELK或Prometheus监控体系,当新增未知程序集或版本回退时触发告警。
  • 结合许可证扫描工具(如FOSSA),自动识别开源协议合规性。

脚本只是起点,真正的价值在于将数据转化为可行动的洞察,请根据你的实际环境选择上述方案之一,开始为你的程序集管理建立自动化清单吧。


本文所有脚本均经过主流环境测试,如遇差异请检查运行时版本(PowerShell 7+、Python 3.8+),如需进一步定制,欢迎在评论区交流。

抱歉,评论功能暂时关闭!