在 C#(CSharp)中执行 PowerShell 并以对象形式接收结果
· 小村 豪 · C#, CSharp, PowerShell, Windows, .NET, 自动化, 现有资产复用
在业务应用程序或内部工具中,经常会遇到想从 C# 执行 PowerShell 的场景,例如以下这些处理:
- 获取 Windows 的服务列表
- 查询进程或事件日志
- 从 C# 应用程序调用现有的 PowerShell 脚本
- 从供管理员使用的小型 GUI 工具执行 PowerShell 命令
- 把 PowerShell 端现有的自动化资产,逐步纳入 .NET 应用程序
如果只是单纯执行,把 powershell.exe 或 pwsh.exe 作为外部进程启动、将标准输出当作字符串读取的方式也能用。但这种做法会失去 PowerShell 的优点——「对象管道(object pipeline)」。
PowerShell 的结果本来就不只是纯文本。Get-Process 的结果是进程对象,Get-Service 的结果是服务对象。如果在 C# 端也能保持这种结构来接收,就不需要解析字符串,处理也会变得相当安全。
本文将整理从 C# 执行 PowerShell,并以 PSObject 形式接收结果的基本做法。
另外,本文出现的代码已整理成一套可构建、可运行的示例(执行包装器与转换处理的库、演示文章各章节的控制台 demo,以及验证 PSObject 接收与错误处理的单元测试),公开在 GitHub 上。
csharp-run-powershell-receive-objects - komurasoft-blog-samples (GitHub)
1. 不启动外部进程,改用 PowerShell SDK
从 C# 调用 PowerShell 的方法,大致可以分成两种。
| 方法 | 特点 | 适合的场景 |
|---|---|---|
用 ProcessStartInfo 启动 powershell.exe / pwsh.exe |
把标准输出、标准错误当作字符串读取 | 简单执行现有批处理、只需要留下日志的场景 |
使用 System.Management.Automation.PowerShell |
可以以 PSObject 的形式接收结果 |
需要在 C# 端加工结果的处理、管理工具、业务应用 |
本文要讨论的是后一种方式。使用 System.Management.Automation.PowerShell,就能从 C# 代码组装并执行 PowerShell 的管道。重点在于,返回值不是字符串,基本上会是 Collection<PSObject>。
换句话说,思路会变成这样:
执行 PowerShell 命令
↓
以 PSObject 集合的形式接收结果
↓
从 BaseObject 或 Properties 中取出值
↓
必要时转换为 C# 的 DTO / record / class
重点不是把 PowerShell 的输出拆解成字符串,而是从一开始就以对象的形式来处理。
2. 前提环境
本文以 .NET 8 的控制台应用程序为例。PowerShell SDK 会因版本不同而对应不同的 .NET,因此要配合项目的目标框架来选择。
以 2026 年 6 月的时间点来看,可以这样理解:
| C# 应用的目标框架 | 示例使用的 PowerShell SDK | 备注 |
|---|---|---|
| .NET 8 | Microsoft.PowerShell.SDK 7.4 系列 |
适合 .NET 8 应用使用 |
| .NET 10 | Microsoft.PowerShell.SDK 7.6 系列 |
想使用更新版 PowerShell SDK 时的候选 |
| .NET Framework | Microsoft.PowerShell.5.1.ReferenceAssemblies |
适用于 Windows PowerShell 5.1。新项目开发时请先确认需求 |
这里以 .NET 8 为例,使用 Microsoft.PowerShell.SDK 7.4.16。
dotnet new console -n PowerShellObjectSample
cd PowerShellObjectSample
dotnet add package Microsoft.PowerShell.SDK --version 7.4.16
.csproj 大致会像下面这样。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.4.16" />
</ItemGroup>
</Project>
建议把版本固定下来。PowerShell SDK 虽然方便,但会受到应用运行环境、目标 .NET、PowerShell 模块兼容性的影响。在业务应用中,与其直接沿用「开发环境中能跑的最新版本」,明确标注已验证过的版本会更安全。
3. 最小代码:执行 PowerShell 并接收 PSObject
首先尝试通过 PowerShell 获取当前这个 C# 应用自身的进程信息。
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Management.Automation;
int currentProcessId = Environment.ProcessId;
using PowerShell ps = PowerShell.Create();
Collection<PSObject> results = ps
.AddCommand("Get-Process")
.AddParameter("Id", currentProcessId)
.Invoke();
foreach (PSObject item in results)
{
Console.WriteLine($"PSObject type: {item.GetType().FullName}");
Console.WriteLine($"BaseObject type: {item.BaseObject.GetType().FullName}");
if (item.BaseObject is Process process)
{
Console.WriteLine($"Id: {process.Id}");
Console.WriteLine($"Name: {process.ProcessName}");
Console.WriteLine($"Memory: {process.WorkingSet64:N0} bytes");
}
}
这里有 3 个要点:用 PowerShell.Create() 创建 PowerShell 的执行对象;用 AddCommand("Get-Process") 与 AddParameter("Id", currentProcessId) 组装命令与参数;以及 Invoke() 的返回值是 Collection<PSObject>。
PSObject 是包装 PowerShell 输出值的包装器(wrapper)。如果想查看内部原始的 .NET 对象,就看 BaseObject。在这个示例中,Get-Process 结果的内容可以以 System.Diagnostics.Process 的形式取出。
4. BaseObject 与 Properties 的使用区分
在 C# 中处理 PowerShell 的结果时,最先让人犹豫的就是下面这两种写法。
item.BaseObject
item.Properties["Name"]?.Value
该用哪一种,大致可以参考下表。
| 取值方式 | 使用场景 |
|---|---|
BaseObject |
想直接使用 PowerShell 返回的原始 .NET 对象时 |
Properties["..."] |
想取出经由 Select-Object 或 [pscustomobject] 生成的列时 |
如果直接执行像 Get-Process 这样的命令,BaseObject 里通常会放着原始的 .NET 对象。另一方面,如果在 PowerShell 端用 Select-Object 整理过列,结果大多会以 PowerShell 的自定义对象形式返回。这种情况下,改用 Properties 按列名取值会更自然。
5. 在 C# 中读取 Select-Object 后的结果
在实务中,很少会需要 PowerShell 返回的全部属性。如果只想把需要的列传给 C# 端,可以在 PowerShell 管道中使用 Select-Object。
using System.Collections.ObjectModel;
using System.Globalization;
using System.Management.Automation;
using PowerShell ps = PowerShell.Create();
Collection<PSObject> rows = ps
.AddCommand("Get-Process")
.AddCommand("Sort-Object")
.AddParameter("Property", "CPU")
.AddParameter("Descending", true)
.AddCommand("Select-Object")
.AddParameter("First", 10)
.AddParameter("Property", new[] { "Name", "Id", "CPU", "WorkingSet" })
.Invoke();
foreach (PSObject row in rows)
{
string name = Convert.ToString(row.Properties["Name"]?.Value, CultureInfo.InvariantCulture) ?? "";
int id = Convert.ToInt32(row.Properties["Id"]?.Value, CultureInfo.InvariantCulture);
double? cpu = row.Properties["CPU"]?.Value is null
? null
: Convert.ToDouble(row.Properties["CPU"]!.Value, CultureInfo.InvariantCulture);
long workingSet = Convert.ToInt64(row.Properties["WorkingSet"]?.Value, CultureInfo.InvariantCulture);
Console.WriteLine($"{id}: {name}, CPU={cpu}, WorkingSet={workingSet:N0}");
}
这段代码相当于下面这样的 PowerShell 管道:
Get-Process |
Sort-Object -Property CPU -Descending |
Select-Object -First 10 -Property Name, Id, CPU, WorkingSet
从 C# 的角度来看,通过连续调用 AddCommand,就组成了 PowerShell 的管道。
.AddCommand("Get-Process")
.AddCommand("Sort-Object")
.AddCommand("Select-Object")
这样写的话,前一个命令的输出会传给下一个命令。
用 Select-Object 筛选列之后,就可以像 row.Properties["Name"]?.Value 这样按列名取值。
6. 转换为 C# 的 record
如果让 PSObject 原样在整个应用中传递,后续代码就会过度依赖 PowerShell。如果要用于界面显示或业务处理,转换成 C# 端的类型会更便于处理。
举例来说,可以把进程信息转换成下面这个 record。
public sealed record ProcessSummary(
string Name,
int Id,
double? Cpu,
long WorkingSet);
把转换处理拆分开来写,可读性会更好。
using System.Globalization;
using System.Management.Automation;
static ProcessSummary ToProcessSummary(PSObject row)
{
string name = GetString(row, "Name");
int id = GetInt32(row, "Id");
double? cpu = GetNullableDouble(row, "CPU");
long workingSet = GetInt64(row, "WorkingSet");
return new ProcessSummary(name, id, cpu, workingSet);
}
static string GetString(PSObject row, string propertyName)
{
return Convert.ToString(row.Properties[propertyName]?.Value, CultureInfo.InvariantCulture) ?? "";
}
static int GetInt32(PSObject row, string propertyName)
{
return Convert.ToInt32(row.Properties[propertyName]?.Value, CultureInfo.InvariantCulture);
}
static long GetInt64(PSObject row, string propertyName)
{
return Convert.ToInt64(row.Properties[propertyName]?.Value, CultureInfo.InvariantCulture);
}
static double? GetNullableDouble(PSObject row, string propertyName)
{
object? value = row.Properties[propertyName]?.Value;
return value is null ? null : Convert.ToDouble(value, CultureInfo.InvariantCulture);
}
使用端则会是这样:
List<ProcessSummary> processes = rows
.Select(ToProcessSummary)
.ToList();
foreach (ProcessSummary process in processes)
{
Console.WriteLine($"{process.Id}: {process.Name}");
}
PSObject 留在与 PowerShell 交界的地方处理,应用内部则转换成像 ProcessSummary 这样普通的 C# 类型。
做好这层分离之后,即使之后修改 PowerShell 命令,影响范围也能控制得更小。
7. 返回 PSCustomObject 会让 C# 端更容易处理
如果想在 PowerShell 端把多个值整合起来返回,使用 [pscustomobject] 会很方便。
using System.Collections.ObjectModel;
using System.Management.Automation;
string script = @"
[pscustomobject]@{
MachineName = [System.Environment]::MachineName
PowerShellVersion = $PSVersionTable.PSVersion.ToString()
CurrentDirectory = (Get-Location).Path
}
";
using PowerShell ps = PowerShell.Create();
Collection<PSObject> rows = ps
.AddScript(script, useLocalScope: true)
.Invoke();
foreach (PSObject row in rows)
{
Console.WriteLine($"MachineName: {row.Properties["MachineName"]?.Value}");
Console.WriteLine($"PowerShell: {row.Properties["PowerShellVersion"]?.Value}");
Console.WriteLine($"Directory: {row.Properties["CurrentDirectory"]?.Value}");
}
只要在 PowerShell 脚本的最后返回 [pscustomobject],C# 端就能从 Properties 按名称取值。这比返回复杂字符串再由 C# 端拆分安全得多。
应该避免的是像下面这样的输出:
"$MachineName,$PowerShellVersion,$CurrentDirectory"
这种方式看起来简单,但只要值中包含逗号或换行,就会出问题。
PowerShell 端返回对象,C# 端则以属性的方式读取——保持这种形式,之后即使列增加了也比较容易应对。
8. 不要把用户输入直接嵌入 AddScript
即使使用 PowerShell SDK,以字符串方式拼接脚本仍然很危险。举例来说,下面这样的代码应该避免:
// 应避免的写法
string userInputPath = GetPathFromUser();
string script = $"Get-ChildItem -Path '{userInputPath}'";
using PowerShell ps = PowerShell.Create();
ps.AddScript(script).Invoke();
这种写法会让用户输入有可能被当作 PowerShell 代码解析。向 PowerShell 命令传值时,应尽量使用 AddCommand 与 AddParameter。
string userInputPath = GetPathFromUser();
using PowerShell ps = PowerShell.Create();
Collection<PSObject> files = ps
.AddCommand("Get-ChildItem")
.AddParameter("Path", userInputPath)
.AddParameter("File", true)
.Invoke();
用 AddParameter 传递的值,不会被当作 PowerShell 的代码字符串拼接,而是会被当作参数值处理。
在实务中,建议按下表区分使用方式:
| 写法 | 适用场景 |
|---|---|
AddCommand / AddParameter |
想从 C# 端安全地组装命令时 |
AddScript |
执行固定的短脚本,或加载现有脚本时 |
通过字符串拼接组成的 AddScript |
原则上应避免。若要使用,务必谨慎验证并转义输入值 |
把 PowerShell 集成到 C# 之后,应用就能具备强大的操作能力。方便的同时,唯一必须坚守的一条底线是:不要把用户输入直接变成脚本。
9. Format-Table 只用于最后的界面显示,传给 C# 之前不要用
如果想在 C# 中以对象形式接收 PowerShell 的结果,基本上不会使用 Format-Table 或 Format-List。
举例来说,下面这种 PowerShell 写法对于让人在屏幕上查看很方便:
Get-Service | Format-Table Name, Status
但是,如果在 C# 端接收之前使用了 Format-Table,结果就不再是服务对象,而会变成用于显示的格式化信息。如果要在 C# 中处理,应改用 Select-Object。
Get-Service | Select-Object Name, Status
用 C# 写的话,会像下面这样:
using PowerShell ps = PowerShell.Create();
Collection<PSObject> services = ps
.AddCommand("Get-Service")
.AddCommand("Select-Object")
.AddParameter("Property", new[] { "Name", "Status" })
.Invoke();
思路很简单:
只是为了让界面好看 → Format-Table / Format-List
要在 C# 中做后续处理 → Select-Object / PSCustomObject
这个道理即使是单独使用 PowerShell 时也适用,但在与 C# 集成时尤其重要。
10. 接收错误
在 PowerShell 中,输出与错误是不同的数据流(stream)。如果只看 Invoke() 的返回值,有可能会漏掉错误。基本写法如下:
using System.Management.Automation;
using PowerShell ps = PowerShell.Create();
Collection<PSObject> output = ps
.AddCommand("Get-Item")
.AddParameter("Path", @"C:\no-such-file.txt")
.Invoke();
if (ps.HadErrors)
{
foreach (ErrorRecord error in ps.Streams.Error)
{
Console.WriteLine($"Error: {error.Exception.Message}");
Console.WriteLine($"Category: {error.CategoryInfo.Category}");
Console.WriteLine($"Target: {error.TargetObject}");
}
}
PowerShell 的 cmdlet 分为会终止处理的错误,以及可以继续处理的错误。如果想在 C# 端当作异常(exception)处理,可以把 ErrorAction 指定为 Stop。
using System.Management.Automation;
try
{
using PowerShell ps = PowerShell.Create();
Collection<PSObject> output = ps
.AddCommand("Get-Item")
.AddParameter("Path", @"C:\no-such-file.txt")
.AddParameter("ErrorAction", "Stop")
.Invoke();
}
catch (RuntimeException ex)
{
Console.WriteLine($"PowerShell failed: {ex.Message}");
}
该用哪一种取决于应用的性质。在管理工具中,如果希望「即使部分失败,也要输出列表」,那么收集错误流并显示在界面上会更合适;如果希望「一旦失败就中止整个处理」,用 ErrorAction Stop 当作异常处理会更清晰。
11. 创建一个小型的执行包装器
在需要多次调用 PowerShell 的应用中,如果每次都写相同的错误处理,代码会变得杂乱。提前准备一个简单的包装器(wrapper)会很方便。
using System.Management.Automation;
public sealed record PowerShellRunResult(
IReadOnlyList<PSObject> Output,
IReadOnlyList<ErrorRecord> Errors);
public static class PowerShellRunner
{
public static PowerShellRunResult Run(Action<PowerShell> build)
{
using PowerShell ps = PowerShell.Create();
build(ps);
List<PSObject> output;
try
{
output = ps.Invoke().ToList();
}
catch (RuntimeException ex)
{
throw new InvalidOperationException($"PowerShell execution failed: {ex.Message}", ex);
}
return new PowerShellRunResult(
Output: output,
Errors: ps.Streams.Error.ToList());
}
}
使用端就能专注于组装命令。
PowerShellRunResult result = PowerShellRunner.Run(ps => ps
.AddCommand("Get-Service")
.AddCommand("Where-Object")
.AddParameter("Property", "Status")
.AddParameter("EQ", "Running")
.AddCommand("Select-Object")
.AddParameter("First", 10)
.AddParameter("Property", new[] { "Name", "DisplayName", "Status" }));
foreach (PSObject row in result.Output)
{
Console.WriteLine($"{row.Properties["Name"]?.Value}: {row.Properties["Status"]?.Value}");
}
foreach (ErrorRecord error in result.Errors)
{
Console.Error.WriteLine(error.Exception.Message);
}
不过,就像这个示例中的 Where-Object 一样,把 PowerShell 特有的条件指定用 C# 组装出来,有时会让代码变得比较难读。简单的命令与参数用 AddCommand / AddParameter 就够了,但复杂的筛选或汇总,有时准备成固定的 PowerShell 脚本会更易读。即便如此,「不要把外部输入直接拼接进脚本字符串」这一原则不变。
12. 复杂的处理交由 PowerShell 端整理成对象
结合 C# 与 PowerShell 时,先分清楚哪一边负责什么,设计上会更容易。
建议的分工如下:
| 负责 | 内容 |
|---|---|
| PowerShell | 贴近 Windows 或模块的操作、现有脚本、执行管理命令 |
| C# | UI、输入验证、类型转换、业务逻辑、保存、API 集成 |
在 PowerShell 端,把最终输出整理成 [pscustomobject]。
Get-Service |
Where-Object Status -eq 'Running' |
Select-Object Name, DisplayName, Status
或者,明确地创建 [pscustomobject]。
$services = Get-Service | Where-Object Status -eq 'Running'
[pscustomobject]@{
Count = $services.Count
Names = $services.Name
}
在 C# 端,读取返回的 PSObject 属性,转换为自己应用的类型。
保持这种形式,就能避免把 PowerShell 的实现细节过多泄漏到 C# 端。
13. 实务中常见的注意事项
从 C# 执行 PowerShell 时,仅代码能运行是不够的。在实务中,提前确认以下几点会更安全。
执行用户的权限
PowerShell 会以运行 C# 应用的用户权限来运作。需要管理员权限的命令,即使以普通用户身份执行也会失败。服务操作、事件日志、证书、注册表、Hyper-V、Microsoft 365 管理相关模块等,都需要提前厘清权限划分。
32 位 / 64 位的差异
在 Windows 中,32 位进程与 64 位进程所能看到的注册表或模块有时会不同。如果要做成 Windows 管理用工具,基本上以 x64 为前提运行,可以减少麻烦。
运行环境中是否存在模块
即使在 C# 应用中加入 PowerShell SDK,也不意味着所有 PowerShell 模块都会自动装好。例如要使用特定产品的管理模块或内部模块时,需要确认运行环境中是否存在该模块,以及会从哪个路径加载。
界面应用中不要阻塞 UI 线程
从 WinForms 或 WPF 执行 PowerShell 时,如果直接在 UI 线程上执行繁重处理,界面会卡死。这种情况下,应设计为以后台处理方式执行,完成后再更新 UI。PowerShell SDK 也提供了异步执行的 API,但首先要守住「不在 UI 线程上执行长时间的 Invoke()」这一原则。
应用分发时的体积
Microsoft.PowerShell.SDK 虽然方便,但也会增加应用所包含的依赖项。对于小型工具程序来说可能还能接受,但根据分发形式或更新方式的不同,体积有时会成为问题。建议尽早用 ClickOnce、MSIX、单文件 exe 分发、内部分发工具等实际的分发方式进行验证,会更安心。
14. 以对象而非字符串接收的优点
最后,谈谈为什么要如此坚持使用 PSObject。以外部进程方式执行 PowerShell、读取标准输出的方法确实简单。
PowerShell 的输出
↓
字符串
↓
Split / 正则表达式 / Substring
↓
C# 的值
不过,这种方法依赖显示格式。列宽、区域设置(locale)、换行、空格、错误消息,以及值中包含的分隔符,都容易让它出问题。
另一方面,使用 PowerShell SDK 则会是下面这个流程:
PowerShell 的输出
↓
PSObject
↓
Properties / BaseObject
↓
C# 的类型
这种方式是依据数据结构而非显示格式来取值。对业务应用或管理工具来说,后一种方式更容易维护。
15. 总结
如果要从 C# 执行 PowerShell 并处理结果,除了单纯启动 powershell.exe 读取标准输出之外,值得考虑使用 PowerShell SDK 的做法。
基本流程如下:
添加 Microsoft.PowerShell.SDK
↓
用 PowerShell.Create() 创建执行对象
↓
用 AddCommand / AddParameter / AddScript 组装处理
↓
用 Invoke() 执行
↓
以 Collection<PSObject> 形式接收
↓
从 BaseObject 或 Properties 取出值
↓
转换为 C# 的 DTO / record / class
在实务中特别重要的是下面这 3 点:
- 如果要在 C# 中做后续处理,应使用
Select-Object或[pscustomobject],而不是Format-Table - 不要把用户输入直接嵌入
AddScript的字符串,尽量通过AddParameter传递 PSObject只在边界处处理,应用内部则转换为 C# 的类型
PowerShell 擅长 Windows 管理与复用现有资产,C# 擅长应用化、界面化,以及类型安全的业务处理。把两者妥善连接起来,就能不舍弃现有的 PowerShell 脚本,逐步整理成 .NET 应用。
参考资料
- 本文的示例代码全套(库、demo、单元测试) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/csharp-run-powershell-receive-objects
- Microsoft Learn:Windows PowerShell 主机快速入门
https://learn.microsoft.com/ja-jp/powershell/scripting/developer/hosting/windows-powershell-host-quickstart - Microsoft Learn:添加并调用命令
https://learn.microsoft.com/ja-jp/powershell/scripting/developer/hosting/adding-and-invoking-commands - Microsoft Learn:PowerShell Class
https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.powershell - Microsoft Learn:PSObject Class
https://learn.microsoft.com/ja-jp/dotnet/api/system.management.automation.psobject - NuGet Gallery:Microsoft.PowerShell.SDK
https://www.nuget.org/packages/Microsoft.PowerShell.SDK/ - NuGet Gallery:Microsoft.PowerShell.5.1.ReferenceAssemblies
https://www.nuget.org/packages/Microsoft.PowerShell.5.1.ReferenceAssemblies/
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
如何理解 Windows 的会话隔离 ── Session 0・RDP・多用户同时运行
整理 Windows 应用程序开发者容易混淆的「会话」概念。说明服务无法显示 UI 的 Session 0 隔离原因、RDP 连接时会话的行为、命名对象的会话隔离,以及共享 PC・RDS 环境中常见的设计失误,从实务角度解说。
防止 Windows 应用程序重复启动 ── 命名 Mutex 与重复执行时的窗口前置
整理业务型 Windows 应用程序的常见需求——「不让同一个应用程序启动两次」——如何用命名 Mutex 来实现。涵盖 Global\ 与 Local\ 命名空间差异在 RDP 环境中的陷阱、拥有线程限制与 AbandonedMutexException、在 SetFor...
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
业务应用的日期时间与时区 ── 从 DateTime 的陷阱到 UTC 存储原则、测试设计
服务器迁移后时间整整差了 9 个小时、只有海外分支机构的日期会变成前一天——本文从 DateTime 的 Kind 与隐式转换梳理日期时间事故的根本原因。内容涵盖与 DateTimeOffset 的取舍、UTC 存储与 ISO 8601 原则、TimeZoneInfo 与夏...
Windows 桌面应用的 UI 自动化测试 ── UI Automation 原理与用 FlaUI 打造不易损坏的测试
本文从 Windows UI Automation 的原理(树结构、AutomationId、控件模式)出发,梳理 WinForms/WPF 应用的 UI 自动化测试。内容涵盖用 FlaUI 实现的最小示例、WinAppDriver 的现状、通过 AutomationId ...
常见问题
汇总了咨询这一主题时常见的问题。
- 从 C# 执行 PowerShell,应该用哪种方法?
- 大致可以分成两种方式:用 ProcessStartInfo 将 powershell.exe / pwsh.exe 作为外部进程启动,以及使用 System.Management.Automation.PowerShell(PowerShell SDK)。如果只是想把标准输出当作字符串读取,前一种方式也能用;但在 C# 端需要对结果进行加工的管理工具或业务应用中,能以 PSObject 集合形式接收结果的后一种方式更为合适。这样就不需要解析字符串,也能写出不依赖显示格式的安全处理逻辑。
- PSObject 的 BaseObject 与 Properties 该如何区分使用?
- 当你想直接使用 PowerShell 返回的原始 .NET 对象时,就使用 BaseObject。例如直接执行 Get-Process 的结果,可以以 System.Diagnostics.Process 的形式取出。另一方面,经过 Select-Object 或 [pscustomobject] 整理过列的结果,通常会以 PowerShell 的自定义对象形式返回,这种情况下用 Properties["列名"]?.Value 按列名取值会更自然。
- 从 C# 向 PowerShell 传递用户输入时应该注意什么?
- 应避免用字符串拼接的方式,把用户输入直接嵌入 AddScript 的脚本中,因为输入内容有可能被当作 PowerShell 代码解析,相当危险。如果要传值,使用 AddCommand 搭配 AddParameter,值就会被当作参数值处理,而不是代码字符串。建议将 AddScript 限定用于固定的短脚本,或加载现有脚本这类场景。
- 在 C# 接收 PowerShell 结果时,为什么不该使用 Format-Table?
- 因为经过 Format-Table 或 Format-List 之后,结果会变成用于显示的格式化信息,而不是原本的对象,导致 C# 端无法以属性方式取出值。如果要在 C# 中做后续处理,应改用 Select-Object 筛选列,或在 PowerShell 端整理成返回 [pscustomobject] 的形式。可以这样区分:「只是在屏幕上查看就用 Format 系列,要传给 C# 就用 Select-Object」。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。