AI模型:Deepseek

开发方法:AI+人工

使用方法:

记事本保存编码utf8bom+代码utf8,请重下代码

管理员运行powershell→cd 脚本目录→./checkFile.ps1

注意事项:

  1. 输入文件是文章《AI工具:ProcessMonitor监控程序安装工具》的结果文件:https://blog.csdn.net/humors221/article/details/159733489

  2. **解析文件内容**:

       - 跳过第一行(假定是标题行)。

       - 每行取第一个逗号之前的内容,并去除首尾双引号和空格。

       - 收集所有路径(去重)。

代码:

<#
.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` 中的解析逻辑。

以上即为该脚本的完整解释。如果需要进一步探讨某个函数的具体实现或潜在改进点,可以继续提问。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