PowerShell에서 COM과 .NET을 호출하는 실무 ── 스크립트가 닿는 범위를 한 번에 넓히기

· 업데이트: · · PowerShell, Windows, COM, .NET, 자동화, 스크립트, 기존 자산 활용, 업무 효율화

수정 이력(5건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 서두에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응하여 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
STA(단일 스레드 아파트먼트) 설명과, Office가 병렬 실행에 맞지 않는 이유를 추가하고, 기존 STA/MTA 기사로 링크했습니다. 「2-dot 규칙」의 한 줄 설명, 무인 작업용 이벤트 로그 출력 대체 코드 예, 삼중 `try`/`finally`의 설계 의도 설명을 추가했습니다. `ZipArchive`의 2GB 상한에 대해서는 클래스 레퍼런스 본문에 수치 기재가 없다는 점도 각주에 명시했습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22174808)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「PowerShell에서 COM과 .NET을 호출하는 실무 ── 스크립트가 닿는 범위를 한 번에 넓히기」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/powershell-com-dotnet-interop/

DOI(등록된 아카이브)
10.5281/zenodo.22174808
DOI(마지막 등록 버전)
10.5281/zenodo.22174809

「cmdlet을 찾아봤지만, 원하는 처리가 없다」── PowerShell을 어느 정도 쓰다 보면 반드시 이 벽에 부딪힙니다. 바로 가기 일괄 작성, ZIP 내용을 풀지 않고 확인하는 처리, 창 조작, Excel 파일 읽기·쓰기. 표준 cmdlet만으로는 손이 닿지 않는 요청은 중소기업 정보시스템·운영 담당자에게서 일상적으로 들어옵니다.

사실 이 벽 너머는 PowerShell이 잘하는 영역입니다. PowerShell은 .NET 위에 구축되어 있으므로, .NET 클래스 라이브러리의 형식을 추가 설치 없이 직접 호출할 수 있습니다. 나아가 Add-Type으로 C# 코드나 Win32 API(P/Invoke)를 그 자리에서 넣고, New-Object -ComObject로 WScript.Shell이나 Excel 같은 COM 객체도 조작할 수 있습니다. VBScript나 VBA로 쓰여 온 「Windows 자동화」 도구 상자를 거의 그대로 쓸 수 있다는 뜻입니다.

다만 이 힘에는 뒷정리 절차가 따라옵니다. 특히 Excel COM 조작은 「스크립트가 끝나도 EXCEL.EXE가 남는다」는 전형적인 문제의 온상이며, 애초에 무인 실행에서 Office를 자동화하는 것 자체를 Microsoft가 권장하지 않습니다. 이 기사에서는 PowerShell에서 .NET 호출·Add-Type·COM 조작의 실무 패턴과 함정, 그리고 「어디까지 스크립트로 버티고, 어디서부터 C# 도구로 옮길지」의 판단까지 정리합니다.

1. 먼저 결론

  • PowerShell은 .NET 위에 올라갑니다. Windows PowerShell 5.1은 .NET Framework, PowerShell 7은 .NET(구 .NET Core) 위에 구축되어 있어, [System.IO.Path]::GetFileNameWithoutExtension()처럼 .NET 정적 메서드를 직접 호출할 수 있습니다.1
  • 인스턴스 생성은 New-Object이거나, PowerShell 5.0 이후라면 [형식]::new()입니다. [형식]::new를(괄호 없이) 치면 생성자 목록을 확인할 수 있어, 인수를 살펴보며 작성할 수 있습니다.2
  • Add-Type을 쓰면 C# 소스 코드를 그 자리에서 컴파일해 세션에 넣을 수 있습니다. DllImport 시그니처를 넘기면 Win32 API의 P/Invoke 호출도 가능합니다. 추가한 형식은 세션 안에서 지울 수 없고, 같은 이름 형식은 다시 정의할 수 없습니다.3
  • COM 객체는 New-Object -ComObject <ProgId>로 만듭니다. VBScript의 CreateObject("Shell.Application")New-Object -ComObject Shell.Application에 그대로 대응하며, WScript.Shell로 바로 가기를 만드는 등 WSH 시절 도구를 PowerShell에서 쓸 수 있습니다.45
  • COM 객체의 수명은 참조 횟수로 관리되고, .NET 쪽에서는 RCW(Runtime Callable Wrapper)가 그것을 붙잡습니다. 참조가 남아 있으면 Excel 같은 프로세스는 종료하지 않습니다. 명시적으로 해제하려면 Marshal.ReleaseComObject를 쓰지만, 남용하면 다른 사고를 부르므로 「정말 필요한 경우에만」이 공식 지침입니다.67
  • 무인 실행에서 Office 자동화는 Microsoft가 「권장하지 않으며, 지원하지 않는다」고 분명히 말하고 있습니다. 서비스·작업 스케줄러·서버 쪽에서 Excel COM을 조작하는 구성은, 돌아가는 것처럼 보여도 비지원 구성입니다. 무인 처리는 Open XML 계열 라이브러리나 CSV 연동으로 바꿉니다.8
  • 5.1과 7에서는 쓸 수 있는 형식·메서드가 다릅니다. String.Split 오버로드 차이처럼 같은 코드로 동작이 바뀌는 예가 있고, 5.1에서는 GAC 어셈블리를 Add-Type으로 명시 로드해야 하는 경우도 있습니다.13
  • .NET 직접 호출이 늘어났다면 C# 도구화를 검토할 시점입니다. 형식을 다시 정의할 수 없는 Add-Type 제약이나 배포 문제가 눈에 띄기 시작하면, 스크립트의 한계가 가깝다는 신호입니다.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. PowerShell은 .NET 위에 올라간다 ── [형식]::메서드와 New-Object

