C#(CSharp)에서 PowerShell을 실행하고 결과를 객체로 받는 방법
· 小村 豪 · C#, CSharp, PowerShell, Windows, .NET, 자동화, 기존 자산 활용
C#에서 PowerShell을 실행하고 싶은 상황은 업무 애플리케이션이나 사내 도구에서 자주 발생합니다. 예를 들면 다음과 같은 처리입니다.
- Windows 서비스 목록을 가져온다
- 프로세스나 이벤트 로그를 조사한다
- 기존 PowerShell 스크립트를 C# 애플리케이션에서 호출한다
- 관리자용 작은 GUI 도구에서 PowerShell 명령을 실행한다
- PowerShell 쪽에 있는 기존 자동화 자산을 .NET 애플리케이션에 조금씩 흡수한다
단순히 실행만 하면 된다면, powershell.exe나 pwsh.exe를 외부 프로세스로 실행하고 표준 출력을 문자열로 읽는 방법으로도 동작합니다. 다만 이 방법으로는 PowerShell의 장점인 “객체 파이프라인”이 사라집니다.
PowerShell의 결과는 원래 단순한 텍스트가 아닙니다. Get-Process의 결과는 프로세스 객체이고, Get-Service의 결과는 서비스 객체입니다. C# 쪽에서도 그 구조를 유지한 채로 받을 수 있다면 문자열 파싱이 필요 없어지고, 처리가 훨씬 안전해집니다.
이 글에서는 C#에서 PowerShell을 실행하고 결과를 PSObject로 받는 기본 방법을 정리합니다.
또한 이 글에 나오는 코드는, 빌드하고 실행할 수 있는 샘플 일체(실행 래퍼와 변환 처리 라이브러리, 각 장을 실연하는 콘솔 데모, 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을 사용하면 PowerShell 파이프라인을 C# 코드에서 구성해 실행할 수 있습니다. 중요한 점은 반환값이 문자열이 아니라, 기본적으로 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를 받는다
먼저 현재 C# 애플리케이션 자신의 프로세스를 PowerShell에서 가져와 보겠습니다.
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");
}
}
여기서 짚어 두고 싶은 점은 세 가지입니다. PowerShell.Create()로 PowerShell 실행 객체를 만드는 것, AddCommand("Get-Process")와 AddParameter("Id", currentProcessId)로 명령과 매개변수를 구성하는 것, 그리고 Invoke()의 반환값이 Collection<PSObject>라는 것입니다.
PSObject는 PowerShell이 출력하는 값을 감싸는 래퍼입니다. 안에 있는 원래의 .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. Select-Object한 결과를 C#에서 읽기
실무에서는 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#에 넘기기 전에는 사용하지 않는다
PowerShell의 결과를 C#에서 객체로 받고 싶을 때, 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에서는 출력과 오류가 별도의 스트림입니다. 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# 쪽에서 예외로 다루고 싶다면, 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을 여러 번 호출하는 애플리케이션에서는, 매번 같은 오류 처리를 작성하면 지저분해집니다. 간단한 래퍼를 준비해 두면 편리합니다.
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#의 값
다만, 이 방법은 표시 형식에 의존합니다. 열 너비, 로캘, 줄바꿈, 공백, 오류 메시지, 값 안의 구분 문자에 따라 쉽게 깨집니다.
한편, 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로 변환한다
실무에서 특히 중요한 것은 다음 세 가지입니다.
- C#에서 후속 처리에 사용한다면,
Format-Table이 아니라Select-Object나[pscustomobject]를 사용한다 - 사용자 입력을
AddScript의 문자열에 직접 삽입하지 말고, 가능한 한AddParameter로 전달한다 PSObject는 경계에서 다루고, 애플리케이션 내부에서는 C#의 타입으로 변환한다
PowerShell은 Windows 관리와 기존 자산 활용에 강하고, C#은 애플리케이션화, 화면화, 타입 안전한 업무 처리에 강합니다. 두 가지를 잘 연결하면, 기존 PowerShell 스크립트를 버리지 않고도 조금씩 .NET 애플리케이션으로 정비할 수 있습니다.
참고 정보
- 이 글의 샘플 코드 일체(라이브러리, 데모, 유닛 테스트) 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 앱 외주·수탁 개발을 의뢰하기 전에 정리해야 할 것
Windows 앱의 외주·수탁 개발을 의뢰하기 전에, 기존 소프트웨어 개수, 장치 연계, COM/ActiveX, 배포·업데이트, 보수와 관련해 정리해야 할 포인트를 설명합니다.
Windows 위장 토큰을 올바르게 다루기 - 스레드 단위 권한 대여와 안전한 복원 방법
Windows의 위장 토큰에 대해 액세스 토큰, 기본 토큰, 스레드 토큰, 위장 레벨, RevertToSelf, .NET의 WindowsIdentity.RunImpersonated까지, 실무에서 안전하게 다루기 위한 사고방식을 정리합니다.
Pester로 하는 PowerShell 테스트 정비 ── 운영 스크립트를 쉽게 망가지지 않게 만드는 실무 패턴
PowerShell 스크립트를 Pester v5로 테스트하여 날짜 처리, 파일 작업, 삭제 처리, Mock, CI 실행까지 안전하게 정비하는 실무 절차를 정리합니다.
PowerShell 실용 명령어 모음 ── 일상 업무에서 자주 쓰는 작은 기능 늘리기
PowerShell로 일상 업무에 쓰는 실용 명령어로서, Measure-Object, Group-Object, Select-String, Compare-Object, Tee-Object, Start-Transcript 등의 활용 시점을 정리합니다.
PowerShell 스크립트 응용 ── 로그 조사·아카이브·리포트화를 안전하게 자동화하기
PowerShell 스크립트로 로그 조사, CSV 리포트, 오래된 로그 아카이브, 증적 보존, 작업 스케줄러 실행까지 안전하게 진행하는 실무 절차를 정리합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기존 자산 활용 & 이관 지원
COM / ActiveX / OCX 자산, 네이티브 코드, 32비트 의존성을 유지하면서 단계적인 이관 계획을 지원합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- C#에서 PowerShell을 실행할 때 어떤 방법이 좋은가요?
- 크게 나누면, ProcessStartInfo로 powershell.exe/pwsh.exe를 외부 프로세스로 실행하는 방법과, System.Management.Automation.PowerShell(PowerShell SDK)을 사용하는 방법이 있습니다. 표준 출력을 문자열로 읽기만 한다면 전자로도 동작하지만, C# 쪽에서 결과를 가공하는 관리 도구나 업무 애플리케이션에서는 결과를 PSObject 컬렉션으로 받을 수 있는 후자가 적합합니다. 문자열 파싱이 필요 없어지고, 표시 형식에 의존하지 않는 안전한 처리를 작성할 수 있습니다.
- PSObject의 BaseObject와 Properties는 어떻게 구분해서 사용하나요?
- BaseObject는 PowerShell이 반환한 원래의 .NET 객체를 그대로 사용하고 싶을 때 사용합니다. 예를 들어 Get-Process를 그대로 실행한 결과는 System.Diagnostics.Process로 꺼낼 수 있습니다. 한편, Select-Object나 [pscustomobject]로 열을 정리한 결과는 PowerShell의 커스텀 객체로 반환되는 경우가 많기 때문에, Properties["열 이름"]?.Value처럼 열 이름을 지정해서 꺼내는 쪽이 자연스럽습니다.
- C#에서 PowerShell에 사용자 입력을 전달할 때 무엇에 주의해야 하나요?
- 사용자 입력을 문자열 연결로 AddScript의 스크립트에 삽입하는 것은 피해야 합니다. 입력이 PowerShell 코드로 해석될 여지가 있어 위험하기 때문입니다. 값을 전달할 때는 AddCommand와 AddParameter를 사용하면, 값이 코드 문자열이 아니라 매개변수 값으로 처리됩니다. AddScript는 고정된 짧은 스크립트를 실행하거나 기존 스크립트를 불러오는 용도로 한정하는 것이 안전합니다.
- PowerShell의 결과를 C#에서 받을 때 Format-Table을 쓰면 안 되는 이유는 무엇인가요?
- Format-Table이나 Format-List를 거치면 결과가 원래 객체가 아니라 화면 표시용 서식 정보로 바뀌어 버려서, C# 쪽에서 속성으로 값을 꺼낼 수 없게 되기 때문입니다. C#에서 후속 처리에 사용할 경우에는 Select-Object로 열을 좁히거나, PowerShell 쪽에서 [pscustomobject]를 반환하는 형태로 정리합니다. "화면에서 보기만 한다면 Format 계열, C#에 넘긴다면 Select-Object"라는 식으로 구분해서 씁니다.
저자 프로필
기사 저자의 프로필 페이지입니다.
Go Komura
합동회사 코무라소프트 대표
Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.
공개 링크