CmdletBinding이란?
[CmdletBinding()]은 PowerShell 함수를 고급 함수(Advanced Function)로 만들어주는 어노테이션이다. Java의 @annotation과 유사하게, 함수에 추가 기능을 부여한다.
기본 함수 vs 고급 함수
1
2
3
4
5
6
7
8
9
10
11
12
| # 기본 함수 (Simple Function)
function Get-SimpleData {
param([string]$Name)
Write-Host "Name: $Name"
}
# 고급 함수 (Advanced Function)
function Get-AdvancedData {
[CmdletBinding()] # 👈 이것만 추가하면 고급 함수!
param([string]$Name)
Write-Host "Name: $Name"
}
|
차이점
| 기능 | 기본 함수 | 고급 함수 (CmdletBinding) |
|---|
-Verbose | ❌ | ✅ |
-Debug | ❌ | ✅ |
-ErrorAction | ❌ | ✅ |
-WarningAction | ❌ | ✅ |
-InformationAction | ❌ | ✅ |
$PSCmdlet 변수 | ❌ | ✅ |
| Pipeline 지원 | 제한적 | 완전 지원 |
실전 예제: 보안 스캐너
Windows Agent의 보안 스캔 스크립트에서 CmdletBinding을 활용한 실제 코드를 살펴보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| <#
.SYNOPSIS
ITSM Agent - Security Scan Tool
.DESCRIPTION
수동으로 보안 스캔을 수행하는 도구입니다.
.PARAMETER ConfigPath
설정 파일 경로 (기본값: 현재 디렉토리의 config\agent-config.json)
.PARAMETER OutputFile
스캔 결과를 저장할 파일 경로 (JSON 형식, 선택사항)
.PARAMETER SendToBackend
스캔 결과를 Backend-portal로 전송합니다
.EXAMPLE
.\Invoke-SecurityScan.ps1
.EXAMPLE
.\Invoke-SecurityScan.ps1 -OutputFile "C:\temp\scan-results.json"
.EXAMPLE
.\Invoke-SecurityScan.ps1 -SendToBackend
.EXAMPLE
.\Invoke-SecurityScan.ps1 -OutputFile "C:\temp\scan-results.json" -SendToBackend
#>
[CmdletBinding()] # 👈 고급 함수 선언
param(
[string]$ConfigPath = "$PSScriptRoot\config\agent-config.json",
[string]$OutputFile = "",
[switch]$SendToBackend # 👈 스위치 파라미터 (true/false)
)
|
CmdletBinding의 이점
- 자동으로
-Verbose 지원 ```powershell실행 시
.\Invoke-SecurityScan.ps1 -Verbose
스크립트 내부에서
Write-Verbose “Loading security patterns…” # -Verbose 시에만 출력
1
2
3
4
5
6
|
2. **에러 처리 개선**
```powershell
# -ErrorAction 자동 지원
.\Invoke-SecurityScan.ps1 -ErrorAction Stop
.\Invoke-SecurityScan.ps1 -ErrorAction SilentlyContinue
|
- 파이프라인 처리 ```powershell [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [string]$InputObject )
process { # 파이프라인으로 들어오는 각 항목 처리 Write-Host “Processing: $InputObject” }
사용 예
Get-Content files.txt | .\Invoke-SecurityScan.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
---
## PowerShell 자동 변수 (Automatic Variables)
PowerShell은 스크립트 실행 시 자동으로 생성되는 변수들을 제공한다.
### 주요 자동 변수
| 변수 | 설명 | 예시 |
|------|------|------|
| `$PSScriptRoot` | 스크립트가 있는 디렉토리 경로 | `C:\Scripts` |
| `$PSCommandPath` | 실행 중인 스크립트 전체 경로 | `C:\Scripts\scan.ps1` |
| `$MyInvocation` | 현재 명령의 실행 정보 | `$MyInvocation.MyCommand.Name` |
| `$PSBoundParameters` | 전달된 파라미터 목록 | `@{ConfigPath="..."}` |
| `$PSCmdlet` | Cmdlet 관련 메서드 제공 | `$PSCmdlet.ShouldProcess()` |
| `$Error` | 에러 히스토리 배열 | `$Error[0]` (최근 에러) |
| `$?` | 마지막 명령 성공 여부 | `$true` 또는 `$false` |
| `$null` | Null 값 | `if ($var -eq $null)` |
### 실전 활용: $PSScriptRoot
```powershell
# ❌ 나쁜 예: 하드코딩된 경로
$ConfigPath = "C:\Program Files\ITSM\config\agent-config.json"
# ✅ 좋은 예: 상대 경로 사용
$ConfigPath = "$PSScriptRoot\config\agent-config.json"
# 모듈 임포트도 동일하게
$ModulesDir = Join-Path $PSScriptRoot "modules"
Import-Module "$ModulesDir\Logger.psm1" -Force
Import-Module "$ModulesDir\Security.psm1" -Force
|
출력 버리기 (Discarding Output)
PowerShell에서는 모든 출력이 파이프라인으로 전달된다. 불필요한 출력을 버리는 여러 방법이 있다.
방법 1: $null에 할당
1
2
3
4
| # 👍 가장 빠른 방법
$null = cmd /c chcp 65001 2>&1
# 설명: cmd 출력을 $null 변수에 할당 → 버려짐
|
방법 2: Out-Null
1
2
| # 👎 느림 (파이프라인 오버헤드)
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
방법 3: [void]
1
2
| # 👍 빠름
[void](New-Item -ItemType Directory -Path $LogDir -Force)
|
방법 4: > $null
1
2
| # 👎 파일 리다이렉션 오버헤드
cmd /c chcp 65001 > $null 2>&1
|
성능 비교
| 방법 | 속도 | 권장 |
|---|
$null = ... | 가장 빠름 | ✅ 권장 |
[void](...) | 빠름 | ✅ 권장 |
... > $null | 보통 | ⚠️ 파일 작업 시 사용 |
... | Out-Null | 느림 | ❌ 비권장 |
해시테이블 vs 스크립트 블록
PowerShell에서 중괄호 {}는 컨텍스트에 따라 다르게 해석된다.
해시테이블 @{}
1
2
3
4
5
6
7
8
9
10
11
12
| # @ 기호로 해시테이블 선언
$agentMetadata = @{
agent_id = $Config.agent.id
agent_version = $Config.agent.version
group_name = $Config.agent.group_name
os_type = "Windows"
os_version = (Get-WmiObject Win32_OperatingSystem).Caption
}
# 접근
Write-Host $agentMetadata.agent_id
Write-Host $agentMetadata["os_type"]
|
스크립트 블록 {}
1
2
3
4
5
6
7
8
| # @ 없이 중괄호만 사용 → 실행 가능한 코드 블록
$scriptBlock = {
Write-Host "This is executable code"
Get-Process | Where-Object CPU -gt 10
}
# 실행
& $scriptBlock # 또는 Invoke-Command -ScriptBlock $scriptBlock
|
실전 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 해시테이블: 데이터 구조
$scanReport = @{
agent_id = $Config.agent.id
scan_timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
total_findings = $findings.Count
summary = @{
by_severity = $severityCount
by_type = $typeCount
}
findings = $findingsData
}
# JSON 변환
$jsonData = $scanReport | ConvertTo-Json -Depth 10
|
타입 지정 (Type Constraints)
PowerShell은 동적 타입이지만, 명시적 타입 지정이 가능하다.
기본 타입
1
2
3
4
5
6
7
8
9
| # 파라미터 타입 지정
[CmdletBinding()]
param(
[string]$ConfigPath, # 문자열
[int]$MaxSize, # 정수
[bool]$Enabled, # 불린
[switch]$SendToBackend, # 스위치 (특별한 불린)
[object]$Data # 모든 타입 허용 (root type)
)
|
배열과 컬렉션
1
2
3
4
5
6
7
8
9
10
11
12
| # 배열 타입
[string[]]$Directories = @("/home", "/var/www", "/tmp")
# 해시테이블 타입
[hashtable]$Config = @{
enabled = $true
max_size_mb = 10
}
# 제네릭 리스트
[System.Collections.Generic.List[string]]$Findings = @()
$Findings.Add("finding1")
|
리턴 타입
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| function Get-SecurityPatterns {
[CmdletBinding()]
param([object]$Config)
# 리턴 타입은 명시하지 않지만, 문서화 블록에 명시
# .OUTPUTS System.Array
return @(
@{ id = "ssn-us"; type = "PII" },
@{ id = "credit-card"; type = "PCI" }
)
}
# 리턴 타입 명시 (잘 안 쓰임)
[Array] Get-SecurityPatterns {
param([object]$Config)
return @()
}
|
인코딩 설정 (UTF-8 전환)
PowerShell의 기본 인코딩은 시스템 로케일을 따르는데, 한글 Windows는 CP949(Windows-1252)를 사용한다. UTF-8로 전환하는 표준 패턴이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # Console encoding settings
try {
# 1. 콘솔 코드 페이지를 UTF-8(65001)로 변경
$null = cmd /c chcp 65001 2>&1
# 2. 출력 인코딩 설정
$OutputEncoding = [System.Text.Encoding]::UTF8
# 3. 콘솔 입출력 인코딩 설정
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
# 4. 모든 파일 관련 cmdlet의 기본 인코딩을 UTF-8로 설정
$PSDefaultParameterValues['*:Encoding'] = 'utf8'
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
$PSDefaultParameterValues['Set-Content:Encoding'] = 'utf8'
$PSDefaultParameterValues['Add-Content:Encoding'] = 'utf8'
} catch {
# 인코딩 설정 실패 시 무시 (일부 환경에서 권한 문제 발생 가능)
}
|
코드 페이지 (Code Page)
| 코드 페이지 | 인코딩 | 설명 |
|---|
| 949 | CP949 | 한글 Windows 기본 (EUC-KR 확장) |
| 1252 | Windows-1252 | 영문 Windows 기본 |
| 65001 | UTF-8 | 유니코드, 전 세계 문자 지원 |
| 437 | OEM-US | DOS 기본 코드 페이지 |
에러 처리 패턴
1
2
3
4
5
6
7
8
9
| $ErrorActionPreference = "Stop" # 모든 에러를 예외로 처리
try {
$Config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
}
catch {
Write-Host "Failed to load configuration: $_" -ForegroundColor Red
exit 1
}
|
에러 정보 접근
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| try {
Invoke-RestMethod -Uri $url -Method GET
}
catch [System.Net.WebException] {
# 특정 예외 타입 처리
if ($_.Exception.Response) {
$statusCode = $_.Exception.Response.StatusCode.value__
$statusDescription = $_.Exception.Response.StatusDescription
Write-Host "HTTP Error: $statusCode $statusDescription"
}
}
catch {
# 모든 예외 처리
$errorMessage = $_.Exception.Message
$exceptionType = $_.Exception.GetType().FullName
Write-Host "Error: $errorMessage"
Write-Host "Type: $exceptionType"
}
|
실전 팁 (Best Practices)
1. 항상 CmdletBinding 사용
1
2
3
4
5
6
| # ✅ 좋은 예
[CmdletBinding()]
param([string]$Path)
# ❌ 나쁜 예
param([string]$Path)
|
2. 상대 경로는 $PSScriptRoot
1
2
3
4
5
| # ✅ 좋은 예
$ConfigPath = "$PSScriptRoot\config\settings.json"
# ❌ 나쁜 예
$ConfigPath = ".\config\settings.json" # 현재 작업 디렉토리 기준 (위험)
|
3. 출력 버리기는 $null 할당
1
2
3
4
5
| # ✅ 좋은 예 (빠름)
$null = New-Item -Path $dir -Force
# ❌ 나쁜 예 (느림)
New-Item -Path $dir -Force | Out-Null
|
4. 타입은 명시적으로
1
2
3
4
5
6
7
8
9
10
| # ✅ 좋은 예
[CmdletBinding()]
param(
[string]$Name,
[int]$Age,
[switch]$Force
)
# ❌ 나쁜 예
param($Name, $Age, $Force)
|
마치며
PowerShell의 고급 기능을 활용하면 더 강력하고 유지보수하기 쉬운 스크립트를 작성할 수 있습니다.
CmdletBinding을 고급 함수의 기본으로 항상 사용하고, $PSScriptRoot, $PSCmdlet 등 자동 변수를 적극 활용합니다. 출력 버리기는 $null = ... 패턴을 사용하고, 해시테이블(@{})과 실행 블록({})을 구분합니다. 파라미터 타입을 명시하여 안정성을 확보하고, UTF-8 전환 패턴을 표준화합니다.
Java의 어노테이션, Go의 구조체와 마찬가지로, PowerShell도 언어의 특성을 이해하고 활용하면 훨씬 프로페셔널한 코드를 작성할 수 있습니다.
도움이 되셨길 바랍니다! 😀