PowerShell cmdlet은 .NET 클래스 라이브러리의 일부를 「운영 작업에 쓰기 쉽게 감싼 것」입니다. 감싸이지 않은 기능도 형식 이름을 대괄호로 쓰면 직접 호출할 수 있습니다. 정적 메서드는 ::, 인스턴스 메서드나 속성은 .입니다.

# .NET 정적 메서드를 직접 호출한다. cmdlet 불필요, 추가 설치도 불필요
[System.IO.Path]::GetFileNameWithoutExtension('C:\data\受注_20260718.csv')  # -> 受注_20260718
[System.IO.Path]::Combine('C:\data', 'archive', '2026-07')                   # -> C:\data\archive\2026-07
[System.Math]::Round(123.456, 1)                                             # -> 123.5

# 인스턴스가 필요하면 New-Object 또는 [형식]::new()로 만든다
$list = [System.Collections.Generic.List[string]]::new()
$list.Add('server01')

# ::new를 괄호 없이 치면 생성자 목록이 반환된다(인수 확인 방법으로 유용)
[System.IO.StreamWriter]::new

[형식]::new()는 PowerShell 5.0에서 추가된 작성법으로, New-Object보다 빠르고, 위 예처럼 생성자 오버로드 목록을 그 자리에서 확인할 수 있습니다.2 한편 주의점도 있습니다. Get-Item 같은 cmdlet이 반환하는 객체에는 PowerShell이 추가 속성(NoteProperty)을 붙이는 경우가 있어, ::new()로 직접 만든 같은 형식 객체와 멤버 구성이 일치하지 않을 수 있습니다.2 「cmdlet으로 가져왔을 때는 있던 속성이 없다」고 헷갈리면 이 구조를 떠올리세요.

실무 레시피를 하나. ZIP 처리에는 Compress-Archive / Expand-Archive가 있지만, 이들의 기반인 ZipArchive API에는 파일 크기 2GB 제한이 있습니다. 이 제한은 압축 쪽·압축 해제 쪽 공식 문서 모두에 적혀 있으며, 어느 쪽이든 「기반 System.IO.Compression.ZipArchive API에 의한 상한」이라고 명시한 뒤 그 클래스 레퍼런스를 참조로 들고 있습니다.910 또한 「풀지 않고 내용 목록만 확인하고 싶다」 같은 세세한 조작은 cmdlet에 없습니다. 그래서 .NET ZipFile 클래스가 등장합니다.11

# Windows PowerShell 5.1에서는 GAC 어셈블리를 명시 로드해야 한다
# (PowerShell 7에서는 기본 제공 어셈블리가 필요할 때 자동 로드되므로, 이 줄이 없어도 동작한다)
Add-Type -AssemblyName System.IO.Compression.FileSystem

# 먼저 읽기부터 ── 풀지 않고 ZIP 내용을 확인한다
$archive = [System.IO.Compression.ZipFile]::OpenRead('C:\deploy\release.zip')
try {
    $archive.Entries | Select-Object FullName, Length, LastWriteTime
}
finally {
    $archive.Dispose()  # 파일 핸들을 확실히 반환한다
}

# 내용에 문제가 없으면 압축을 푼다. 대상에 같은 이름 파일이 있으면 이 2인수 버전은
# 중간까지 푼 상태에서 예외를 낸다(덮어쓰지 않음). 기존 폴더에 바로 푸는 것은 피하고,
# 매번 새 폴더에 푼 뒤 전환하는 편이 안전하다
$dest = "C:\apps\web_$(Get-Date -Format yyyyMMddHHmmss)"
[System.IO.Compression.ZipFile]::ExtractToDirectory('C:\deploy\release.zip', $dest)

.NET Framework에서는 ZipFile 클래스를 쓰려면 System.IO.Compression.FileSystem 어셈블리 참조가 필요하고, 이는 PowerShell 5.1에서도 같습니다.11 7에서는 기본 제공 어셈블리가 요청 시 자동 로드되므로 Add-Type 없이 동작하지만, 적어 두면 양쪽에서 돌아가므로 공용 스크립트에서는 명시하는 편이 무난합니다.3

