返回

解决VS Code PowerShell运行批处理命令序列问题

windows

VS Code PowerShell 中运行批处理命令序列的那些坑

平时喜欢用 VS Code, 也习惯了它的集成终端。但最近用 PowerShell 跑一批命令,踩了几个坑,记录一下。主要是想从 VS Code 的 PowerShell 终端里跑一些 Windows 命令行的东西,遇到点问题。

一、问题现象

具体来说,就是手动在 Windows 命令提示符(cmd,注意不是 PowerShell)里,这几个命令挨个执行没问题:

cd cmake\windows\rel
"C:\Program Files (x86)\Intel\oneAPI\setvars.bat"
vtune -collect hotspots --app-working-dir=%cd% -- %cd%/CMakeProject
set /p name="Enter Hotspot Folder name: "
vtune-gui %name%/%name%.vtune

这几个命令做了什么呢?首先,进入项目编译生成的可执行文件目录 cmake\windows\rel。然后,运行 setvars.bat 批处理文件来加载环境变量。接着,通过 vtune 命令启动性能分析器。分析结束后,需要输入一个文件夹名字,把这个文件夹名作为输入传给 vtune-gui 命令打开图形界面。

特别要说的是,vtunevtune-gui 不在 Windows 的环境变量 PATH 里。setvars.bat 会临时在当前 cmd 会话中设置路径,让我可以在命令行里直接敲 vtunevtune-gui

问题来了。把这些命令写到批处理文件(bfile.bat)里,从 VS Code 默认的 PowerShell 终端执行,情况是这样的:

PS MyProjectFolder> .\bfile.bat
MyProjectFolder> cd cmake\windows\rel
MyProjectFolder\cmake\windows\rel> "C:\Program Files (x86)\Intel\oneAPI\setvars.bat"
...setvars.bat 的输出...执行了,没报错...
PS MyProjectFolder>

你会发现, 批处理文件中的第三、四、五行命令压根没跑,PowerShell 也没提示什么错误。最后一行输出显示又回到了 MyProjectFolder 目录。 还有就是, 只有第一行和最后一行输出有 PS 前缀。第二、第三行这些输出都没有。

这可不行, 我总不能每次都手动敲这五个命令吧? 得想办法让批处理文件完整执行才行。

二、原因分析

问题出在几个地方:

  1. PowerShell 与 cmd 的差异: PowerShell 和 Windows cmd 虽然都能执行批处理文件,但它们对某些命令的解释、执行方式有差别。特别是涉及环境变量、路径的处理。
  2. 上下文切换: 运行 setvars.bat 确实改变了环境, 但是仅仅对 cmd 有效, PowerShell 调用了 cmd,cmd设置了自己的环境变量,cmd结束了,这个环境就没有了。PowerShell还是原本的PowerShell。后续命令的执行上下文其实回到了 PowerShell,而 PowerShell 并没有被 setvars.bat 影响。
  3. cd 命令行为。 cd 之后虽然显示进入了cmake\windows\rel, 但是这其实是 cmd 的行为, 实际上 PowerShell 当前工作目录是不会改变的。

三、解决办法

试试这几个办法:

1. 直接在 cmd 中运行

最简单的,就是别在 VS Code 的 PowerShell 终端里折腾,直接打开 cmd 窗口,运行你的批处理文件,或者手动敲命令。这样最省事,避免了 PowerShell 和 cmd 的兼容性问题。

2. 使用 Invoke-Expression (或 & )

PowerShell 提供了 Invoke-Expression (别名是 iex,也可以直接用 & 符号) 来执行字符串形式的命令。可以把整个批处理文件的内容作为字符串,然后用 Invoke-Expression 执行。但这样做会失去 cmd 设置环境变量的功能。所以我们要改写下批处理。

可以像这样做:

  1. 修改 bfile.bat:
@echo off
cd cmake\windows\rel
"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" > nul
vtune -collect hotspots --app-working-dir=%cd% -- %cd%/CMakeProject
set /p name="Enter Hotspot Folder name: "
vtune-gui %name%/%name%.vtune
  1. 建立一个bfile.ps1,PowerShell 文件:
$batFilePath = "$PSScriptRoot\bfile.bat" #保证兼容性, 获取文件的完整路径
cmd.exe /c "$batFilePath"  # 使用 cmd.exe /c 来执行, 这让 batch 在它自己的进程中执行

原理:

  • cmd.exe /c 可以理解为启动一个cmd进程执行了batch file. 而这又在 powershell 的统一控制之下。

3. 改造为 PowerShell 脚本

彻底点,可以把整个批处理文件改写成 PowerShell 脚本。 这能充分利用 PowerShell 的特性,避免各种兼容性问题。

# 进入目录
Set-Location "cmake\windows\rel"

