PowerShell 메서드 사용법

PowerShell 메서드 사용법 (about_Methods)

객체에는 데이터를 담는 속성(property)이 있고, 그 객체를 바꿀 수 있게 해주는 메서드(method)도 함께 있어요. 메서드가 정확히 뭔지, 어떻게 호출하는지, 메서드 정의는 어떻게 읽는지까지 차근차근 설명해 드릴게요.

출처: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods

본문

PowerShell에서 데이터 저장소의 항목이나 컴퓨터의 상태는 모두 객체로 표현돼요. 예를 들어 파일 시스템 드라이브의 파일은 FileInfo 객체로, 컴퓨터에서 실행 중인 프로세스는 ProcessInfo 객체로 나타나죠.

객체에는 객체에 대한 데이터를 저장하는 속성이 있고, 그 객체를 변경할 수 있게 하는 메서드가 있어요.

여기서 "메서드"란, 객체에 대해 수행할 수 있는 동작을 지정하는 명령어의 집합이에요. 예를 들어 FileInfo 객체에는 해당 객체가 나타내는 파일을 복사하는 CopyTo 메서드가 포함돼 있어요.

어떤 객체의 메서드를 확인하고 싶다면 Get-Member cmdlet을 쓰면 돼요. MemberType 속성을 "Method" 값으로 지정하면 되는데, 다음 명령은 프로세스 객체의 메서드를 가져와요.

Get-Process | Get-Member -MemberType Method
TypeName: System.Diagnostics.Process