3. Add-Type으로 자체 C#과 Win32 API(P/Invoke)를 넣기

.NET에 있는 기능 다음은 .NET에 없는 기능입니다. Add-Type에 C# 소스 코드를 넘기면 그 자리에서 컴파일되어 세션에 형식이 추가됩니다. 나아가 -MemberDefinition에 DllImport가 붙은 시그니처를 넘기면 Win32 API를 P/Invoke로 호출할 수 있습니다. 이 사용법은 공식 문서에도 예로 실려 있는, 정당한 수단입니다.3

# 로컬에서 돌린 장시간 처리 완료를 대화 상자로 알린다(user32.dll의 MessageBoxW를 P/Invoke로 호출)
$signature = @'
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type);
'@

$native = Add-Type -MemberDefinition $signature -Name 'NativeMethods' `
    -Namespace 'Win32' -PassThru

# MB_ICONINFORMATION(0x40)을 붙여 표시
[void]$native::MessageBoxW([IntPtr]::Zero, '백업 처리가 완료되었습니다.', '장시간 처리', 0x40)

주의할 점이 있습니다. 모달 대화 상자는 자신이 데스크톱 앞에 있는 대화형 실행 전용입니다. 작업 스케줄러의 무인 실행이나 Windows 서비스 같은 비대화형 세션에서는, 아무도 닫지 못하는 대화 상자로 처리가 멈춘 채 돌아오지 않습니다. 무인 작업 알림은 로그 파일·이벤트 로그·메일 발송처럼 응답을 기다리지 않는 수단으로 하세요.

같은 「처리 완료를 알리기」를 무인 작업용으로 바꾸면 아래와 같습니다. 대화 상자 대신 이벤트 로그에 한 건 쓰고, 함께 로그 파일에도 남기는 형태입니다.

# 무인 작업 버전 알림: 응답을 기다리지 않고, 나중에 추적할 수 있는 곳에 남긴다
$source  = 'KomuraSoft.NightlyJob'   # 이벤트 소스 이름(임의의 문자열)
$logPath = 'C:\logs\nightly.log'

# 소스 등록에는 관리자 권한이 필요. 도입 시 한 번만 실행해 두고,
# 야간 작업 본체에서는 등록된 전제로 쓰기만 한다
#   New-EventLog -LogName Application -Source 'KomuraSoft.NightlyJob'

Write-EventLog -LogName Application -Source $source -EntryType Information `
    -EventId 1000 -Message '백업 처리가 완료되었습니다.'
Add-Content -LiteralPath $logPath -Value "$(Get-Date -Format o) 백업 처리가 완료되었습니다."

이벤트 뷰어 > Windows 로그 > Application에서 소스 이름 KomuraSoft.NightlyJob으로 좁히면 이력을 따라갈 수 있습니다. 모니터링 도구가 있는 환경이라면 이 이벤트 ID를 잡아 알림으로 넘길 수 있습니다. 참고로 New-EventLog / Write-EventLogWindows PowerShell 5.1 cmdlet이며, PowerShell 7에서는 삭제되었습니다(6장 표 참조). PowerShell 7로 실행하는 야간 작업이라면, 알림 부분만 powershell.exe에 넘기거나, 로그 파일 기록과 파일 감시 쪽 알림(작업 스케줄러의 이벤트 시작이나 Power Automate 등)으로 모으는 편이 자연스럽습니다.

Add-Type에는 운영상 중요한 제약이 세 가지 있습니다.3

  • 추가한 형식은 그 세션에 한합니다. 다른 세션이나 원격에서는 다시 Add-Type이 필요합니다.
  • 같은 이름 형식은 다시 정의할 수 없습니다. 시그니처를 고쳐 수정하려면 이름을 바꾸거나 새 세션을 시작합니다. 시행착오는 일회용 콘솔에서 하세요.
  • PowerShell 7에서는 같은 이름 형식이 이미 있으면 컴파일 자체를 건너뜁니다. 「고친 코드가 반영되지 않는다」면 이것을 의심하세요.

하나 더, P/Invoke 공통 주의입니다. 시그니처(인수 형식, 문자 집합, 호출 규약)를 잘못 쓰면 예외로 끝나지 않고 프로세스 전체가 죽을 수 있습니다. 문자열이나 핸들 마샬링의 함정은 「C#에서 Win32 API를 안전하게 호출하기 ── P/Invoke 실무 가이드(DllImport / LibraryImport / CsWin32)」에서 자세히 정리했으므로, Add-Type으로 Win32 API를 본격적으로 쓰기 전에 한 번 읽어 보기를 권합니다.

4. COM을 호출한다 ── New-Object -ComObject와 WSH 도구 상자