# 执行 setvars.bat 并捕获输出
$env:Path += $(cmd.exe /c '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && echo %PATH%') -join ';'

# 执行 vtune
vtune -collect hotspots --app-working-dir="$PWD" -- "$PWD/CMakeProject"

# 获取用户输入
$name = Read-Host "Enter Hotspot Folder name"

# 执行 vtune-gui
& "$env:ProgramFiles (x86)\Intel\oneAPI\vtune\latest\bin64\vtune-gui.exe" "$name/$name.vtune"

原理:

  • Set-Location: PowerShell 的 cd 命令。$PWD 和 powershell 中的 %cd%等价。
  • cmd.exe /c: 执行 setvars.bat 并获取输出(注意这里的技巧:执行完 setvars.bat 后,再执行 echo %PATH% 来获取设置后的 PATH 值)。把 PATH 获取出来,然后拼接到了 $env:Path里。
  • &: 因为 vtune-gui 路径可能带有空格,为了保证正确执行,使用了 & 调用操作符, 并使用了明确的 vtune-gui 的位置避免出错。

代码拆解及进阶技巧:

  • 环境变量处理:
    上面的代码用 $(cmd.exe /c '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" && echo %PATH%') -join ';' 来捕获并合并路径. 你还可以这样做:

       $tempEnv = cmd.exe /c '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" > nul && set'
       $tempEnv | ForEach-Object {
            if ($_ -match '^(.+?)=(.*)
       $tempEnv = cmd.exe /c '"C:\Program Files (x86)\Intel\oneAPI\setvars.bat" > nul && set'
       $tempEnv | ForEach-Object {
            if ($_ -match '^(.+?)=(.*)$') {
             $env:($matches[1]) = $matches[2]
              }
       }
    
    #x27;
    ) { $env:($matches[1]) = $matches[2] } }

    这个更强大, 可以设置 setvars.bat 里所有设置的环境变量, 不仅仅是 PATH。 原理是用 cmd 执行 setvars.batset, 获取到所有的环境变量设置, 通过正则提取出 键=值,在 PowerShell 环境里重新建立出来。

  • 错误处理:

    实际应用中,强烈建议对命令执行结果进行判断, 并加上错误处理。比如,可以用 $LASTEXITCODE 来判断上一个命令的退出码:

    vtune -collect hotspots --app-working-dir="$PWD" -- "$PWD/CMakeProject"
    if ($LASTEXITCODE -ne 0) {
        Write-Error "vtune 执行失败!"
        exit $LASTEXITCODE  # 或者其他错误处理逻辑
    }
    
  • 用户输入安全:

    如果你担心用户输入的文件夹名有问题, 比如包含特殊字符或者路径分隔符,导致安全漏洞, 可以做一下输入验证或者转义:

     $name = Read-Host "Enter Hotspot Folder name"
      #简单点的净化
     $name =  $name -replace '[^a-zA-Z0-9_\-]', ''
    
      #或
      # [System.IO.Path]::GetInvalidFileNameChars() 获取非法文件名
    
      #其他保护....
    

4. 修改VSCode终端默认配置(慎用)

修改VSCode的设置。可以直接修改默认的shell。

具体步骤如下(慎重选择):

  1. 打开 VS Code 设置 (File > Preferences > Settings 或 Ctrl+,).
  2. 搜索 "terminal.integrated.shell.windows".
  3. 将其值改为 C:\Windows\System32\cmd.exe.
  4. 可以选择配置 profiles, 指定你运行某些 bat 文件使用 cmd.exe

严重注意 :

修改这个值会让所有新的终端都使用 cmd。 这会改变所有你用 VS Code 打开的终端。 你之后也许会需要再次修改配置才能在 PowerShell 里进行操作。

5. 使用 VSCode Tasks

通过VS Code的Task功能, 定义运行指令。

  1. 创建 tasks.json 文件:
    在你的项目根目录下创建 .vscode 文件夹(如果还没有的话)。然后在 .vscode 文件夹里创建 tasks.json 文件。

  2. 配置任务:

    {
        "version": "2.0.0",
        "tasks": [
            {
                "label": "Run VTune Analysis",
                "type": "shell",
                "command": "cmd.exe",
                "args": [
                    "/c",
                    "${workspaceFolder}\\cmake\\windows\\rel\\bfile.bat" //改到bfile.bat的位置。
    
                ],
                "problemMatcher": [],
                 "group": {
                    "kind": "build",
                    "isDefault": true
                }
    
            }
        ]
    }
    

这个task, 就是用了cmd 执行你的 bat 文件。

  • Ctrl+Shift+B 就可以启动 tasks。

这种办法也很棒, 通过VSCode原生的 tasks, 执行 cmd.exe。

这几个方法, 各有优缺点,按需选择吧。 个人更推荐改造为 PowerShell 脚本,并用一些小技巧完善它。