Name                      MemberType Definition
----                      ---------- ----------
BeginErrorReadLine        Method     System.Void BeginErrorReadLine()
BeginOutputReadLine       Method     System.Void BeginOutputReadLine()
...
Kill                      Method     System.Void Kill()
Refresh                   Method     System.Void Refresh()
Start                     Method     bool Start()
ToString                  Method     string ToString()
WaitForExit               Method     bool WaitForExit(int milliseconds), ...
WaitForInputIdle          Method     bool WaitForInputIdle(int millisecon...

객체의 메서드를 수행(또는 "호출")하려면 점(.)을 찍고 메서드 이름, 그리고 괄호 ()를 입력해요. 메서드에 인수가 있으면 그 인수 값을 괄호 안에 넣어요. 인수가 하나도 없을 때조차 모든 메서드 호출에 괄호는 필수고요. 메서드가 여러 인수를 받는다면 쉼표로 구분해 주면 돼요.

예를 들어 다음 명령은 프로세스의 Kill 메서드를 호출해서 컴퓨터에서 실행 중인 메모장(Notepad) 프로세스를 종료해요.

$notepad = Get-Process notepad
$notepad.Kill()

이 예시는 위 두 문장을 합쳐서 더 짧게 줄일 수도 있어요.

(Get-Process Notepad).Kill()

Get-Process 명령을 괄호로 묶은 건, Kill 메서드를 호출하기 전에 그 명령이 먼저 실행되도록 하기 위해서예요. 이렇게 해서 반환된 Process 객체에 Kill 메서드를 호출하는 거죠.

또 하나 아주 유용한 메서드가 문자열의 Replace 메서드예요. Replace 메서드는 문자열 안의 텍스트를 교체해 줘요. 아래 예시에서처럼 점(.)은 문자열의 닫는 따옴표 바로 뒤에 붙일 수도 있어요.

'this is rocket science'.Replace('rocket', 'rock')
this is rock science

앞선 예시들에서 봤듯이, 명령으로 얻은 객체든, 변수에 담긴 객체든, 결과가 객체가 되는 어떤 것이든(따옴표 안의 문자열처럼) 그 객체에서 메서드를 호출할 수 있어요.

또한 PowerShell 4.0부터는 동적 메서드 이름을 사용한 메서드 호출도 지원돼요.

메서드 알아보기

객체의 메서드 정의를 찾으려면 해당 객체 유형의 도움말 항목으로 가서 메서드 페이지를 찾아보면 돼요. 예를 들어 다음 페이지는 프로세스 객체의 메서드를 설명해요: System.Diagnostics.Process.

메서드의 인수를 파악하려면 메서드 정의를 살펴보면 되는데, 이건 PowerShell cmdlet의 구문 다이어그램과 비슷한 모양이에요.

메서드 정의에는 메서드 시그니처가 하나 이상 있을 수 있어요. 이는 PowerShell cmdlet의 매개 변수 집합과 비슷한 개념인데, 시그니처는 메서드를 호출할 수 있는 모든 유효한 명령 형식을 보여줘요.

예를 들어 FileInfo 클래스의 CopyTo 메서드에는 다음과 같은 두 개의 메서드 시그니처가 있어요.

    CopyTo(String destFileName)
    CopyTo(String destFileName, Boolean overwrite)

첫 번째 메서드 시그니처는 대상 파일 이름(과 경로)을 받아요. 다음 예시는 첫 번째 CopyTo 메서드를 사용해서 Final.txt 파일을 C:\bin 디렉터리로 복사해요.

(Get-ChildItem C:\final.txt).CopyTo("C:\bin\final.txt")

참고 — PowerShell의 인수(argument) 모드와 달리, 객체 메서드는 식(expression) 모드에서 실행돼요. 이건 PowerShell이 기반으로 하는 .NET 프레임워크에 그대로 전달되는 방식이죠. 식 모드에서는 bareword 인수(따옴표 없는 문자열)를 쓸 수 없어요. 이 차이는 경로를 매개 변수로 쓸 때와 인수로 쓸 때에서 확인할 수 있어요. 구문 분석(parsing) 모드에 대해 더 자세히 알고 싶다면 about_Parsing 문서를 읽어보세요.

두 번째 메서드 시그니처는 대상 파일 이름과, 대상 파일이 이미 존재할 때 덮어쓸지 여부를 결정하는 Boolean 값을 받아요.

다음 예시는 두 번째 CopyTo 메서드를 사용해서 Final.txt 파일을 C:\bin 디렉터리로 복사하고, 기존 파일을 덮어써요.

(Get-ChildItem C:\final.txt).CopyTo("C:\bin\final.txt", $true)

멤버 액세스 열거 (Member-access enumeration)

PowerShell 3.0부터, 목록 컬렉션에 존재하지 않는 메서드에 멤버 액세스 연산자(.)로 접근하면 PowerShell이 자동으로 컬렉션의 항목들을 열거하고 각 항목에 대해 그 메서드를 호출해요. 자세한 내용은 about_Member-Access_Enumeration 문서를 참고하세요.

예시

다음 예시는 객체 컬렉션 안의 개별 프로세스 객체에 Kill 메서드를 실행해요.

첫 번째 명령은 메모장 프로세스를 세 개 시작해요. Get-Process가 세 개의 메모장 프로세스를 모두 가져와 $p 변수에 저장해요.

Notepad; Notepad; Notepad
$p = Get-Process Notepad
$p.Count
3

다음 명령은 $p 변수 안의 세 프로세스 모두에 Kill 메서드를 실행해요. 프로세스 컬렉션에는 Kill 메서드가 없는데도 이 명령은 잘 동작해요.

$p.Kill()
Get-Process Notepad

그리고 Get-Process 명령으로 Kill 메서드가 실제로 동작했는지 확인해요.

Get-Process : Cannot find a process with the name "notepad". Verify the proc
ess name and call the cmdlet again.
At line:1 char:12
+ Get-Process <<<<  notepad
    + CategoryInfo          : ObjectNotFound: (notepad:String) [Get-Process]
, ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShel
l.Commands.GetProcessCommand

이 예시는 ForEach-Object cmdlet을 사용해서 컬렉션의 각 객체에 메서드를 실행한 것과 기능적으로 동일해요.

$p | ForEach-Object {$_.Kill()}

ForEach 및 Where 메서드

PowerShell 4.0부터는 컬렉션 필터링을 메서드 구문으로 할 수 있게 됐어요. 그래서 컬렉션을 다룰 때 두 개의 새 메서드인 ForEachWhere를 쓸 수 있게 된 거죠.

이 메서드들에 대한 자세한 내용은 about_Arrays 문서에서 확인해 볼게요.

여러 오버로드가 존재할 때 특정 메서드 호출하기

.NET 메서드를 호출할 때 이런 상황을 생각해 볼게요. 메서드가 객체를 받는데, 인터페이스를 통해 더 구체적인 형식을 받는 오버로드가 함께 있다면, PowerShell은 명시적으로 그 인터페이스로 캐스팅하지 않는 한 객체를 받는 메서드를 선택해요.

Add-Type -TypeDefinition @'

   // Interface
   public interface IFoo {
     string Bar(int p);
   }

   // Type that implements the interface
   public class Foo : IFoo {

   // Direct member method named 'Bar'
   public string Bar(object p) { return $"object: {p}"; }

   // *Explicit* implementation of IFoo's 'Bar' method().
   string IFoo.Bar(int p) {
       return $"int: {p}";
   }

}
'@

이 예시에서는 Bar 메서드의 덜 구체적인 object 오버로드가 선택됐어요.

[Foo]::new().Bar(1)
object: 1

이번 예시에서는 메서드를 IFoo 인터페이스로 캐스팅해서 Bar 메서드의 더 구체적인 오버로드를 선택했어요.

([IFoo] [Foo]::new()).Bar(1)
int: 1

어떤 오버로드가 사용됐는지 찾기

PowerShell 7.6부터는 MethodInvocation 추적을 사용해서 PowerShell이 어떤 메서드 오버로드를 선택했는지 확인할 수 있어요.

다음 예시들은 Trace-Command를 사용해서 String.Split 메서드를 호출할 때 선택된 오버로드를 보여줘요.

Trace-Command -PSHost -Name MethodInvocation -Expression {
    "a 1 b 1 c 1 d".Split(1)
}

첫 번째 예시에서는 정수 1[string]이 아니라 [char]로 변환됐어요. 그래서 문자열이 string "1"이 아니라 [char]1로 분리된 거예요.

DEBUG: ... MethodInvocation Information: 0 : Invoking method: string[] Spli
t(char separator, System.StringSplitOptions options = System.StringSplitOpt
ions.None)

a 1 b 1 c 1 d

두 번째 예시에서는 Split() 메서드를 string "1"로 호출해요.

Trace-Command -PSHost -Name MethodInvocation -Expression {
    "a 1 b 1 c 1 d".Split("1")
}

이번에는 PowerShell이 [string] 구분 기호를 받는 오버로드를 선택했어요.

DEBUG: ... MethodInvocation Information: 0 : Invoking method: string[] Spli
t(string separator, System.StringSplitOptions options = System.StringSplitO
ptions.None)

a
 b
 c
 d

파일 시스템 경로를 받는 .NET 메서드 사용하기

PowerShell은 프로세스당 여러 runspace를 지원해요. 그리고 각 runspace에는 자체 current directory(현재 디렉터리)가 있어요. 이건 현재 프로세스의 작업 디렉터리, 즉 [System.Environment]::CurrentDirectory와는 다른 값이에요.

.NET 메서드는 프로세스 작업 디렉터리를 사용하는 반면, PowerShell cmdlet은 runspace 위치를 사용해요. 또한 .NET 메서드는 파일 시스템 고유(native) 경로에서만 동작하고, PowerShell 경로(Path) 객체에서는 동작하지 않아요. 그래서 PowerShell 경로를 .NET 메서드에 쓰려면, 그 경로를 .NET 메서드에 전달하기 전에 파일 시스템 고유 경로로 먼저 확인(resolve)해 줘야 해요.

더 알아보기