.NET보다 오래전부터 Windows에 실려 있는 부품 무리가 COM입니다. New-Object -ComObject <ProgId>로 만들 수 있고, VBScript의 Set objShell = CreateObject("Shell.Application")$objShell = New-Object -ComObject Shell.Application에 그대로 대응합니다.4 WScript.Shell, WScript.Network, Scripting.FileSystemObject 같은 WSH(Windows Script Host) 유래 객체도 마찬가지로 쓸 수 있습니다.5 COM이 무엇인지라는 토대 이야기는 「COM / ActiveX / OCX란 무엇인가 - 차이와 관계를 모아 설명」을 참조하세요.

대표적인 실무 레시피가 바로 가기 일괄 작성입니다. 바로 가기(.lnk) 작성은 cmdlet이 없고, 공식 문서도 「바로 가기 작성과 같은 일부 작업은 WSH 클래스를 쓰는 편이 쉽다」며 WScript.Shell 예를 싣고 있습니다.5

# 공유 폴더 위 도구로의 바로 가기를 모든 단말의 공용 데스크톱에 배포하는 가정
$shortcuts = Import-Csv 'C:\deploy\shortcuts.csv'   # Name, Target, Args 3열
$wsh = New-Object -ComObject WScript.Shell

foreach ($item in $shortcuts) {
    $lnkPath = Join-Path 'C:\Users\Public\Desktop' "$($item.Name).lnk"
    $lnk = $wsh.CreateShortcut($lnkPath)   # 기존 .lnk가 있으면 덮어쓰기 대상이 된다
    $lnk.TargetPath = $item.Target
    $lnk.Arguments  = $item.Args
    $lnk.Save()
    Write-Host "작성: $lnkPath"
}

COM 객체 멤버는 $wsh | Get-Member로 조사할 수 있습니다.5 Outlook에서 메일 초안 작성, Shell.Application으로 특수 폴더 조작 등 응용 범위는 넓습니다. 다만 Excel.Application 같은 ActiveX 실행 파일(별도 프로세스로 시작되는 COM 서버)에는 다음 장의 뒷정리 문제가 따라다닙니다. 공식 문서도, 참조를 놓았을 때 프로세스가 종료하는지는 상대 애플리케이션에 달렸으며 쓰기 전에 종료 동작을 테스트해야 한다고 주의합니다.5

5. COM 뒷정리 ── EXCEL.EXE가 남는 문제와 「무인 Office는 비지원」

5.1. 왜 프로세스가 남는가

COM 객체의 수명은 참조 횟수로 관리됩니다. .NET(=PowerShell)에서 COM을 다루면 COM 객체마다 RCW(Runtime Callable Wrapper)라는 프록시가 만들어지고, RCW가 살아있는 동안 COM 쪽 참조는 해제되지 않습니다. RCW 회수는 가비지 컬렉션에 맡깁니다.6$excel.Quit()를 호출해도, 어딘가에 참조(그것도 $excel.Workbooks.Open(...)처럼 변수에 넣지 않은 중간 객체의 RCW를 포함)가 남아 있으면 EXCEL.EXE는 종료하지 않습니다.

다음 코드는 try/finally가 삼중으로 중첩되어 있어, 처음 보면 구조를 읽기 어려울 것입니다. 먼저 왜 그렇게 쓰는지를 적어 둡니다. 요지는 「뒤쪽 처리일수록 절대 건너뛰고 싶지 않다」이므로, 건너뛰고 싶지 않은 순으로 안쪽에 넣은 것뿐입니다.

  • 가장 바깥 try: Excel을 다루는 본문 처리. 여기서 무엇이 실패해도 이후 뒷정리에는 반드시 들어갑니다.
  • 1단 finally(통합 문서를 닫음): 저장에 실패해도 연 통합 문서는 닫으러 갑니다. 다만 Close 자체가 실패할 수 있습니다.
  • 2단 finally(Quit 호출): 그래서 Close 실패에 휘말리지 않는 위치에서 Quit를 호출합니다. Excel이 응답 없음이 되면 Quit도 실패할 수 있습니다.
  • 3단 finally(RCW를 해제하고 GC를 돌림): 그래서 Quit 성패와 무관하게 참조 해제만은 반드시 실행합니다. 여기가 빠지면 프로세스가 남습니다.

즉 삼중인 것은 겉모양을 위한 것이 아니라, 「뒷정리 자체가 실패할 수 있다」는 전제를 한 단씩 없앤 결과입니다. 반대로 말하면 CloseQuit도 실패하지 않는다는 전제로 써도 된다면 finally는 하나로 충분합니다.

