告别手动部署:为.NET嵌入式开发打造一键式ADB自动化部署流程

背景与痛点

在嵌入式设备(如Android Things、Linux开发板、IoT网关等)上开发.NET应用时,我们通常会遇到这样的循环:

  1. 在Visual Studio 2022中编写代码

  2. 编译生成可执行文件与依赖库

  3. 通过adb push手动复制文件到设备

  4. adb shell chmod +x赋予执行权限

  5. adb shell运行程序,查看输出

这一连串操作不仅繁琐,而且每次调试都要重复,极大地消耗了开发者的精力和时间。尤其当项目包含多个依赖文件时,手动复制极易遗漏或出错。

本文将向你展示如何利用Visual Studio 2022的“后期生成事件” + PowerShell脚本 + ADB,实现编译后自动部署、增量同步、权限赋予、独立窗口运行的全自动化流程,让嵌入式开发的迭代速度提升一个量级。


方案概览

  • 触发时机:VS 2022 项目生成事件(Build Events)中的“后期生成事件”(Post-build event)

  • 核心脚本:PowerShell(.ps1),负责处理文件同步、对比、权限与运行

  • 通信工具:ADB(Android Debug Bridge),用于与开发板交互

  • 运行方式:弹出独立CMD窗口,支持交互式程序

最终效果:每次编译成功后,自动将变更的文件推送至设备,并立即在独立窗口中启动程序,全程无需人工干预。


准备工作

