使用 Windows 沙箱运行 PowerShell 脚本
如果你的应用需要调用 Windows 命令或已有的 PowerShell 脚本,可以用 Windows 模板创建沙箱,通过 SDK 传入文件、执行脚本并读回产物。下面的例子把两组测试结果写成 CSV,在沙箱内统计通过和失败的用例数,然后把 JSON 结果读回本地。
准备账号和模板
从七牛云 API 密钥管理获取 API Key。扬州和达拉斯均提供公开的 Windows 11 基础模板。两个区域使用同一个完整别名 1001939809911/windows11,选择离业务较近的 API 地址即可。
| 区域 | API 地址 |
|---|---|
| 中国(扬州 1) | https://cn-yangzhou-1-sandbox.qiniuapi.com |
| 美国(达拉斯 1) | https://us-south-1-sandbox.qiniuapi.com |
在调用端安装 Node.js 22 或更高版本。下面的 TypeScript 在调用端运行,PowerShell 在远端 Windows 实例中运行;本地可以使用 Linux、macOS 或 Windows。新建一个目录,在该目录中安装依赖。
npm init -y
npm install e2b@2.49.0 dotenv
npm install -D tsx
在该目录创建 .env,填入自己的 API Key。以下配置使用扬州区域,如需在达拉斯运行,只需将 E2B_API_URL 换成上表中的达拉斯地址。API Key 只在本地或服务端使用。
E2B_API_KEY=<七牛云 API Key>
E2B_API_URL=https://cn-yangzhou-1-sandbox.qiniuapi.com
WINDOWS_TEMPLATE=1001939809911/windows11
模板所属账号也可以直接调用 Sandbox.create('windows11')。短名称优先匹配调用方账号下的模板;使用上面的完整别名可以明确指定这份公开模板。
在 Windows 中运行第一个任务
将以下代码保存为 test-windows.ts。示例中的 csv 是本地输入,C:\Windows\Temp 是沙箱内的目录。脚本通过 $PSScriptRoot 在同一目录中查找输入和写入结果。
import 'dotenv/config'
import { Sandbox } from 'e2b'
async function main() {
const { E2B_API_KEY: apiKey, E2B_API_URL: apiUrl, WINDOWS_TEMPLATE: template } = process.env
if (!apiKey || !apiUrl || !template) {
throw new Error('Set E2B_API_KEY, E2B_API_URL and WINDOWS_TEMPLATE')
}
const sandbox = await Sandbox.create(template, {
apiKey, apiUrl, timeoutMs: 5 * 60_000, requestTimeoutMs: 120_000,
})
try {
console.log('Sandbox:', sandbox.sandboxId)
const dir = String.raw`C:\Windows\Temp`
const csv = ['suite,passed,failed', 'unit,12,0', 'integration,7,1'].join('\r\n')
await sandbox.files.write(`${dir}\\checks.csv`, csv)
const script = `
$ErrorActionPreference = 'Stop'
$rows = Import-Csv -LiteralPath (Join-Path $PSScriptRoot 'checks.csv')
$report = [ordered]@{
passed = [int](($rows | Measure-Object -Property passed -Sum).Sum)
failed = [int](($rows | Measure-Object -Property failed -Sum).Sum)
}
$report | ConvertTo-Json -Compress | Set-Content -Encoding ASCII -LiteralPath (Join-Path $PSScriptRoot 'report.json')
`
await sandbox.files.write(`${dir}\\summarize.ps1`, script)
await sandbox.commands.run(`powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${dir}\\summarize.ps1"`)
const report = JSON.parse(await sandbox.files.read(`${dir}\\report.json`))
console.log(JSON.stringify(report))
if (report.passed !== 19 || report.failed !== 1) throw new Error('Unexpected report')
} finally {
await sandbox.kill()
}
}
main().catch(error => {
console.error(error.message)
process.exitCode = 1
})
运行示例。
npx tsx test-windows.ts
第一行显示本次沙箱 ID。第二行是从沙箱文件系统读回的报告。两组测试分别通过 12 项和 7 项,合计通过 19 项,失败 1 项。
{"passed":19,"failed":1}
理解运行结果和超时
Sandbox.create(template, …) 选择执行环境。示例中的 requestTimeoutMs: 120_000 给创建请求留出两分钟等待响应,timeoutMs: 5 * 60_000 设置实例的存活时间。一次命令的执行超时可在 commands.run(command, { timeoutMs: … }) 中单独指定。接入耗时任务时,要给上传、命令执行和下载产物一起预留实例时间。
一次成功的示例有两层结果。commands.run 返回说明汇总脚本执行成功;JSON 中的 failed: 1 是输入数据中的测试失败数。示例末尾的断言用于核对这组已知输入。接入发布流水线时,可改为先归档报告,再根据 report.failed 决定是否放行。若命令以非零状态退出,SDK 会抛出 CommandExitError,应先定位脚本错误。无论成功还是异常,finally 都会调用 kill() 释放实例。
将现有脚本和测试文件接入
假设仓库已经维护了 summarize.ps1 和 checks.csv,希望 CI 结束后在调用端留下 report.json。把这两个输入文件放在启动 Node.js 的目录中。保留上例中的沙箱创建和 finally,用以下代码替换 try 块里的内容,并将两个导入移到文件顶部。
import { openAsBlob } from 'node:fs'
import { writeFile } from 'node:fs/promises'
// 以下语句放在 try 块中,sandbox 是已经创建的实例。
const dir = String.raw`C:\Windows\Temp`
for (const name of ['checks.csv', 'summarize.ps1']) {
await sandbox.files.write(`${dir}\\${name}`, await openAsBlob(name))
}
await sandbox.commands.run(
`powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "${dir}\\summarize.ps1"`,
{ cwd: dir }
)
const bytes = await sandbox.files.read(`${dir}\\report.json`, { format: 'bytes' })
await writeFile('report.json', bytes)
cwd: dir 把脚本的工作目录设为上传目录,已有脚本可以通过 .\checks.csv 读取输入,通过 .\report.json 写出报告。
openAsBlob 保留本地文件的原始字节,适合上传已有脚本、ZIP 或命令行程序。format: 'bytes' 适合把输出原样保存到调用端。若脚本本身含中文且由 Windows PowerShell 5.1 执行,建议把 .ps1 保存为带 BOM 的 UTF-8。若报告里包含中文,应把快速开始中的 -Encoding ASCII 换成明确的 Unicode 编码。例如,下面的 PowerShell 语句会生成无 BOM 的 UTF-8 文件,调用方可以直接用 files.read 和 JSON.parse 读取。
$json = $report | ConvertTo-Json -Compress
[IO.File]::WriteAllText((Join-Path $PSScriptRoot 'report.json'), $json, [Text.UTF8Encoding]::new($false))
调用命令和定位失败
commands.run 的默认命令按 CMD 语法解释。运行 .ps1 文件时,像上例一样显式指定 powershell.exe -File;文件路径包含空格时保留双引号。示例的 -ExecutionPolicy Bypass 只作用于这次 PowerShell 进程,用来执行你自己提供的脚本。编写包含管道、变量或多行处理逻辑的命令时,把逻辑放进脚本文件,可以减少 TypeScript、CMD 和 PowerShell 之间的引号转义。
接入外部命令行工具时,PowerShell 脚本应将工具的退出状态传回调用方。例如,执行 tool.exe 后紧接着写 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }。调用端可以单独捕获非零退出,并把诊断信息交给任务日志。下面的命令故意返回 7,方便先验证错误处理逻辑。
import { CommandExitError } from 'e2b'
// 放在已创建沙箱的 try 块内,外层 finally 负责释放实例。
try {
await sandbox.commands.run('cmd.exe /c exit 7')
} catch (error) {
if (error instanceof CommandExitError) {
console.error('Command failed:', error.exitCode, error.stderr)
}
throw error
}
| 现象 | 检查方法 |
|---|---|
| 创建时返回 HTTP 401 | 核对调用端实际加载的 E2B_API_KEY,确认是有效的七牛云 API Key。 |
| 找不到模板或没有访问权限 | 使用上表中的区域地址和公开模板的完整别名。切换自定义模板时,核对同一区域内的模板名称与当前账号的访问权限。 |
创建时报 resources are not supported for Windows templates |
从创建请求中移除非空 resources,实例创建后通过 files.write 上传输入。 |
| 提示找不到脚本或输入文件 | 核对沙箱里的完整路径。示例用 $PSScriptRoot 查找脚本旁的 CSV,本地进程的当前目录只影响上传时从哪里读文件。 |
| 终端中的中文或特殊符号显示异常 | 如果程序从 PowerShell 的标准输出读取文本,在脚本开头设置 [Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)。如果读取的是文件,则检查写文件时的编码。 |
什么时候需要派生自己的模板?
每次变化的测试包和样本数据适合在创建实例后上传。若每次都要安装同一版本的 CLI、设置相同的工作目录,可以把这些步骤移到模板构建阶段。具备 Windows 模板构建权限的账号可从对应区域的公开 Windows 11 模板派生模板,构建成功后将 WINDOWS_TEMPLATE 换为新模板的别名,复用上面的运行流程。
定义 Windows 构建时将基础模板的完整别名传给 fromTemplate,通过 WORKDIR 或 setWorkdir 设置 Windows 工作路径。构建中的 RUN 同样使用 CMD 语法,调用 PowerShell 时请显式指定解释器。Windows 构建步骤使用控制台账户执行,USER 会被忽略,RUN 指定其他用户、COPY 指定 Unix 所有者或权限位都会被拒绝。基础模板的使用权限与派生模板的构建权限分别管理。查看模板构建流程