$excel = New-Object -ComObject Excel.Application
try {
    $excel.DisplayAlerts = $false
    $books = $excel.Workbooks              # 중간 객체도 변수로 받아, 나중에 해제할 수 있게 한다
    $book  = $books.Open('C:\work\月次売上.xlsx')
    $sheet = $book.Worksheets.Item(1)
    $sheet.Cells.Item(1, 1).Value2 = "갱신: $(Get-Date -Format 'yyyy-MM-dd')"
    $book.Save()
}
finally {
    # Close가 실패하는 경로야말로 EXCEL.EXE가 남기 쉬우므로,
    # 중첩 finally로 Quit와 해제를 반드시 실행한다
    try {
        if ($book) { $book.Close($false) }
    }
    finally {
        # Quit가 실패해도(Excel 응답 없음·COM 서버 끊김 등) RCW 해제는 반드시 수행한다
        try {
            if ($excel) { $excel.Quit() }
        }
        finally {
            # RCW 참조 횟수를 명시적으로 줄인다(자식에서 부모 순)
            foreach ($obj in @($sheet, $book, $books, $excel)) {
                if ($obj) { [void][System.Runtime.InteropServices.Marshal]::ReleaseComObject($obj) }
            }
            # 변수에 못 받은 RCW를 GC가 회수하게 하는 보험
            [System.GC]::Collect()
            [System.GC]::WaitForPendingFinalizers()
            [System.GC]::Collect()
        }
    }
}

Marshal.ReleaseComObject는 RCW 참조 횟수를 줄이고, 0이 되는 시점에 COM 쪽 참조를 해제합니다. 다만 공식 문서는 해제된 RCW에 접근하면 예외나 액세스 위반을 부르므로 「절대 필요한 경우에만 쓸 것」이라고 강하게 주의합니다.7

위 코드에서 중간 객체를 하나씩 변수로 받는 것은 흔히 「2-dot 규칙」이라 부르는 절차입니다. $excel.Workbooks.Open(...)처럼 점을 두 개 이상 이으면, 중간의 Workbooks에 해당하는 RCW가 어떤 변수에도 들어가지 않은 채 생성되어 해제할 수단이 남지 않습니다. 그래서 한 줄에 점은 하나까지로 두고, 사이 객체를 반드시 변수로 받는다 ── 가 이 규칙의 내용입니다. 해제 패턴의 두 갈래(ReleaseComObject 쪽과 GC 쪽), 2-dot 규칙의 상세, 그래도 남은 프로세스 특정 방법은 「C# Excel 조작에서 EXCEL.EXE가 남는 문제 ── COM 참조 해제 패턴과 대체 판단」에서 자세히 다룹니다. C# 기사지만 RCW 구조는 PowerShell에서도 동일합니다.

5.2. 애초에 무인 실행에서 Office를 쓰지 않는다

더 근본적인 판단이 있습니다. Microsoft는 「무인·비대화형 클라이언트 애플리케이션이나 구성 요소(ASP, ASP.NET, DCOM, NT 서비스 포함)에서 Office를 자동화하는 것을 현재 권장하지 않으며, 지원하지도 않는다」고 공식으로 밝히고 있습니다. Office는 대화형 사용을 전제로 설계되어 있어, 무인 환경에서는 불안정한 동작이나 교착을 보일 수 있기 때문입니다.8 예상치 못한 대화 상자로 처리가 멈추거나, STA 기반이라 다중 실행을 견디지 못하는 구체적 문제도 열거되어 있습니다.8 여기서 STA는 단일 스레드 아파트먼트(Single-Threaded Apartment)의 약자로, COM 스레드 모델 중 하나입니다. 객체로의 호출을 그 객체를 만든 스레드로 모아 한 줄씩 처리하는 구조이며, Office 애플리케이션은 이 전제로 만들어져 있습니다. 그래서 「한 대 서버에서 같은 처리를 병렬로 여러 개 돌린다」는 쓰임새에 구조적으로 맞지 않는다는 이야기입니다. COM 스레드 모델 자체는 「COM STA/MTA 기초 지식 - 스레드 모델과 hang을 피하는 사고방식」에서 자세히 다룹니다.

즉 작업 스케줄러의 야간 작업이나 서비스에서 Excel COM을 두드리는 구성은, 돌아가는 때부터 비지원입니다. 무인 처리에서는 Office 프로그램 본체를 시작하지 않는 Open XML 계열 파일 직접 편집이나 CSV 연동으로의 대체가 권장됩니다.8 PowerShell에서의 구체적 대체 레시피는 동시에 공개한 「PowerShell에서 Excel·CSV 업무 처리 레시피」에 정리했습니다. Excel COM을 써도 되는 것은 「사람이 로그온한 데스크톱에서, 사람의 작업을 보조하는」 용도까지로 선을 그으세요.

6. 5.1과 7에서 「쓸 수 있는 .NET」은 다르다

Windows PowerShell 5.1은 .NET Framework 4.5 계열, PowerShell 7은 .NET(구 .NET Core) 위에 구축되어 있습니다.1 cmdlet만 쓰는 동안에는 차이를 의식할 장면이 한정되지만, 이 기사처럼 .NET을 직접 호출하기 시작하면 차이가 드러납니다.

