AI工具:文件签名校验工具
·
AI模型:Deepseek
开发方法:AI+人工
使用方法:
记事本保存编码utf8bom+代码utf8,请重下代码
管理员运行powershell→cd 脚本目录→./checkFile.ps1
注意事项:
-
输入文件是文章《AI工具:ProcessMonitor监控程序安装工具》的结果文件:https://blog.csdn.net/humors221/article/details/159733489
-
**解析文件内容**:
- 跳过第一行(假定是标题行)。
- 每行取第一个逗号之前的内容,并去除首尾双引号和空格。
- 收集所有路径(去重)。
代码:
<#
.SYNOPSIS
使用 Sigcheck 批量校验文件安全性(VirusTotal)
.DESCRIPTION
读取当前目录下所有包含“扫描件”前缀的 .txt 文件,提取文件路径,
调用 Sigcheck -accepteula -nobanner -vt -vr 查询 VirusTotal,
生成 CSV 报告和 TXT 日志。自动请求管理员权限,并规避系统目录权限问题。
.NOTES
版本:1.5 (彻底解决权限拒绝问题,输出到桌面)
#>
# ========== 1. 自动请求管理员权限 ==========
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "当前非管理员权限,尝试以管理员身份重启脚本..." -ForegroundColor Yellow
Start-Sleep -Seconds 1
$scriptPath = $MyInvocation.MyCommand.Path
$arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
try {
Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments
pause
exit
}
catch {
Write-Host "请求管理员权限失败,请右键点击 PowerShell 选择“以管理员身份运行”后重新执行脚本。" -ForegroundColor Red
Read-Host "按 Enter 退出"
exit 1
}
}
Write-Host "已获得管理员权限,继续执行..." -ForegroundColor Green
# ========== 2. 检查脚本是否位于系统保护目录 ==========
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$protectedPaths = @(
$env:windir,
$env:ProgramFiles,
$env:ProgramFiles + " (x86)",
"$env:windir\System32",
"$env:windir\SysWOW64"
)
$isProtected = $false
foreach ($p in $protectedPaths) {
if ($p -and $scriptRoot.StartsWith($p, [StringComparison]::OrdinalIgnoreCase)) {
$isProtected = $true
break
}
}
if ($isProtected) {
Write-Host "错误:脚本位于系统受保护目录中:$scriptRoot" -ForegroundColor Red
Write-Host "请将脚本移动到一个普通用户目录(如桌面或文档)后重新运行。" -ForegroundColor Yellow
Read-Host "按 Enter 退出"
exit 1
}
# ========== 3. 配置输出路径(使用桌面,避免权限问题) ==========
$desktop = [Environment]::GetFolderPath("Desktop")
$logDir = Join-Path $desktop "SigcheckLogs_$((Get-Date -Format 'yyyyMMdd'))"
$reportDir= $logDir # 日志和报告放在同一个目录下
$toolsDir = Join-Path $scriptRoot "tools"
$sigcheckExe = Join-Path $toolsDir "sigcheck.exe"
$sigcheckUrl = "https://live.sysinternals.com/sigcheck.exe"
$sigcheckUrlBackup = "https://download.sysinternals.com/files/Sigcheck.zip"
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$logFile = Join-Path $logDir "CheckLog_$timestamp.txt"
$reportFile= Join-Path $reportDir "VirusTotalReport_$timestamp.csv"
$script:results = @()
$script:totalFiles = 0
$script:checkedFiles = 0
$script:errorFiles = 0
$script:maliciousFiles = 0
# ========== 4. 辅助函数 ==========
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$logLine = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] $Message"
switch ($Level) {
"ERROR" { Write-Host $logLine -ForegroundColor Red }
"WARN" { Write-Host $logLine -ForegroundColor Yellow }
"OK" { Write-Host $logLine -ForegroundColor Green }
default { Write-Host $logLine }
}
# 确保日志文件可写
try {
Add-Content -Path $logFile -Value $logLine -Encoding UTF8 -ErrorAction Stop
}
catch {
Write-Host "无法写入日志文件: $_" -ForegroundColor Red
}
}
function Ensure-Sigcheck {
if (-not (Test-Path $toolsDir)) {
try {
New-Item -ItemType Directory -Path $toolsDir -Force | Out-Null
}
catch {
Write-Log "无法创建 tools 目录: $_" "ERROR"
throw "请在脚本所在目录手动创建 tools 文件夹"
}
}
if (Test-Path $sigcheckExe) {
Write-Log "Sigcheck 已存在: $sigcheckExe"
return $true
}
Write-Log "Sigcheck 未找到,正在下载..." "WARN"
try {
Invoke-WebRequest -Uri $sigcheckUrl -OutFile $sigcheckExe -UseBasicParsing -ErrorAction Stop
if (Test-Path $sigcheckExe) {
Write-Log "Sigcheck 下载成功" "OK"
Unblock-File -Path $sigcheckExe -ErrorAction SilentlyContinue
return $true
}
}
catch {
Write-Log "主地址下载失败: $_" "WARN"
try {
$zipPath = Join-Path $toolsDir "sigcheck.zip"
Invoke-WebRequest -Uri $sigcheckUrlBackup -OutFile $zipPath -UseBasicParsing -ErrorAction Stop
Expand-Archive -Path $zipPath -DestinationPath $toolsDir -Force
Remove-Item $zipPath -Force
if (Test-Path $sigcheckExe) {
Write-Log "Sigcheck 下载成功 (备用地址)" "OK"
Unblock-File -Path $sigcheckExe -ErrorAction SilentlyContinue
return $true
}
else {
throw "解压后未找到 sigcheck.exe"
}
}
catch {
Write-Log "备用地址下载失败: $_" "ERROR"
throw "无法获取 sigcheck.exe,请手动下载并放入 $toolsDir"
}
}
}
function Get-FileListFromScannedTxt {
# 查找当前目录下所有包含“扫描件”的 .txt 文件(使用 .NET 方法避免 PowerShell 提供程序权限问题)
$txtFiles = @()
try {
$allFiles = [System.IO.Directory]::GetFiles($scriptRoot, "*.txt")
foreach ($file in $allFiles) {
if ([System.IO.Path]::GetFileName($file) -like "*扫描件*") {
$txtFiles += $file
}
}
}
catch {
Write-Log "扫描 txt 文件失败: $_" "ERROR"
return @()
}
if ($txtFiles.Count -eq 0) {
Write-Log "未找到包含“扫描件”的 .txt 文件" "ERROR"
return @()
}
$allPaths = @()
foreach ($txt in $txtFiles) {
Write-Log "读取文件列表: $([System.IO.Path]::GetFileName($txt))"
try {
$lines = [System.IO.File]::ReadAllLines($txt, [System.Text.Encoding]::UTF8)
}
catch {
Write-Log "读取文件 $txt 失败: $_" "ERROR"
continue
}
for ($i = 1; $i -lt $lines.Length; $i++) {
$line = $lines[$i].Trim()
if ([string]::IsNullOrEmpty($line)) { continue }
$firstCommaIndex = $line.IndexOf(',')
if ($firstCommaIndex -ge 0) {
$pathPart = $line.Substring(0, $firstCommaIndex)
} else {
$pathPart = $line
}
$pathPart = $pathPart.Trim('"').Trim()
if (-not [string]::IsNullOrEmpty($pathPart)) {
$allPaths += $pathPart
}
}
}
$allPaths = $allPaths | Select-Object -Unique
Write-Log "从 txt 文件中提取到 $($allPaths.Count) 个路径(去重后)"
$existingFiles = @()
foreach ($p in $allPaths) {
$fullPath = $p
if (-not [System.IO.Path]::IsPathRooted($p)) {
$fullPath = [System.IO.Path]::Combine($scriptRoot, $p)
}
# 拒绝注册表等非文件系统路径
if ($fullPath -match '^[A-Za-z]+:.*' -and $fullPath -notmatch '^[A-Za-z]:[\\/]') {
Write-Log "跳过非文件系统路径: $p" "WARN"
$script:results += [PSCustomObject]@{
FilePath = $p
Status = "Invalid path type"
Verified = "N/A"
Permalink = ""
Error = "Not a file system path"
}
$script:errorFiles++
continue
}
# 使用 .NET 方法检查文件存在性(不依赖 PowerShell 提供程序)
if ([System.IO.File]::Exists($fullPath)) {
$existingFiles += $fullPath
} else {
$errMsg = "File does not exist"
if ([System.IO.Directory]::Exists($fullPath)) {
$errMsg = "Is a directory"
Write-Log "路径指向目录,跳过: $p" "WARN"
} else {
Write-Log "文件不存在,跳过: $p" "WARN"
}
$script:results += [PSCustomObject]@{
FilePath = $p
Status = "File not found"
Verified = "N/A"
Permalink = ""
Error = $errMsg
}
$script:errorFiles++
}
}
Write-Log "有效文件数: $($existingFiles.Count)"
return $existingFiles
}
function Invoke-SigcheckScan {
param([string]$FilePath)
$arguments = "-accepteula -nobanner -vt `"$FilePath`""
Write-Log "扫描文件: $FilePath" -Level "INFO"
try {
try {
$output = & $sigcheckExe -accepteula -nobanner -vt $FilePath 2>&1
} catch {
Write-Error "执行 sigcheck 失败: $_"
exit 1
}
$verified = "Unknown"
$status = "Unknown"
$permalink = ""
for($i = 0; $i -lt $output.split(" ").length; $i++) {
$line = $output.split(" ")[$i]
if($line -ne ''){
#write-host $line
if ($line -eq "Verified:") {
#write-host "Verified match"
$verified = $output.split(" ")[$i+1]
}
if ($line -eq "VT detection:") {
#write-host "VT detection"
$status = $output.split(" ")[$i+1]
}
if ($line -eq "VT link:") {
#write-host "VT link"
$permalink = $output.split(" ")[$i+1]
}
}
}
if ($status -eq "Unknown") {
$status = "No VirusTotal result"
Write-Log "未获取到 VirusTotal 结果" "WARN"
}
Write-Log "结果 -> Verified: $verified, Status: $status" "OK"
return [PSCustomObject]@{
FilePath = $FilePath
Status = $status
Verified = $verified
Permalink = $permalink
Error = ""
}
}
catch {
Write-Log "扫描文件失败 [$FilePath]: $_" "ERROR"
$script:errorFiles++
return [PSCustomObject]@{
FilePath = $FilePath
Status = "Scan failed"
Verified = "N/A"
Permalink = ""
Error = $_.Exception.Message
}
}
}
# ========== 5. 主流程 ==========
try {
# 创建输出目录(桌面)
if (-not (Test-Path $logDir)) {
try {
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
Write-Log "创建输出目录: $logDir" "OK"
}
catch {
Write-Log "无法创建输出目录 $logDir : $_" "ERROR"
throw "无法创建输出目录,请检查桌面写入权限"
}
}
Write-Log "========== 脚本开始 ==========" "INFO"
Write-Log "脚本位置: $scriptRoot" "INFO"
Write-Log "输出目录: $logDir" "INFO"
Ensure-Sigcheck
$fileList = Get-FileListFromScannedTxt
$script:totalFiles = $fileList.Count
Write-Log "待检查文件总数: $script:totalFiles"
if ($script:totalFiles -eq 0) {
Write-Log "没有有效文件可检查,脚本结束。" "WARN"
exit 0
}
$current = 0
foreach ($file in $fileList) {
$current++
Write-Progress -Activity "VirusTotal 扫描" -Status "进度 $current / $script:totalFiles" -PercentComplete (($current / $script:totalFiles) * 100)
$result = Invoke-SigcheckScan -FilePath $file
$script:results += $result
if ($result.Status -like "Malicious*") { $script:maliciousFiles++ }
if ($result.Status -eq "Scan failed" -or $result.Error) { $script:errorFiles++ }
$script:checkedFiles++
Start-Sleep -Milliseconds 500
}
$script:results | Export-Csv -Path $reportFile -NoTypeInformation -Encoding UTF8
Write-Log "报告已导出: $reportFile" "OK"
$summary = @"
========== 执行摘要 ==========
总文件数(去重后): $($script:totalFiles)
成功扫描: $($script:checkedFiles)
扫描失败: $($script:errorFiles)
检测到恶意文件: $($script:maliciousFiles)
报告位置: $reportFile
日志位置: $logFile
=============================
"@
Write-Log $summary
Write-Host $summary -ForegroundColor Cyan
Write-Log "========== 脚本结束 ==========" "OK"
}
catch {
Write-Log "脚本发生致命错误: $_" "ERROR"
Write-Host "详细错误信息: $($_.Exception.ToString())" -ForegroundColor Red
Write-Host "请尝试以下解决方案:" -ForegroundColor Yellow
Write-Host "1. 将脚本移动到桌面或文档目录后重新运行" -ForegroundColor Yellow
Write-Host "2. 暂时关闭 Windows Defender 的“受文件夹保护”功能" -ForegroundColor Yellow
Write-Host "3. 手动下载 sigcheck.exe 并放入脚本所在目录的 tools 文件夹" -ForegroundColor Yellow
Read-Host "按 Enter 退出"
exit 1
}
介绍:
该 PowerShell 脚本的主要功能是**批量检查指定文件的 VirusTotal 安全状态**。它从当前目录下所有文件名包含“扫描件”的 `.txt` 文件中读取文件路径列表,然后调用 Sysinternals 工具 **Sigcheck** 查询每个文件的签名信息和 VirusTotal 检测结果,最终生成 CSV 报告和日志文件。
下面按脚本结构逐部分详细解释。
---
## 1. 脚本元数据 (注释块)
```powershell
<#
.SYNOPSIS
使用 Sigcheck 批量校验文件安全性(VirusTotal)
.DESCRIPTION
读取当前目录下所有包含“扫描件”前缀的 .txt 文件,提取文件路径,
调用 Sigcheck -accepteula -nobanner -vt -vr 查询 VirusTotal,
生成 CSV 报告和 TXT 日志。自动请求管理员权限,并规避系统目录权限问题。
.NOTES
版本:1.5 (彻底解决权限拒绝问题,输出到桌面)
#>
```
- 说明脚本用途、版本和关键特性。
---
## 2. 自动请求管理员权限
```powershell
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
$isAdmin = $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
```
- 获取当前用户的安全主体,并检查是否属于 Administrator 角色。
```powershell
if (-not $isAdmin) {
# 以管理员身份重启本脚本
$scriptPath = $MyInvocation.MyCommand.Path
$arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
Start-Process powershell.exe -Verb RunAs -ArgumentList $arguments
pause
exit
}
```
- 如果不是管理员,则用 `Start-Process -Verb RunAs` 重新启动脚本(提权),原进程退出。
- `-NoProfile` 避免加载用户配置文件,`-ExecutionPolicy Bypass` 绕过执行策略限制。
---
## 3. 检查脚本是否位于系统保护目录
```powershell
$protectedPaths = @( $env:windir, $env:ProgramFiles, ... )
```
- 定义系统受保护路径列表(如 Windows 目录、Program Files、System32 等)。
```powershell
if ($isProtected) {
Write-Host "错误:脚本位于系统受保护目录中..."
exit 1
}
```
- 如果脚本存放在上述任何路径下,直接报错退出。原因是这些目录通常有严格的写入权限限制,脚本后续需要创建日志和临时文件,移动至普通目录(如桌面)可避免权限问题。
---
## 4. 配置输出路径
```powershell
$desktop = [Environment]::GetFolderPath("Desktop")
$logDir = Join-Path $desktop "SigcheckLogs_$(Get-Date -Format 'yyyyMMdd')"
$toolsDir = Join-Path $scriptRoot "tools"
$sigcheckExe = Join-Path $toolsDir "sigcheck.exe"
$logFile = Join-Path $logDir "CheckLog_$timestamp.txt"
$reportFile = Join-Path $logDir "VirusTotalReport_$timestamp.csv"
```
- 所有输出(日志、CSV报告)都放在桌面上的 `SigcheckLogs_日期` 文件夹中,避免权限问题。
- `tools` 子目录用于存放 `sigcheck.exe` 工具。
- 使用时间戳命名日志和报告文件,防止覆盖。
---
## 5. 辅助函数
### 5.1 `Write-Log`
```powershell
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$logLine = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] $Message"
# 根据级别用不同颜色输出到控制台
# 同时追加到 $logFile
}
```
- 统一输出带时间戳和级别的信息,同时写入控制台和日志文件(UTF-16 编码)。
- 支持 `INFO` / `WARN` / `ERROR` / `OK` 级别。
### 5.2 `Ensure-Sigcheck`
```powershell
function Ensure-Sigcheck {
# 检查 tools 目录和 sigcheck.exe 是否存在
# 若不存在,依次从两个 URL 下载:
# https://live.sysinternals.com/sigcheck.exe
# https://download.sysinternals.com/files/Sigcheck.zip
# 解压备用地址的 zip 包,并解除文件锁定(Unblock-File)
}
```
- 确保脚本运行所需的 `sigcheck.exe` 已准备就绪。
- 如果下载失败,抛出异常提示用户手动放置。
### 5.3 `Get-FileListFromScannedTxt`
**功能**:解析所有“扫描件”txt 文件,提取有效文件路径。
- **步骤 1**:使用 .NET `[System.IO.Directory]::GetFiles` 获取当前目录下所有 `.txt` 文件,过滤出文件名包含“扫描件”的。
- 为什么不用 `Get-ChildItem`?因为某些系统目录下 PowerShell 提供程序可能有权限限制,.NET 方法更底层、可靠。
- **步骤 2**:逐行读取每个 txt 文件(假设第一行为标题,从第二行开始读取)。
- 每行格式:`路径,其他信息,...`,提取第一个逗号之前的部分作为文件路径。
- 去除引号和空白。
- **步骤 3**:路径标准化和有效性检查。
- 相对路径转换为绝对路径(基于脚本所在目录)。
- 拒绝非文件系统路径(如 `HKLM:\...` 注册表路径)。
- 使用 `[System.IO.File]::Exists` 检查文件是否存在;若不存在,记录到结果数组并增加 `$errorFiles` 计数。
- 若存在,加入待扫描列表。
- **返回值**:有效文件路径的数组。
### 5.4 `Invoke-SigcheckScan`
**功能**:对单个文件调用 `sigcheck.exe` 并解析输出。
```powershell
$arguments = "-accepteula -nobanner -vt `"$FilePath`""
$output = & $sigcheckExe -accepteula -nobanner -vt $FilePath 2>&1
```
- 参数解释:
- `-accepteula`:自动接受最终用户许可协议(避免交互)。
- `-nobanner`:不显示启动横幅。
- `-vt`:查询 VirusTotal(需要联网)。
- 捕获标准输出和错误输出(`2>&1`)。
**解析输出**:
Sigcheck 的输出包含制表符分隔的键值对,例如:
```
Verified: Signed
VT detection: 0/70
VT link: https://www.virustotal.com/...
```
脚本遍历分割后的 token,匹配 `"Verified:"`、`"VT detection:"`、`"VT link:"`,提取对应的值。
- `Verified`:签名验证状态(Signed / Unsigned / ...)。
- `Status`:VirusTotal 检测结果,格式类似 `"0/70"` 或 `"Malicious/xx"`。
- `Permalink`:VirusTotal 报告的 URL。
返回包含这些字段的自定义对象。
---
## 6. 主流程
1. **创建输出目录**(桌面上的日期文件夹)。
2. **初始化日志**,写入开始信息。
3. **确保 Sigcheck 可用**(调用 `Ensure-Sigcheck`)。
4. **获取文件列表**(调用 `Get-FileListFromScannedTxt`)。
- 若无有效文件,直接退出。
5. **循环扫描每个文件**:
- 显示进度条(`Write-Progress`)。
- 调用 `Invoke-SigcheckScan`,将结果加入 `$script:results`。
- 更新统计计数器(总扫描数、恶意文件数、错误数)。
- 每次扫描后 `Start-Sleep -Milliseconds 500`,避免请求过快。
6. **导出 CSV 报告**:`$results | Export-Csv -Path $reportFile -NoTypeInformation`。
7. **输出执行摘要**:显示总文件数、成功/失败数、恶意文件数、报告路径等。
8. **异常处理**:任何致命错误(如无法创建目录、下载 sigcheck 失败)会捕获并显示友好提示,建议用户移动脚本或关闭 Defender 受保护文件夹。
---
## 关键设计思想
- **权限规避**:
- 请求管理员权限(某些文件可能需要读取权限)。
- 禁止脚本在系统目录运行,输出强制放到桌面。
- 使用 .NET 文件操作方法,避免 PowerShell 提供程序可能遇到的权限限制。
- **容错性**:
- 下载 sigcheck 失败时尝试备用地址。
- 无法写入日志时仅向控制台输出,不中断脚本。
- 对无效路径、目录、不存在的文件都进行记录并继续处理。
- **可追溯性**:
- 每个步骤都有详细的日志(控制台+文件)。
- 最终 CSV 报告包含每个文件的扫描状态、VT 链接,方便后续分析。
---
## 使用示例
1. 将脚本放在一个普通文件夹(如桌面)中。
2. 在该文件夹下创建 `tools` 子目录(脚本会自动下载 sigcheck,也可手动放入)。
3. 准备若干文件名包含“扫描件”的 `.txt` 文件,每行记录一个待检测文件的路径(绝对或相对于脚本目录)。
4. 以管理员身份运行脚本(或让脚本自动提权)。
5. 等待扫描完成,在桌面上的 `SigcheckLogs_日期` 文件夹中查看 CSV 报告和 TXT 日志。
---
## 注意事项
- 需要**联网**才能查询 VirusTotal。
- VirusTotal 有 API 频率限制,脚本中每次扫描后等待 0.5 秒可略微缓解,但仍可能因大量文件触发临时限制。
- `sigcheck.exe` 的 `-vt` 选项依赖于 Microsoft 的 VirusTotal 集成,实际检测结果可能不是实时最新。
- 脚本假定 txt 文件第一行为标题,实际数据从第二行开始;如果格式不同,需要调整 `Get-FileListFromScannedTxt` 中的解析逻辑。
以上即为该脚本的完整解释。如果需要进一步探讨某个函数的具体实现或潜在改进点,可以继续提问。
更多推荐

所有评论(0)