1. 安装ADB并配置环境变量

  • 下载Android Platform Tools(包含adb.exe

  • 将ADB所在目录添加到系统PATH环境变量,或直接在脚本中使用绝对路径

  • 确保设备已开启USB调试并通过adb devices可见

2. Visual Studio 2022 项目准备

  • 任意.NET项目(控制台、类库等均可)

  • 确保项目生成输出包含可执行文件(若为类库,则主程序为同名的无后缀文件)


详细实施步骤

第一步:编写PowerShell部署脚本

在解决方案根目录或项目目录下,新建一个文本文件,命名为Deploy.ps1。将以下完整脚本粘贴进去。

param(
    [string]$LocalSrcDir,
    [string]$DeviceDstRoot = "/data/local/tmp",
    [string]$DeviceSubDir = "",
    [string]$TargetFileName = ""
)

# 清洗可能的多余引号
$LocalSrcDir = $LocalSrcDir.Trim('"')
$DeviceSubDir = $DeviceSubDir.Trim('"')
$TargetFileName = $TargetFileName.Trim('"')

$DevicePath = if ($DeviceSubDir) { "$DeviceDstRoot/$DeviceSubDir" } else { $DeviceDstRoot }

Write-Host "Deploying to $DevicePath ..."

# 1. 创建设备目标目录
adb shell mkdir -p "$DevicePath" 2>$null

# 2. 一次性获取远程所有文件的大小(用于增量对比)
$remoteLs = adb shell ls -l "$DevicePath" 2>$null
$remoteFileSizes = @{}
if ($LASTEXITCODE -eq 0 -and $remoteLs) {
    $lines = $remoteLs -split "`r`n" | Where-Object { $_ -and $_ -notmatch '^total\s+' }
    foreach ($line in $lines) {
        $fields = $line.Trim() -split '\s+'
        if ($fields.Count -ge 9) {
            $size = [int64]$fields[4]          # ls -l 的第5列是大小
            $fname = $fields[-1]               # 最后一列是文件名
            $remoteFileSizes[$fname] = $size
        }
    }
}

# 3. 获取本地所有文件
$LocalFiles = Get-ChildItem -Path $LocalSrcDir -File

# 4. 遍历并仅推送大小变化的文件
foreach ($file in $LocalFiles) {
    $localSize = $file.Length
    $remoteSize = $remoteFileSizes[$file.Name]

    if ($remoteSize -ne $null -and $localSize -eq $remoteSize) {
        Write-Host "Skipping $($file.Name) (size unchanged)"
        continue
    }

    Write-Host "Pushing $($file.Name) ..."
    adb push "$($file.FullName)" "$DevicePath/$($file.Name)"
    if ($LASTEXITCODE -ne 0) {
        Write-Error "Failed to push $($file.Name)"
        exit 1
    }
}

# 5. 处理可执行文件:去除 .dll 后缀,赋权并弹出CMD运行
if ($TargetFileName) {
    $ExeBaseName = $TargetFileName
    if ($ExeBaseName -match '\.dll$') {
        $ExeBaseName = $ExeBaseName -replace '\.dll$', ''
        Write-Host "Detected .dll, using base name: $ExeBaseName"
    }

    $remoteExe = "$DevicePath/$ExeBaseName"

    adb shell test -f "$remoteExe" 2>$null
    if ($LASTEXITCODE -ne 0) {
        Write-Warning "Executable '$remoteExe' not found on device. Skipping run."
    } else {
        Write-Host "Setting execute permission for $remoteExe"
        adb shell chmod +x "$remoteExe"

        # 弹出独立的CMD窗口,支持交互
        Write-Host "Launching CMD window for $remoteExe ..."
        Start-Process cmd -ArgumentList "/k adb shell `"$remoteExe`""
        # 若希望程序结束后窗口自动关闭,可将 /k 改为 /c
    }
}

Write-Host "Deployment completed."

脚本核心设计思想

  • 增量同步:通过 ls -l 一次性获取远程文件大小,与本地文件大小比对,仅推送变更文件,大幅减少传输时间。

  • 后缀处理:自动识别 .dll 并剥离,适用于生成类库的项目(实际可执行文件为无后缀同名文件)。

  • 独立窗口:使用 Start-Process cmd 打开新窗口,避免程序在VS后台阻塞,且支持交互式输入(如串口调试、菜单操作)。

  • 健壮性:检查文件存在性,错误时退出并返回非零码,让VS感知失败。

第二步:配置Visual Studio后期生成事件

  1. 在解决方案资源管理器中,右键点击你的项目 → 选择 “属性”

  2. 切换到 “生成事件” 选项卡。

  3. 在 “后期生成事件命令行” 文本框中,输入以下命令:

cmd
powershell -ExecutionPolicy Bypass -File $(SolutionDir)Deploy.ps1 -LocalSrcDir "$(TargetDir)" -DeviceDstRoot "/root" -DeviceSubDir "$(ProjectName)" -TargetFileName "$(TargetFileName)"

参数说明

 
参数含义示例
-LocalSrcDir 本地生成输出目录 $(TargetDir) 展开为 bin\Debug\net8.0\
-DeviceDstRoot 设备目标根目录 可自定义,如 /sdcard/
-DeviceSubDir 设备子目录名(建议用项目名隔离) $(ProjectName)
-TargetFileName 主可执行文件名(含后缀) $(TargetFileName)

注意:$(SolutionDir) 和 $(ProjectDir) 是VS宏,会在构建时展开。确保脚本保存在解决方案根目录,或调整为实际路径。

第三步:编译测试

按 Ctrl+Shift+B 或点击“生成”按钮,观察“输出”窗口。你会看到类似以下的日志:

text
Deploying to /data/local/tmp/MyProject ...
Pushing MyApp.dll ...
Pushing MyApp.runtimeconfig.json ...
Skipping SomeLib.dll (size unchanged)
Detected .dll, using base name: MyApp
Setting execute permission for /data/local/tmp/MyProject/MyApp
Launching CMD window for /data/local/tmp/MyProject/MyApp ...
Deployment completed.

同时,一个新CMD窗口会弹出,其中运行你的设备程序,你可以进行交互操作。


进阶定制

1. 修改运行参数

若你的程序需要命令行参数,可修改脚本中的运行行:

powershell
Start-Process cmd -ArgumentList "/k adb shell `"$remoteExe --port 8080 --verbose`""

2. 后台运行(不弹出窗口)

若希望静默运行(不显示窗口),可使用:

powershell
Start-Process -WindowStyle Hidden -FilePath "adb" -ArgumentList "shell $remoteExe"

但此时无法交互,适合服务类程序。

3. 清理远程多余文件

如果需要在推送前删除设备目录下本地已不存在的文件,可在获取远程文件后增加对比逻辑,使用 adb shell rm 删除。

4. 支持子目录结构

若你的输出包含子文件夹(如 publish 目录),可将 Get-ChildItem -File 改为 Get-ChildItem -Recurse -File,并调整远程路径的拼接以保留相对路径。


常见问题与解决办法

Q1: 执行脚本时提示“无法加载,因为在此系统上禁止运行脚本”

原因:PowerShell执行策略限制。
解决:在调用命令中添加 -ExecutionPolicy Bypass,我们已经加上。若仍失败,可临时修改系统策略(以管理员身份运行 Set-ExecutionPolicy RemoteSigned)。

Q2: 后期生成事件中参数被错误解析,导致路径包含额外字符串

原因:引号使用不当或空格导致。
解决:确保每个参数值都用双引号括起,且参数名与值之间有空格。避免将整个参数字符串放在一个引号内。

Q3: ls -l 输出格式与脚本预期不符(如列数不同)

原因:不同嵌入式系统(如Android、OpenWrt)的 ls 输出可能略有差异(如缺少用户组列)。
解决:在设备上手动执行 adb shell ls -l /data/local/tmp/,观察字段位置。若大小字段不在第5列(索引4),调整脚本中 $fields[4] 的索引值。

Q4: 设备端程序运行后立即退出,看不到输出

原因:程序执行完即结束,窗口可能闪退。
解决:若你使用 /k 参数,窗口不会关闭,可以查看输出。若程序本身是快速退出,可在程序末尾增加 Console.ReadKey() 或延时。

Q5: 权限不足,无法给文件添加执行权限

原因:ADB shell 可能不是 root 用户。
解决:若文件在 /data/local/tmp 下,通常普通用户也可 chmod。如果仍失败,尝试使用 adb root 获取 root 权限(需设备支持)。


总结与延伸

通过上述方案,我们成功将“复制-赋权-运行”三部曲整合到一次编译过程中,不仅节省了大量手动操作时间,还通过文件大小比对实现了智能增量同步。这套流程特别适合嵌入式开发的快速迭代场景,无论是C#、.NET Core还是.NET 5/6/7/8项目,均可适用。

未来,你还可以将这套脚本与CI/CD系统(如GitHub Actions、Azure DevOps)结合,实现自动化测试和发布。甚至可扩展为支持多设备同时部署,进一步提升效率。

希望本文能帮助你摆脱繁琐的部署困扰,将精力更多投入到代码逻辑和功能创新上。如果你在实践中有任何疑问或优化建议,欢迎在评论区留言讨论!

以上信息来自AI,使用时请根据实际情况进行修改

posted @ 2026-07-27 23:39  AnAng  阅读(8)  评论(0)    收藏  举报