논점 Windows PowerShell 5.1 PowerShell 7
기반 .NET Framework 4.5 계열1 .NET(7.4는 .NET 8.0 등, 버전마다 갱신)1
메서드 오버로드 적다(예: String.Split은 6종)1 많다(같은 Split('pq')라도 결과가 바뀌는 예가 공식에 실림)1
어셈블리 로드 GAC 어셈블리는 Add-Type으로 명시 로드가 필요한 경우가 많다3 기본 제공 어셈블리는 요청 시 자동 로드3
쓸 수 없게 된 것 *-EventLog 등 Windows 전용 cmdlet 일부가 삭제1

「5.1에서 됐는데 7에서 안 된다」「그 반대」 모두 일어날 수 있습니다. 양쪽 대응이 필요한 스크립트는 반드시 양쪽 환경에서 테스트하세요. 이전 판단의 전체 그림은 동시에 공개한 「Windows PowerShell 5.1과 PowerShell 7의 차이와 이전」에서 다룹니다.

7. 실무 정석(판단표)

논점 선택지(권장에 「권장」을 명시) 판단 기준
원하는 처리가 cmdlet에 없다 포기한다 / .NET 클래스를 직접 호출(권장) 먼저 [형식]::메서드를 찾는다. System.IO, System.Text, System.IO.Compression 부근에서 「한 걸음」의 대부분은 메워진다1
인스턴스 생성 New-Object / [형식]::new()(권장) 5.0 이후만이면 ::new(). 생성자 목록이 보이고 빠르다. COM만은 New-Object -ComObject 한 가지24
Win32 API가 필요 수작업으로 참는다 / Add-Type으로 P/Invoke(권장) API 몇 개라면 Add-Type으로 충분. 시그니처 오류는 프로세스 전체를 죽이므로 일회용 세션에서 검증3
바로 가기 작성 등 WSH 유래 조작 COM(WScript.Shell)(권장) cmdlet이 없는 영역은 COM이 지금도 현역. 공식 샘플도 이 방식5
사람의 작업을 보조하는 Excel 조작 COM + 확실한 뒷정리(권장) finally에서 Quit·ReleaseComObject·GC까지를 세트로. 중간 객체도 변수로 받는다76
무인 작업에서의 Excel/Office 처리 COM / Office를 시작하지 않는 방식(권장) 무인 Office 자동화는 비지원. Open XML 계열·CSV로 바꾼다8
스크립트가 너무 커졌다 PowerShell로 버틴다 / C# 도구화(권장) Add-Type 코드가 수백 줄이 됐다, 형식 재정의 제약이 개발을 방해한다, 배포처에서 매번 컴파일시키고 싶지 않다 ── 중 하나가 나오면 이전 신호

마지막 행이 이 기사의 마무리 판단입니다. Add-Type으로 넣는 C#이 비대해졌다면, 이미 「C# 프로그램을 PowerShell 껍데기로 감싼」 상태입니다. Visual Studio의 형식 검사와 디버거, NuGet 패키지, 단위 테스트를 쓸 수 있는 C# 프로젝트로 옮기는 편이 개발도 유지보수도 빨라집니다. C# 쪽에서 PowerShell 자산을 다시 불러오는 방법도 있으므로, 이전은 전부 다시 쓰는 일이 아닙니다. 「C#(CSharp)으로 PowerShell을 실행해 객체로 받는 방법」에서 다리 역할 패턴을 소개합니다.

8. 정리

  • PowerShell은 .NET 위에 구축되어 있어, [형식]::정적 메서드·New-Object·[형식]::new()로 .NET 클래스 라이브러리를 직접 호출할 수 있습니다. cmdlet에 없는 기능의 첫 후보는 .NET입니다.
  • Add-Type으로 C# 코드를 그 자리에서 컴파일할 수 있고, DllImport 시그니처를 넘기면 Win32 API도 P/Invoke로 호출할 수 있습니다. 형식은 세션에 한하며, 같은 이름 재정의는 할 수 없습니다.
  • COM은 New-Object -ComObject로 조작합니다. 바로 가기 작성 등 WSH 유래 도구는 지금도 현역입니다.
  • COM 수명은 참조 횟수+RCW로 관리되며, 참조가 남으면 EXCEL.EXE 같은 프로세스가 잔류합니다. finally에서 Quit·ReleaseComObject·GC까지 포함한 뒷정리를 씁니다.
  • 무인 실행에서 Office 자동화는 Microsoft가 비권장·비지원이라고 분명히 말하고 있습니다. 야간 작업은 Office를 시작하지 않는 방식으로 바꿉니다.
  • 5.1과 7은 기반 .NET이 달라, 쓸 수 있는 형식·오버로드가 다릅니다. 양쪽 대응 스크립트는 양쪽 테스트가 필수입니다.
  • Add-Type이 비대해지면 C# 도구화 신호입니다. 스크립트와 실행 파일의 다리는 기존 형식이 있습니다.

