AI模型:Deepseek
开发:AI+人工
测试环境:鸿蒙4.2+windows11
准备:手机数据线连接电脑+USB连接方式为传输文件+手机被系统识别+授权
保存:记事本编码ansi+所有类型+名称.ps1
运行:右键使用Powershell运行
代码:
# ====================== 辅助函数 ======================
# 确保 sigcheck.exe 存在,如果不存在则自动下载
function Ensure-Sigcheck {
# 检查脚本目录下是否存在 sigcheck.exe
$scriptParent = split-path -parent $env:SCRIPT_PATH
$sigcheckExe = Join-Path $scriptParent "sigcheck.exe"
if (Test-Path $sigcheckExe) {
#Write-Host "Sigcheck 已存在: $sigcheckExe"
return $sigcheckExe
}
Write-Host "Sigcheck 未找到,正在下载..." "WARN"
$sigcheckUrl = "https://live.sysinternals.com/sigcheck.exe"
try {
Invoke-WebRequest -Uri $sigcheckUrl -OutFile $sigcheckExe -UseBasicParsing -ErrorAction Stop
if (Test-Path $sigcheckExe) {
Write-Host "Sigcheck 下载成功" "OK"
Unblock-File -Path $sigcheckExe -ErrorAction SilentlyContinue
return $sigcheckExe
}
}
catch {
Write-Host "主地址下载失败: $_" "WARN"
$sigcheckUrlBackup = "https://download.sysinternals.com/files/Sigcheck.zip"
$zipPath = Join-Path $scriptParent "sigcheck.zip"
try {
$zipPath = Join-Path $scriptParent "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-Host "Sigcheck 下载成功 (备用地址)" "OK"
Unblock-File -Path $sigcheckExe -ErrorAction SilentlyContinue
return $sigcheckExe
}
else {
throw "解压后未找到 sigcheck.exe"
}
}
catch {
Write-Host "备用地址下载失败: $_" "ERROR"
throw "无法获取 sigcheck.exe,请手动下载并放入 $scriptParent"
}
}
}
# 递归获取 MTP 文件并处理(复制+扫描+删除)
function Invoke-MTPFileScan {
param(
$FolderObject,
$RelativePath = "",
[switch]$AutoScan,
[ref]$Stats # 用于统计成功/失败数量
)
$items = $FolderObject.Items()
foreach ($item in $items) {
$currentPath = if ($RelativePath) { "$RelativePath\$($item.Name)" } else { $item.Name }
if ($item.IsFolder) {
if (-not $AutoScan) {
$choice = Read-Host -Prompt "是否扫描文件夹 [$currentPath] ? (y/n)"
$scanThis = ($choice -eq 'y' -or $choice -eq 'Y')
} else {
$scanThis = $true
}
if ($scanThis) {
$subFolder = $item.GetFolder()
if ($subFolder) {
Invoke-MTPFileScan -FolderObject $subFolder -RelativePath $currentPath -AutoScan -Stats $Stats
}
}
} else {
# 跳过以点开头的隐藏文件
if ($item.Name -like '.*') {
Write-Warning "跳过隐藏文件: $currentPath"
continue
}
# 尝试获取文件大小,跳过空文件(大小显示为 "0 KB" 或 "0 字节")
try {
$parentFolder = $item.ParentFolder()
if ($parentFolder) {
$sizeStr = $parentFolder.GetDetailsOf($item, 1) # 大小列
if ($sizeStr -match "0\s*(KB|字节|k)") {
Write-Warning "文件大小为 0,跳过: $currentPath"
continue
}
}
} catch {
Write-Warning "无法获取文件大小,继续尝试处理: $currentPath"
}
$fileName = $item.Name
$localFile = "$env:TEMP\mobile\$fileName"
$localDir = Split-Path $localFile -Parent
if (-not (Test-Path $localDir)) {
New-Item -ItemType Directory -Path $localDir -Force | Out-Null
}
Write-Host "正在处理: $currentPath" -ForegroundColor Cyan
$success = $false
# 复制
try {
Copy-MTPFileToLocal -MTPFileItem $item -DestFilePath $localFile
} catch {
Write-Error "复制失败: $currentPath - $($_.Exception.Message)"
$Stats.Value['failed']++
return
}
# 扫描
try {
$result = Submit-FilesToVirusTotal -FilePath $localFile -RealFilePath $currentPath
if ($result) {
# 可将结果实时追加到 CSV,这里简单显示
Write-Host " 扫描结果: Verified=$($result.Verified), Detection=$($result.VT_Detection)" -ForegroundColor Green
} else {
throw "VirusTotal 扫描未返回有效结果"
}
} catch {
Write-Error "扫描失败: $currentPath - $($_.Exception.Message)"
$Stats.Value['failed']++
$success = $false
}
# 删除临时文件
try {
if (Test-Path $localFile) {
Remove-Item -Path $localFile -Force -ErrorAction Stop
}
} catch {
Write-Warning "删除临时文件失败: $localFile - $($_.Exception.Message)"
}
if ($success) {
$Stats.Value['succeeded']++
Write-Host " 完成: $currentPath" -ForegroundColor Green
} else {
$Stats.Value['failed']++
}
$Stats.Value['processed']++
}
}
}
# 复制单个 MTP 文件到本地
function Copy-MTPFileToLocal {
param(
[Parameter(Mandatory = $true)]$MTPFileItem,
[Parameter(Mandatory = $true)][string]$DestFilePath
)
$shell = New-Object -ComObject Shell.Application
$destDir = Split-Path $DestFilePath -Parent
if (-not (Test-Path $destDir)) {
New-Item -ItemType Directory -Path $destDir -Force | Out-Null
}
$destFolderObj = $shell.NameSpace($destDir)
# 0x14 = 不显示进度对话框 + 处理文件可能出现的对话框
$destFolderObj.CopyHere($MTPFileItem, 0x14)
# 等待复制完成(最多 60 秒)
$timeout = 60
while ($timeout -gt 0 -and -not (Test-Path $DestFilePath)) {
Start-Sleep -Milliseconds 500
$timeout -= 0.5
}
if (Test-Path $DestFilePath) {
return $DestFilePath
} else {
throw "复制超时 (60秒): $($MTPFileItem.Name)"
}
}
# 使用 sigcheck 扫描本地文件并返回结果对象
function Submit-FilesToVirusTotal {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0)]
[string]$FilePath, # 临时文件路径
[Parameter(Mandatory=$true)]
[string]$RealFilePath # 手机文件路径
)
# 检查 sigcheck.exe
$sigcheck = Ensure-Sigcheck
if (-not (Test-Path $FilePath)) {
throw "文件不存在: $FilePath"
}
Write-Host " 正在上传到 VirusTotal: $RealFilePath" -ForegroundColor Gray
try {
# -accepteula 静默接受许可,-nobanner 不显示版权信息
$output = & $sigcheck -accepteula -nobanner -vt $FilePath 2>&1 | Out-String
} catch {
throw "执行 sigcheck 失败: $($_.Exception.Message)"
}
# 解析输出(基于制表符分隔)
$verified = ""
$vtDetection = ""
$vtLink = ""
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"
$vtDetection = $output.split(" ")[$i+1]
}
if ($line -eq "VT link:") {
#write-host "VT link"
$vtLink = $output.split(" ")[$i+1]
}
}
}
if (-not $verified -and -not $vtDetection -and -not $vtLink) {
Write-Warning "解析输出失败,原始输出: $output"
return $null
}
# 可选:将单次结果追加到 CSV
$result = [PSCustomObject]@{
文件名 = $RealFilePath
Verified = $verified
VT_Detection = $vtDetection
VT_Link = $vtLink
扫描时间 = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
# 实时追加到 CSV(避免内存积累)
$csvPath = $env:CSV_FILE
if (-not (Test-Path $csvPath)) {
$result | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
} else {
$result | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 -Append
}
return $result
}
# 清理临时目录
function Clear-MobileTemp {
$tempDir = "$env:TEMP\mobile"
if (Test-Path $tempDir) {
Write-Host "正在清理临时目录: $tempDir" -ForegroundColor Gray
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# ====================== 主程序 ======================
# ========== 1. 自动请求管理员权限 ==========
$scriptPath = if ($PSCommandPath) { $PSCommandPath } else { $MyInvocation.MyCommand.Path }
$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 -WindowStyle Maximized
exit
}
catch {
Write-Host "请求管理员权限失败,请右键点击 PowerShell 选择"以管理员身份运行"后重新执行脚本。" -ForegroundColor Red
exit 1
}
}
Write-Host "已获得管理员权限,继续执行..." -ForegroundColor Green
$stats = @{ processed = 0; succeeded = 0; failed = 0 }
$tempRoot = "$env:TEMP\mobile"
try {
Write-Host "=== 手机文件 VirusTotal 扫描脚本 ===" -ForegroundColor Yellow
# 创建临时根目录
if (Test-Path $tempRoot) {
Remove-Item $tempRoot -Recurse -Force -ErrorAction Stop
}
New-Item -ItemType Directory -Path $tempRoot -Force | Out-Null
# 创建 Shell 对象
$shell = New-Object -ComObject Shell.Application -ErrorAction Stop
if ($null -eq $shell) {
throw "无法创建 Shell.Application 对象,请检查系统组件。"
}
# 定位到"此电脑"
$myComputer = $shell.NameSpace(0x11)
if ($null -eq $myComputer -or -not $myComputer.Items()) {
throw "无法获取此电脑文件夹。"
}
# ========== 1. 选择设备类型(便携设备) ==========
$allItems = $myComputer.Items()
Write-Host "`n此电脑中的项目:" -ForegroundColor Cyan
$index = 1
$itemList = @()
foreach ($item in $allItems) {
Write-Host " $index. Name: $($item.Name), Type: $($item.Type)"
$itemList += $item
$index++
}
$selectedTypeIndex = Read-Host -Prompt "`n请选择你的设备类型(输入序号,例如便携设备通常对应 Type 为便携设备)"
$selectedTypeItem = $itemList[[int]$selectedTypeIndex - 1]
$tp = $selectedTypeItem.Type
$portableDevices = $allItems | Where-Object { $_.Type -eq $tp }
if (-not $portableDevices) {
throw "未找到 Type 为 '$tp' 的设备,请确认手机已连接并解锁,且处于 MTP 模式。"
}
# ========== 2. 选择具体设备(支持部分匹配,但改为序号选择) ==========
Write-Host "`n找到以下便携设备:" -ForegroundColor Cyan
$deviceList = @($portableDevices)
$deviceIndex = 1
foreach ($device in $deviceList) {
Write-Host " $deviceIndex. Name: $($device.Name), Type: $($device.Type)"
$deviceIndex++
}
$selectedDeviceNum = Read-Host -Prompt "请选择你的设备(输入序号)"
$device = $deviceList[[int]$selectedDeviceNum - 1]
Write-Host "已选择设备: $($device.Name)" -ForegroundColor Green
# 进入设备内部
$deviceFolder = $device.GetFolder()
if (-not $deviceFolder) {
throw "无法访问设备文件夹,请确认手机已解锁。"
}
$storageAreas = $deviceFolder.Items()
# ========== 3. 选择内部存储区域 ==========
Write-Host "`n设备中的存储区域:" -ForegroundColor Cyan
$storageList = @($storageAreas)
$storageIndex = 1
foreach ($area in $storageList) {
Write-Host " $storageIndex. Name: $($area.Name), Type: $($area.Type)"
$storageIndex++
}
$selectedStorageNum = Read-Host -Prompt "请选择内部存储区域(输入序号,通常为内部共享存储空间或Phone)"
$internalStorage = $storageList[[int]$selectedStorageNum - 1]
$storageFolder = $internalStorage.GetFolder()
if (-not $storageFolder) {
throw "无法打开内部存储文件夹。"
}
Write-Host "`n开始扫描手机内部存储,请按提示确认是否进入每个文件夹..." -ForegroundColor Yellow
$env:SCRIPT_PATH = $scriptPath
$env:SCRIPT_PARENT = split-path -parent $env:SCRIPT_PATH
$env:FILE_STAMP = Get-Date -Format "yyyyMMdd_HHmmss"
$env:CSV_FILE = Join-Path $env:SCRIPT_PARENT "VirusTotalResults_$env:FILE_STAMP.csv"
Invoke-MTPFileScan -FolderObject $storageFolder -Stats ([ref]$stats)
Write-Host "`n=== 扫描完成 ===" -ForegroundColor Yellow
Write-Host "总处理文件数: $($stats.processed)" -ForegroundColor Cyan
Write-Host "成功扫描: $($stats.succeeded)" -ForegroundColor Green
Write-Host "失败: $($stats.failed)" -ForegroundColor Red
Write-Host "结果已保存到: $env:CSV_FILE" -ForegroundColor Green
pause
}
catch {
Write-Error "脚本执行出错: $($_.Exception.Message)"
pause
exit 1
}
finally {
# 清理临时目录
Clear-MobileTemp
}
说明:
这是一个功能完整的 PowerShell 脚本,用于连接 Android 手机(MTP 模式),遍历内部存储中的文件,将每个文件复制到本地临时目录,调用 Sysinternals 的 `sigcheck.exe` 上传至 VirusTotal 进行病毒扫描,最后输出扫描结果并清理临时文件。脚本会自动请求管理员权限,支持交互式选择设备/文件夹,并生成 CSV 格式的报告。
下面我将详细解释脚本的每个部分。
---
## 一、辅助函数
### 1. `Ensure-Sigcheck`
- **目的**:确保 `sigcheck.exe` 存在于脚本所在目录,若不存在则自动下载。
- **步骤**:
1. 获取脚本所在目录(`$scriptParent`)。
2. 检查 `$scriptParent\sigcheck.exe` 是否存在,若存在直接返回路径。
3. 若不存在,首先尝试从 `https://live.sysinternals.com/sigcheck.exe` 直接下载。
4. 如果失败,则从备用地址下载 `Sigcheck.zip`,解压到当前目录,删除 zip 文件。
5. 最终若仍未找到,抛出异常要求用户手动下载。
- **关键点**:`Unblock-File` 用于解除可能被系统标记为不安全文件的锁定;`-UseBasicParsing` 避免 IE 引擎依赖。
### 2. `Invoke-MTPFileScan`
- **目的**:递归遍历 MTP 设备中的文件夹和文件,对每个文件执行复制 → 扫描 → 删除临时文件。
- **参数**:
- `$FolderObject`:当前 MTP 文件夹对象(来自 `Shell.Application`)。
- `$RelativePath`:当前相对路径(用于显示和 CSV 记录)。
- `$AutoScan`:是否自动扫描子文件夹(若不开启,则每个文件夹询问用户)。
- `$Stats`:引用传递的哈希表,用于累计处理成功/失败数量。
- **处理逻辑**:
- 遍历文件夹内所有项(`$items`)。
- 若为文件夹:
- 根据 `$AutoScan` 决定是否询问用户。
- 若允许扫描,递归调用自身。
- 若为文件:
- **跳过隐藏文件**:名称以 `.` 开头。
- **跳过空文件**:通过 `ParentFolder().GetDetailsOf(item, 1)` 获取大小列,若匹配 `0 KB` 或 `0 字节` 则跳过。
- 构造临时文件路径:`$env:TEMP\mobile\<fileName>`。
- 调用 `Copy-MTPFileToLocal` 复制到本地。
- 调用 `Submit-FilesToVirusTotal` 进行扫描。
- 删除本地临时文件。
- 更新统计信息。
### 3. `Copy-MTPFileToLocal`
- **目的**:将 MTP 设备中的单个文件复制到本地指定路径。
- **实现**:
- 使用 `Shell.Application` 的 `CopyHere` 方法进行复制。
- 第二个参数 `0x14`(十六进制 20)含义:
- `0x10`(16)→ 不显示进度对话框。
- `0x04`(4)→ 处理可能出现的文件冲突对话框(静默覆盖?实际上文档复杂,这里主要是避免挂起)。
- 轮询等待文件出现,超时 60 秒后抛出异常。
### 4. `Submit-FilesToVirusTotal`
- **目的**:调用 `sigcheck.exe` 对本地文件进行 VirusTotal 扫描,解析输出,并将结果追加到 CSV。
- **参数**:
- `$FilePath`:本地临时文件路径。
- `$RealFilePath`:手机中的原始路径(用于记录)。
- **流程**:
1. 调用 `Ensure-Sigcheck` 确保 `sigcheck.exe` 可用。
2. 执行命令:`& $sigcheck -accepteula -nobanner -vt $FilePath`。
3. 捕获输出(字符串)。
4. **解析输出**:脚本假设输出是制表符分隔的格式。通过 `.split("`t")` 分割,寻找 `Verified:`、`VT detection:`、`VT link:` 等关键字,并取下一个字段作为值。
5. 构建一个 `PSCustomObject`,包含文件名、Verified、VT_Detection、VT_Link、扫描时间。
6. 实时追加到 CSV 文件(若文件不存在则创建并写表头,否则追加)。
7. 返回该对象。
- **注意**:如果解析失败,返回 `$null`。
### 5. `Clear-MobileTemp`
- **目的**:删除 `%TEMP%\mobile` 目录,清理复制到本地的临时文件。
---
## 二、主程序流程
### 1. 自动请求管理员权限
- 检查当前进程是否具有管理员权限。
- 如果没有,使用 `Start-Process -Verb RunAs` 重新以管理员身份启动脚本。
- 退出当前非管理员进程。
- 这是很多需要访问系统 Shell 对象或某些注册表/文件操作的脚本的标准做法。
### 2. 初始化统计和临时目录
- `$stats = @{ processed=0; succeeded=0; failed=0 }`
- 清理并重新创建 `$env:TEMP\mobile`。
### 3. 创建 Shell 对象并定位到"此电脑"
- `$shell = New-Object -ComObject Shell.Application`
- `$myComputer = $shell.NameSpace(0x11)` → `0x11` 是 `ssfDRIVES`(我的电脑)。
### 4. 选择设备类型(便携设备)
- 列出"此电脑"中的所有项目(`$myComputer.Items()`),显示序号、名称、类型。
- 提示用户输入序号,选择一项作为"便携设备"类型(脚本中未使用实际类型过滤,而是让用户选择一个项目,然后取它的 `Type` 属性作为后续过滤条件,这其实是 **有问题的** ------ 用户应该直接选择便携设备,而不是先选一个参考项。实际逻辑是:用户任意选一个设备,然后脚本用它的 `Type` 去匹配所有相同类型的设备。这可能会选错,但可能用户通常会选自己的手机,从而得到正确类型。)
- 用这个 `Type` 筛选出所有便携设备。
### 5. 选择具体设备
- 列出所有便携设备,用户输入序号选择。
- 调用 `$device.GetFolder()` 进入设备根目录。
### 6. 选择内部存储区域
- 设备内部通常包含"内部共享存储空间"、"Phone"或类似名称的存储区域。
- 列出所有子项,用户选择其一。
- 调用 `$storageFolder = $internalStorage.GetFolder()`。
### 7. 开始扫描
- 设置环境变量:
- `$env:SCRIPT_PATH`:当前脚本路径(供 `Ensure-Sigcheck` 使用)。
- `$env:SCRIPT_PARENT`:脚本所在目录。
- `$env:FILE_STAMP`:时间戳。
- `$env:CSV_FILE`:输出 CSV 文件路径(位于脚本目录下,名称如 `VirusTotalResults_20260404_123456.csv`)。
- 调用 `Invoke-MTPFileScan -FolderObject $storageFolder -Stats ([ref]$stats)`。
- 该函数会递归遍历并处理文件,过程中实时输出扫描结果,并追加到 CSV。
### 8. 完成并清理
- 输出统计信息。
- 暂停(`pause`)等待用户按键。
- 无论是否发生异常,`finally` 块中调用 `Clear-MobileTemp` 删除临时文件。
---
## 三、潜在问题与改进建议
1. **设备类型选择逻辑混乱**
- 原意可能是让用户先选择类型为"便携设备"的项目,然后列出所有便携设备。但实现方式是先让用户任意选一个项目,然后用它的 `Type` 去匹配,这会导致如果用户误选了"本地磁盘",则 `Type` 变成"本地磁盘",后续只会列出所有本地磁盘,无法找到手机。
- **改进**:直接遍历 `$myComputer.Items()`,筛选 `$_.Type -eq "便携设备"`(或 `-like "*便携*"`),无需第一步用户输入。
2. **MTP 复制可能不稳定**
- `CopyHere` 是异步的,脚本通过轮询文件是否存在来等待,超时 60 秒。对于大文件可能不够。
- 可以考虑增大超时或使用 `ShellFolderItem.Verbs()` 中的"复制"动词,但更可靠的方式是使用 `IPortableDevice` COM 接口或 `MTP Drive Tool`。
3. **sigcheck 输出解析脆弱**
- 解析基于制表符分割,但实际输出可能有多行或格式变化。例如 `Verified:` 后面可能是 `Unsigned`,且 `VT detection:` 可能包含多个值。当前脚本只取第一个分割后的下一个字段,可能不准确。
- 建议使用正则表达式匹配:`(?m)^Verified:\s+(.*)$` 等。
4. **文件大小检测**
- 使用 `GetDetailsOf(item, 1)` 获取大小列,但不同语言系统下列索引不同(中文为"大小"列索引 1 正确,但英文系统可能也是 1?实际取决于 Shell 视图设置)。更可靠的是使用 `item.Size` 属性(MTP 文件对象通常支持 `Size` 属性)。
5. **并发与性能**
- 脚本是单线程顺序处理,对于大量文件可能很慢。可考虑并行上传(但需要控制 API 速率限制,VirusTotal 免费版有限制)。
6. **错误处理**
- 部分错误被捕获并记录,但有时会继续执行。例如复制失败后仍然尝试扫描,会导致二次错误。当前代码在复制失败时直接 `return`,没有更新失败计数?实际上在复制 catch 块中执行了 `$Stats.Value['failed']++` 然后 `return`,正确。
7. **管理员权限必要性**
- 脚本请求管理员权限,但 MTP 操作和 sigcheck 通常不需要管理员。不过如果脚本需要访问某些受保护的临时目录或执行某些注册表操作,可能必要。一般可以去掉管理员请求。
---
## 四、使用示例
1. 手机通过 USB 连接,选择"传输文件"(MTP 模式)。
2. 以管理员身份运行 PowerShell,执行脚本(或右键 Run with PowerShell)。
3. 按提示:
- 看到"此电脑中的项目"列表,随便选一个(比如你的手机),但脚本设计有问题,建议用户选择自己的手机设备。
- 然后选择具体设备(通常只有一个)。
- 选择内部存储(通常名为"Phone"或"内部共享存储空间")。
4. 脚本开始遍历文件夹,每个文件夹会询问是否进入(按 `y` 继续)。
5. 对每个文件,显示"正在处理",然后输出 `sigcheck` 的扫描结果(`Verified` 表示签名状态,`VT_Detection` 表示杀软检出率,`VT_Link` 是 VirusTotal 报告链接)。
6. 结束后输出 CSV 文件路径,按任意键退出。
---
## 五、总结
这是一个实用的手机文件病毒扫描脚本,利用了 Windows 自带的 Shell 接口访问 MTP 设备,结合 Sysinternals 工具完成 VirusTotal 上传。代码结构清晰,包含了下载依赖、权限提升、临时文件管理、CSV 输出等常见功能。主要不足之处在于设备选择逻辑和 sigcheck 输出解析的健壮性,但基本功能可以正常运行。