관련 기사

관련 상담 영역

합동회사 小村소프트에서는 PowerShell로 사내 업무를 자동화하는 일, COM 자산(Excel 연동·레거시 부품)을 포함한 스크립트 설계·개수, 「스크립트가 너무 커진」 상태에서 C# 도구화하는 일을 다룹니다. EXCEL.EXE 잔류 같은 장애 조사에도 대응합니다.

참고 링크

  1. Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x. Windows PowerShell 5.1이 .NET Framework 4.5 위에, PowerShell 6.0 이후가 .NET Core(현재 .NET) 위에 구축되어 있다는 점, 각 버전의 기반이 되는 .NET 버전 목록, String.Split 오버로드 차이로 같은 코드의 결과가 바뀌는 예, 7에서 삭제된 cmdlet에 대해.  2 3 4 5 6 7 8 9

  2. Microsoft Learn, about_Object_Creation. PowerShell 5.0에서 모든 .NET 형식에 추가된 정적 new() 메서드, ::new를 치면 생성자 오버로드 목록을 확인할 수 있다는 점, cmdlet으로 얻은 객체에는 PowerShell이 NoteProperty를 추가하므로 ::new()로 만든 객체와 멤버가 일치하지 않는 경우가 있다는 점에 대해.  2 3 4

  3. Microsoft Learn, Add-Type. Add-Type이 C# 소스를 컴파일해 세션에 형식을 추가한다는 점, MemberDefinition으로 P/Invoke(user32.dll의 ShowWindowAsync)하는 공식 샘플, 추가한 형식은 세션에 한하며 같은 이름 형식은 재정의할 수 없다는 점, PowerShell 7에서는 같은 이름 형식이 있으면 컴파일을 건너뛴다는 점, 5.1에서는 GAC 어셈블리 로드에 Add-Type이 필요하고 6 이후는 자동 로드된다는 점에 대해.  2 3 4 5 6 7 8

  4. Microsoft Learn, New-Object. New-Object가 .NET 또는 COM 객체 인스턴스를 만든다는 점, -ComObject 매개변수에 ProgId를 지정한다는 점, VBScript의 CreateObject(“Shell.Application”)이 New-Object -ComObject “Shell.Application”에 대응한다는 점에 대해.  2 3

  5. Microsoft Learn, Creating .NET and COM objects (New-Object). WScript.Shell 등 WSH 객체 생성, 바로 가기 작성은 WSH 클래스를 쓰는 편이 쉽다는 기술과 CreateShortcut 실례, COM 객체에 Get-Member를 적용하는 점, ActiveX 실행 파일의 종료 동작이 애플리케이션에 따라 달라 사전 테스트가 필요하다는 점, New-Object가 .NET RCW를 쓴다는 점에 대해.  2 3 4 5 6

  6. Microsoft Learn, Runtime Callable Wrapper. .NET이 COM 객체를 RCW라는 프록시로 공개한다는 점, COM 객체마다 RCW가 하나 만들어진다는 점, RCW가 가비지 컬렉션으로 회수될 때 COM 객체 참조를 해제한다는 점에 대해.  2 3

  7. Microsoft Learn, Marshal.ReleaseComObject(Object) Method. ReleaseComObject가 RCW 참조 횟수를 줄이고 0에서 기반 COM 객체를 해제한다는 점, 해제 후 RCW 사용이 예외나 액세스 위반을 부른다는 점, 「절대 필요한 경우에만 쓴다」는 공식 주의, FinalReleaseComObject와의 관계에 대해.  2 3

  8. Microsoft Learn, Considerations for unattended automation of Office in the Microsoft 365 for unattended RPA environment. Microsoft가 무인·비대화형 클라이언트 앱이나 구성 요소(ASP, ASP.NET, DCOM, NT 서비스 포함)에서 Office 자동화를 권장하지 않으며 지원하지도 않는다는 점, 무인 환경에서의 불안정 동작이나 교착 가능성, 대화 상자에 의한 정지·STA에서 비롯된 다중 실행 제약 등 구체적 문제, Open XML 파일 형식의 직접 편집이 권장 대체라는 점에 대해.  2 3 4 5

  9. Microsoft Learn, Expand-Archive. Expand-Archive가 System.IO.Compression.ZipArchive API를 사용한다는 점, 이 API에 최대 파일 크기 2GB 제한이 있다는 점, 이 .NET API가 PKWARE사의 공식 ZIP 파일 형식 사양에 맞는 파일을 다룬다는 점에 대해. 

  10. Microsoft Learn, Compress-Archive. Compress-Archive가 System.IO.Compression.ZipArchive API로 압축한다는 점, 「The API limits the maximum file size to 2GB」로 2GB 상한이 기반 API 쪽 제한이라고 명시되고, 참조로 ZipArchive 클래스가 들어 있다는 점에 대해(ZipArchive 클래스 레퍼런스 본문에는 2GB라는 수치 기재가 없으므로, 수치의 일차 정보는 이 cmdlet 쪽 페이지가 됩니다). 

  11. Microsoft Learn, ZipFile Class. ZipFile 클래스가 ZIP 작성·압축 해제·열기의 정적 메서드를 제공한다는 점, .NET Framework에서 쓰려면 System.IO.Compression.FileSystem 어셈블리 참조가 필요하다는 점, CreateFromDirectory/ExtractToDirectory/OpenRead의 용도에 대해.  2

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

ActiveX 이관

COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.

이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

PowerShell에서 .NET 클래스를 직접 호출할 수 있습니까?
호출할 수 있습니다. PowerShell은 .NET 위에 구축되어 있으므로, [System.IO.Path]::GetFileNameWithoutExtension()처럼 대괄호로 형식 이름을 쓰고 ::로 정적 메서드를 호출합니다. 인스턴스가 필요하면 New-Object 또는 PowerShell 5.0부터 쓸 수 있는 [형식]::new()로 만듭니다. cmdlet이 없는 처리라도 .NET 클래스 라이브러리에 있으면 추가 설치 없이 스크립트에서 쓸 수 있습니다.
New-Object와 [형식]::new() 중 어느 쪽을 써야 합니까?
어느 쪽이든 객체는 만들 수 있지만, [형식]::new()는 인수 없이 치면 그 형식의 생성자 목록이 나오므로 인수를 확인하면서 쓰기에 맞습니다. 성능도 [형식]::new() 쪽이 유리합니다. 한편 COM 객체 생성은 New-Object -ComObject로만 가능합니다. PowerShell 5.0보다 오래된 환경까지 고려할 때도 New-Object를 씁니다.
PowerShell에서 Excel을 COM으로 조작하면 EXCEL.EXE가 남는 이유는 무엇입니까?
COM 객체는 참조 횟수로 수명이 관리되고, .NET 쪽에서는 RCW(Runtime Callable Wrapper)가 그 참조를 붙잡고 있습니다. $excel.Workbooks.Open()처럼 쓰면 중간 Workbooks 객체에도 보이지 않는 RCW가 만들어져, 변수에 넣지 않은 참조가 남습니다. Quit()를 호출해도 참조가 남아 있으면 프로세스는 종료하지 않습니다. 사용이 끝나면 Marshal.ReleaseComObject로 명시적으로 해제하거나, 변수를 null로 만든 뒤 GC.Collect()와 WaitForPendingFinalizers()로 회수하는 뒷정리가 필요합니다.
서버나 야간 배치에서 Excel을 COM으로 조작해도 됩니까?
권장되지 않습니다. Microsoft는 무인·비대화형 클라이언트 앱이나 구성 요소에서 Office 애플리케이션을 자동화하는 것을 권장하지 않으며 지원하지도 않는다고 공식으로 밝히고 있습니다. Office는 대화형 데스크톱 사용을 전제로 설계되어 있어, 무인 실행에서는 불안정한 동작이나 교착이 일어날 수 있기 때문입니다. 무인 처리에서는 Open XML 계열 라이브러리나 CSV 연동처럼 Office 프로그램 본체를 시작하지 않는 방법으로 바꾸는 것이 정석입니다.
Add-Type으로 Win32 API를 호출할 수 있습니까?
호출할 수 있습니다. Add-Type의 -MemberDefinition에 DllImport가 붙은 C# 시그니처를 넘기면 그 자리에서 컴파일되어 P/Invoke로 Win32 API를 호출할 수 있게 됩니다. 공식 문서에도 user32.dll의 ShowWindowAsync를 호출하는 예가 실려 있습니다. 다만 추가한 형식은 세션 안에 남고, 같은 이름 형식은 다시 정의할 수 없으므로 시행착오할 때는 새 세션에서 다시 실행하세요. 시그니처를 잘못 쓰면 프로세스 전체가 죽을 수도 있어, 동작 확인은 일회용 콘솔에서 하는 편이 안전합니다.
Windows PowerShell 5.1과 PowerShell 7에서 .NET 호출에 차이가 있습니까?
있습니다. 5.1은 .NET Framework 4.5 계열, PowerShell 7은 .NET(구 .NET Core) 위에 구축되어 있어, 쓸 수 있는 형식이나 메서드 오버로드가 다릅니다. 예를 들어 String.Split 오버로드는 7 쪽이 많고, 같은 코드라도 결과가 바뀌는 예가 공식으로 소개되어 있습니다. 또한 5.1에서는 GAC 어셈블리를 Add-Type으로 로드해야 하는 경우가 많은 반면, 7에서는 기본 제공 어셈블리가 필요할 때 자동 로드됩니다. 양쪽에서 돌릴 스크립트는 어느 환경에서든 테스트하세요.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기