Write-Host를 그만두다 ── PowerShell의 출력 스트림과 로그 설계

· · PowerShell, Windows, 로그, 운영 개선, 자동화, 스크립트, 설계, 유지보수성

「스크립트는 동작하고 있지만, 실패했을 때 무슨 일이 일어났는지 알 수 없다」 ── 운영에 올린 PowerShell 스크립트에서 가장 많이 받는 상담이 이것입니다. 그리고 원인을 추적해 보면, 대개 같은 구조에 다다릅니다. 처리 상황이 Write-Host로만 표현되어 있어서, 화면을 보고 있지 않았던 야간 실행에서는 아무것도 남아 있지 않다는 것입니다.

PowerShell에는 6가지 출력 스트림이 있으며, 각각 「누구를 향한 정보인가」가 다릅니다. 값을 반환할 것인지, 사람에게 읽힐 것인지, 조사할 때만 필요한 것인지. 이를 의식해서 구분해 쓰면, 같은 스크립트가 대화형 실행에서는 친절하게, 무인 실행에서는 기계가 읽을 수 있게 동작하게 됩니다. 반대로 전부 Write-Host로 흘려보내면, 값으로도 쓸 수 없고 로그에도 남지 않는 정보만 늘어납니다.

이 글에서는 6가지 스트림의 역할, Write-Host의 올바른 위치, 함수의 반환값이 오염되는 구조, 호출 측에서 상세도를 제어하는 방법, 그리고 운영에서 쓸 수 있는 구조화 로그의 형태까지 정리합니다.

1. 먼저 결론

  • PowerShell의 출력 스트림은 6가지입니다. 성공(1)·에러(2)·경고(3)·상세(4)·디버그(5)·정보(6)이며, 각각 번호로 리다이렉트할 수 있습니다. *>는 전체 스트림입니다.1
  • PowerShell 5.0 이후, Write-Host는 정보 스트림에 기록합니다. 이 때문에 6>를 통한 리다이렉트나 -InformationVariable로 포착하는 것이 가능해졌습니다. 그 이전에는 포착도 억제도 할 수 없었습니다.2
  • Write-Host는 「사람에게 보여주는 표시」 전용입니다. 값을 반환하는 용도로는 사용할 수 없습니다. 파이프라인에 넘길 값은 Write-Output(또는 맨 값 출력)입니다.23
  • 함수는 내부에서 출력된 모든 객체를 반환합니다. return의 유무는 관계없습니다. 불필요한 출력은 $null = ...으로 버리는 것이 정석입니다.4
  • 진행 상황은 Write-Progress, 처리 경과는 Write-Verbose입니다. 진행 상황 표시는 리다이렉트할 수 있는 데이터 스트림이 아닙니다.5
  • [CmdletBinding()]을 붙인 함수는 -Verbose -Debug -InformationAction 등의 공통 매개변수를 자동으로 갖게 됩니다. 호출 측이 상세도를 결정할 수 있는 형태로 만드는 것이 정답입니다.67
  • 기본값은 기억해 둘 가치가 있습니다. $VerbosePreference $DebugPreference $InformationPreference는 SilentlyContinue, $WarningPreference $ErrorActionPreference는 Continue입니다.8
  • 나중에 분석하고 싶은 로그는 구조화(한 줄에 하나의 JSON)합니다. 화면의 모습을 그대로 재현하고 싶다면 Start-Transcript를 함께 사용합니다.9

이 글이 전제로 하는 버전과, 5.1에서의 차이

정보 시스템 부서 현장에서는 여전히 Windows PowerShell 5.1이 주류입니다. 이 글의 서술이 어느 버전을 전제로 하는지 먼저 정리해 둡니다.

서술 전제 버전 Windows PowerShell 5.1에서는
6가지 출력 스트림과 번호가 붙은 리다이렉트, *> 5.1에서도 PowerShell 7에서도 동일 그대로 적용됩니다1
Write-Host가 정보 스트림(6번)에 기록한다(6>-InformationVariable로 포착 가능) PowerShell 5.0 이후(3장) 5.1은 5.0 이후이므로 그대로 적용됩니다2
Write-Information-InformationAction / -InformationVariable PowerShell 5.0 이후(4장) 그대로 적용됩니다7
함수가 내부 출력을 모두 반환하는 것, $null = ...을 통한 억제 버전에 의존하지 않습니다(5장) 그대로 적용됩니다4
외부 명령(네이티브 명령)의 2>&1 취급 PowerShell 7.4 이후의 동작으로 설명합니다(6장) 적용되지 않습니다. 외부 명령의 출력을 타입으로 구분하는 방식은 실행하는 버전에서 반드시 확인하세요
Write-Progress-ProgressAction 공통 매개변수로 제어하는 것 PowerShell 7.4 이후5 사용할 수 없습니다. $ProgressPreference로 제어합니다(8장은 이쪽으로 작성했습니다)
글 말미에서 배포하는 샘플 코드 PowerShell 7.6으로 실행하여 검증(Pester 14건)

요컨대, 2장~5장과 7장은 5.1에서도 그대로 사용할 수 있습니다. 버전을 의식해야 하는 곳은 외부 명령의 리다이렉트(6장)와 진행 상황 표시 제어 방법(8장), 이 두 곳뿐입니다.

2. 6가지 스트림과 그 상대

먼저 어느 기록 명령이 어디로 도달하는지를 한 장에 정리합니다.

Write-Output / 맨 값 출력Write-Error / Write-WarningWrite-Verbose / Write-DebugWrite-Information / Write-Host1 성공 스트림2 에러 / 3 경고 / 4 상세5 디버그 / 6 정보후속 처리로 넘어감파이프라인·변수 대입화면에 표시됨기본으로 표시되는지는 환경설정 변수에 따라 다름파일에 남길 수 있음번호가 붙은 리다이렉트·포착용 변수

후속 처리로 넘어가는 것은 성공 스트림뿐입니다. 나머지 5가지는 화면에 표시되거나, 리다이렉트나 -*Variable로 포착하거나 둘 중 하나이며, 파이프라인에는 실리지 않습니다. 왼쪽 분기(어느 명령으로 쓸 것인가)를 잘못 고르면, 값이 전달되지 않는다·로그에 남지 않는다는 형태로 반드시 겉으로 드러납니다. 번호는 3> warnings.log처럼 리다이렉트를 지정할 때 사용합니다(6장).

# 스트림 기록하는 명령 예상 독자 기본 환경설정
1 성공(Success) Write-Output / 맨 값 출력 후속 처리(파이프라인)
2 에러(Error) Write-Error / 스로우 사람 + 모니터링 Continue
3 경고(Warning) Write-Warning 사람 Continue
4 상세(Verbose) Write-Verbose 조사 중인 사람 SilentlyContinue
5 디버그(Debug) Write-Debug 개발자 SilentlyContinue
6 정보(Information) Write-Information / Write-Host 사람 + 기록 SilentlyContinue

정보 스트림의 기본값이 SilentlyContinue인데도 Write-Host가 화면에 표시되는 것은 모순이 아닙니다. Write-Host만이 예외이며, Microsoft Learn은 「$InformationPreference 환경설정 변수와 -InformationAction 공통 매개변수는 Write-Host의 메시지에 영향을 주지 않는다」고 명시하고 있습니다.2Write-Information은 기본으로는 나오지 않지만, Write-Host는 기본으로도 표시되며, 억제할 수 있는 것은 -InformationAction Ignore6>를 통한 리다이렉트뿐입니다.

이 표에서 가장 중요한 것은 첫 번째 행입니다. 성공 스트림은 「사람을 향한 메시지」를 적는 곳이 아닙니다. 여기에 사람을 향한 문자열을 적으면, 그 함수를 |로 연결한 순간 후속 처리에 예상치 못한 문자열이 흘러 들어갑니다.

function Get-KsTargetFile {
    Write-Output "대상을 검색하고 있습니다..."   # 【NG】반환값에 섞여 버림
    Get-ChildItem -Path $path -Filter '*.csv'
}

# 호출 측은 FileInfo 배열을 기대하고 있는데, 맨 앞에 문자열이 들어옴
$files = Get-KsTargetFile
$files[0].FullName    # → 비어 있음(맨 앞이 문자열이기 때문)

올바르게는, 진행 경과 보고는 상세 스트림이나 정보 스트림으로 보냅니다.

function Get-KsTargetFile {
    [CmdletBinding()]
    param([string] $Path)

    Write-Verbose "대상을 검색하고 있습니다: $Path"   # -Verbose 지정 시에만 표시됨
    Get-ChildItem -Path $Path -Filter '*.csv'          # 반환값은 FileInfo뿐
}

3. Write-Host는 「악」인가

한때 「Write-Host는 쓰지 마라」는 주장이 널리 퍼졌지만, 지금의 PowerShell에서는 사정이 달라졌습니다. PowerShell 5.0 이후, Write-Host는 정보 스트림(6번)에 대한 기록으로 구현되어 있으며, 6>로 리다이렉트하거나 -InformationVariable로 포착할 수 있습니다.2 「화면에밖에 낼 수 없고, 나중에 전혀 주워담을 수 없다」는 당시의 비판은 더 이상 해당하지 않습니다. 다만 2장에서 다뤘듯이, 정보 스트림의 기본값이 SilentlyContinue여도 Write-Host의 표시만은 사라지지 않습니다. $InformationPreference-InformationAction의 영향을 받지 않기 때문입니다(유일한 예외는 -InformationAction Ignore로, 이것만은 Write-Host의 출력도 억제합니다). 「포착할 수 있게 되었다」는 것과 「기본으로 화면에 표시된다」는 것은 양립합니다.2

그렇다고는 해도, 쓰임새는 한정됩니다.

Write-Host가 적합한 상황

  • 대화형으로 사용하는 도구에서 색이 들어간 표제나 구분선을 출력하고 싶을 때(-ForegroundColor)
  • 「이 스크립트가 지금부터 무엇을 할 것인가」를 사용자에게 전달할 때
  • 처리의 값이 아니라, 꾸며진 표시 자체가 목적일 때

Write-Host를 사용해서는 안 되는 상황

  • 함수의 반환값으로 값을 넘기고 싶을 때(→ Write-Output)
  • 나중에 분석할 운영 로그를 남기고 싶을 때(→ 구조화 로그, 또는 Write-Information)
  • 호출 측에서 표시·비표시를 전환하고 싶을 때(→ Write-Verbose)

무인 실행 스크립트에는 애초에 표시할 곳이 없습니다. Write-Host만으로 상황을 표현한 스크립트는, 작업 스케줄러에서 실행된 순간 「아무것도 알 수 없는 스크립트」가 됩니다. 이것이 이 글 제목의 의미입니다.

4. 호출 측에 제어를 넘기다 ── [CmdletBinding()]과 공통 매개변수

Write-Verbose의 진가는 표시할지 말지를 호출 측이 결정할 수 있다는 점에 있습니다. 함수에 [CmdletBinding()]을 붙이기만 하면, -Verbose -Debug -WarningAction -InformationAction -ErrorAction 등의 공통 매개변수를 자동으로 쓸 수 있게 됩니다.67

function Invoke-KsImport {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [string] $CsvPath
    )

    Write-Verbose "가져오기 시작: $CsvPath"          # 기본으로는 나오지 않음
    Write-Information "가져오기: $CsvPath" -Tags 'KsImport'   # 기본으로는 나오지 않음(포착은 가능)

    $rows = Import-Csv -Path $CsvPath
    if ($rows.Count -eq 0) {
        Write-Warning "$CsvPath 에 가져올 대상이 없습니다"   # 기본으로 나옴
        return
    }

    Write-Verbose "$($rows.Count) 건을 처리합니다"
    $rows | ForEach-Object { ConvertTo-KsRecord $_ }         # 반환값은 이것뿐
}

# 일반 실행: 경고만 표시되고, 반환값은 레코드
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv'

# 조사 시: 경과도 보고 싶음
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -Verbose

# 정보 스트림만 변수로 포착해서, 나중에 로그 파일로
$records = Invoke-KsImport -CsvPath 'D:\in\orders.csv' -InformationVariable info
$info | ForEach-Object { $_.MessageData } | Add-Content -Path $logPath

자체적인 $LogLevel 변수를 만들어 if문으로 분기하는 구현을 자주 보게 되는데, 표준 체계에 올리는 편이 짧고, 다른 사람에게도 의도가 잘 전달됩니다. -Verbose를 붙이면 상세 내용이 나온다는 것은 PowerShell을 사용하는 모든 사람의 공통된 인식이기 때문입니다.

또한 $VerbosePreference 등의 환경설정 변수는 해당 스코프와 자식 스코프에 적용됩니다.8 함수를 -Verbose를 붙여 호출하면, 그 안에서 호출되는 커맨드릿도 상세 출력을 시작하기 때문에, 예상보다 출력이 늘어나는 경우가 있습니다.

5. 함수의 반환값이 오염된다 ── PowerShell 특유의 함정

PowerShell의 함수는 명시적인 return이 없어도, 내부에서 출력된 모든 객체를 반환합니다.4 이는 강력한 사양인 동시에, 가장 사고가 많이 일어나는 부분이기도 합니다.

function New-KsWorkFolder {
    param([string] $Path)

    New-Item -Path $Path -ItemType Directory   # 【함정】DirectoryInfo가 반환값에 섞임

    $list = [System.Collections.Generic.List[string]]::new()
    $list.Add('log')                            # 【함정2】.Add()는 void이므로 실질적 피해 없음
    $sb = [System.Text.StringBuilder]::new()
    $sb.Append('x')                             # 【함정3】StringBuilder 자신이 반환됨

    return $Path
}

$p = New-KsWorkFolder -Path 'D:\work'   # $p는 3개 요소의 배열이 됨(DirectoryInfo, StringBuilder, string)

대처법은 「불필요한 출력을 버리는」 것입니다. 작성 방법은 3가지가 있지만, $null = ...이 가장 가볍습니다.

$null = New-Item -Path $Path -ItemType Directory   # 권장
New-Item -Path $Path -ItemType Directory | Out-Null # 파이프 만큼 느림
[void] $sb.Append('x')                             # .NET 메서드용으로 자주 쓰임

이 동작은 테스트를 작성하면 단번에 알아챌 수 있습니다. 「반환값의 형태를 고정하는」 테스트의 중요성은 「Pester로 하는 PowerShell 테스트 정비」에서 다루고 있습니다.

6. 리다이렉트와 포착

스트림은 번호로 개별적으로 리다이렉트할 수 있습니다.1

.\Invoke-NightlyExport.ps1 3> warnings.log            # 경고만 별도 파일로
.\Invoke-NightlyExport.ps1 4>&1 | Tee-Object -FilePath run.log   # 상세를 성공 스트림에 합류
.\Invoke-NightlyExport.ps1 *> all.log                 # 전체 스트림을 하나의 파일로
.\Invoke-NightlyExport.ps1 2>&1 | Where-Object { $_ -is [System.Management.Automation.ErrorRecord] }

>는 덮어쓰기, >>는 추가 기록입니다. 다만, 리다이렉트로 합류시키면 타입이 섞인다는 점은 의식해 두세요. 위 예시처럼 PowerShell 스크립트나 함수의 에러 스트림을 합류시킨 경우, 그 요소는 ErrorRecord 그대로이므로 위처럼 타입으로 구분할 수 있습니다.

한편, 외부 프로그램(네이티브 명령)의 2>&1은 사정이 다릅니다. PowerShell 7.4 이후는 리다이렉트 출력이 바이트 스트림으로 취급되어, 합류 후에는 문자열이 되기 때문에 ErrorRecord로 구분하는 것이 통하지 않습니다. 외부 명령의 stdout/stderr를 구분하고 싶은 경우에는, 합류시키지 말고 따로따로 받으세요(「PowerShell에서 외부 exe를 올바르게 호출하기」).

작성한 대로 나뉘는지는 직접 한 번 시험해 보는 것이 확실합니다. 성공 스트림과 경고 스트림을 둘 다 출력하는 짧은 코드로 확인할 수 있습니다.

# 경고만 별도 파일로. 화면에는 '데이터'만 남음
& { Write-Output '데이터'; Write-Warning '주의사항' } 3> warnings.log
Get-Content warnings.log     # 경고 메시지가 들어 있음. '데이터'는 들어 있지 않음

# 전체 스트림을 하나의 파일로
& { Write-Output '데이터'; Write-Warning '주의사항'; Write-Verbose '상세' -Verbose } *> all.log
Get-Content all.log          # 3개의 출력이 함께 들어감

3>를 붙였는데도 warnings.log가 비어 있다면, 그 메시지는 경고 스트림에 기록되지 않은 것입니다(Write-Host로 기록하고 있다면 6>입니다). 「로그에 나왔어야 하는 것이 안 나온다」의 원인 분리는 이 두 줄부터 시작하는 것이 가장 빠릅니다.

또한 *>는 간편하지만, 한데 묶어 버리면 나중에 스트림별로 다시 구분하기가 어려워집니다. 기계적으로 집계하고 싶다면, 스트림별로 다른 파일에 출력하거나, 다음 장의 구조화 로그로 만드세요.

실행 증거를 통째로 남기고 싶다면 Start-Transcript가 간편합니다. 세션의 명령과 출력을 텍스트로 기록하므로, 「그때 화면에 무엇이 나와 있었는지」를 나중에 재현할 수 있습니다.9

Start-Transcript -Path "C:\Logs\export_$(Get-Date -f yyyyMMdd_HHmmss).log" -Append
try   { Invoke-KsExport }
finally { Stop-Transcript }

7. 나중에 분석할 수 있는 로그로 만들다 ── 구조화 로그

사람이 읽는 로그와, 기계가 집계하는 로그는 별개입니다. 「지난달 이 에러가 몇 건 나왔는가」를 알고 싶을 때, 자유 서식의 텍스트는 grep과의 체력 싸움이 됩니다. 한 줄에 하나의 JSON(JSON Lines)으로 적어 두면, 집계는 PowerShell만으로 완결됩니다.

function Write-KsLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)] [ValidateSet('INFO','WARN','ERROR')] [string] $Level,
        [Parameter(Mandatory)] [string] $Message,
        [hashtable] $Data,
        [string] $Path = $script:KsLogPath
    )

    $entry = [ordered]@{
        ts      = (Get-Date).ToString('o')    # ISO 8601. 정렬도 대조도 쉬움
        level   = $Level
        message = $Message
        script  = $MyInvocation.ScriptName
        host    = $env:COMPUTERNAME
        user    = $env:USERNAME
    }
    # 추가 정보는 data 아래에 넣습니다. 최상위에 섞으면, 호출 측이
    # level이나 user 같은 키를 넘겼을 때 기본 항목을 덮어써 버립니다
    if ($Data) { $entry['data'] = $Data }

    # -Compress로 한 줄로 만듦. 추가 기록은 Add-Content(UTF-8)
    $entry | ConvertTo-Json -Compress -Depth 5 | Add-Content -Path $Path -Encoding utf8

    # 사람을 향한 표시는 표준 체계에 올림(화면에는 내지만 값은 반환하지 않음)
    switch ($Level) {
        # -ErrorAction은 지정하지 않습니다. 여기서 고정하면, 호출 측이
        # -ErrorAction Stop으로 종료 에러로 만들고 싶어도 할 수 없게 됩니다
        'ERROR' { Write-Error   $Message }
        'WARN'  { Write-Warning $Message }
        default { Write-Verbose $Message }
    }
}

# 사용 예
Write-KsLog -Level INFO -Message '가져오기 완료' -Data @{ rows = 1250; file = 'orders.csv'; ms = 4210 }
# → {"ts":"...","level":"INFO","message":"가져오기 완료","script":"...","host":"...","user":"...",
#    "data":{"rows":1250,"file":"orders.csv","ms":4210}}

집계는 이렇게 됩니다.

Get-Content 'C:\Logs\ks.log' |
    ForEach-Object { $_ | ConvertFrom-Json } |
    Where-Object { $_.level -eq 'ERROR' -and [datetime]$_.ts -ge (Get-Date).AddDays(-30) } |
    Group-Object message | Sort-Object Count -Descending | Select-Object Count, Name

Windows의 표준적인 로그 기반(이벤트 로그·ETW)에 올리는 선택지도 있습니다. 모니터링 도구와의 연계나, 여러 대에서의 수집을 고려한다면 그쪽이 유리합니다. 설계 비교는 「Windows 이벤트 로그·ETW와 구조화 로그」에, 로그 파일의 세대 관리는 「PowerShell 스크립트 응용 ── 로그 조사·아카이브·리포트화」에 정리했습니다.

8. 진행 상황 표시의 취급

Write-Progress는 호스트의 진행 상황 표시 기능을 사용하는 것으로, 리다이렉트 가능한 데이터 스트림이 아닙니다.5 즉 로그에는 남길 수 없습니다. 무인 실행에서는 표시할 곳이 없을 뿐 아니라, 환경에 따라서는 진행 상황 갱신의 비용이 무시할 수 없는 경우도 있습니다.

# 무인 실행 스크립트의 앞부분에서 진행 상황 표시를 멈춤
$ProgressPreference = 'SilentlyContinue'

진행 상황을 운영 로그에 남기고 싶다면, 마디마다만 상세 스트림에 적는 것이 실용적입니다.

$i = 0
foreach ($row in $rows) {
    $i++
    if ($i % 100 -eq 0) { Write-Verbose "$i / $($rows.Count) 건 완료" }
    ...
}

9. 실무의 정석(판단표)

내고 싶은 정보 사용할 것 이유
후속 처리에 넘길 값 Write-Output / 맨 값 출력 성공 스트림은 데이터 전용3
처리 경과(조사 시에만 보고 싶음) Write-Verbose 호출 측이 -Verbose로 제어7
운영으로서 기록하고 싶은 사건 Write-Information + 구조화 로그 -InformationVariable로 포착 가능2
대화형 도구의 꾸민 표시 Write-Host 정보 스트림을 경유하므로 포착도 가능2
예상 범위지만 주의해야 할 사상 Write-Warning 기본으로 표시되고, -WarningVariable로 포착8
실패 Write-Error / throw 에러 처리는 전용 글을 참조
개발 중인 내부 상태 Write-Debug -Debug를 붙였을 때만7
진행 상황 Write-Progress(대화형 실행 시만) 로그에는 남지 않음. 무인 실행에서는 멈춤5
실행 증거를 통째로 Start-Transcript 자체 로그의 보험으로 함께 사용9

10. 정리

  • PowerShell의 출력은 6가지 스트림으로 나뉩니다. 성공 스트림은 데이터 전용이며, 사람을 향한 메시지를 섞으면 반환값이 망가집니다.
  • PowerShell 5.0 이후의 Write-Host는 정보 스트림에 기록하므로 포착이 가능하지만, 값을 반환하는 용도와 운영 로그 용도에는 사용할 수 없습니다.
  • 함수는 내부에서 출력된 모든 것을 반환합니다. 불필요한 출력은 $null = ...으로 버리는 것이 정석입니다.
  • [CmdletBinding()]을 붙여 Write-Verbose / Write-Information을 사용하면, 상세도의 제어를 호출 측에 넘길 수 있습니다. 자체적인 로그 레벨 변수보다 짧고, 의도가 잘 전달됩니다.
  • 나중에 집계할 로그는 한 줄에 하나의 JSON으로 된 구조화 로그로 만듭니다. 화면의 재현이 목적이라면 Start-Transcript를 함께 사용합니다.
  • 진행 상황 표시는 데이터 스트림이 아니므로 로그에 남지 않습니다. 무인 실행에서는 $ProgressPreference = 'SilentlyContinue'로 멈춰 버리는 것이 실용적입니다.

샘플 코드 다운로드

이 글에서 다룬 코드는 그대로 실행할 수 있는 형태로 정리하여 배포하고 있습니다. 한 줄에 하나의 JSON으로 된 구조화 로그와, 반환값을 오염시키지 않는 함수 작성법이 들어 있습니다.

샘플 코드 다운로드(zip)

이 글의 샘플은 PowerShell 7.6에서 실제로 실행하여 검증했습니다(Pester 14건). zip에 포함된 Invoke-SampleTests.ps1을 실행하면, 여러분의 환경에서도 같은 검증을 재현할 수 있습니다.

# 구문 분석 + 정적 분석 + Pester 테스트
./Invoke-SampleTests.ps1

설정값(경로, 서버명, 테넌트 ID 등)은 예시입니다. 그대로 운영 환경에서 실행하지 말고, 자사 환경에 맞게 바꿔서 사용하세요.

관련 글

관련 상담 영역

합동회사 코무라소프트에서는 운영 스크립트의 로그 설계 재검토, 「실패한 이유를 알 수 없는」 상태의 해소, 모니터링·집계로 이어지는 로그 기반 정비를 다루고 있습니다.

참고 링크

  1. Microsoft Learn, about_Redirection. PowerShell이 성공·에러·경고·상세·디버그·정보의 각 스트림을 가지며 각각 번호로 식별된다는 점, > >>를 통한 파일로의 리다이렉트, n>&1을 통한 다른 스트림으로의 합류, *>를 통한 전체 스트림의 리다이렉트에 대해.  2 3

  2. Microsoft Learn, Write-Host. PowerShell 5.0 이후의 Write-Host가 Write-Information의 래퍼로서 정보 스트림에 출력하게 되어 6>를 통한 리다이렉트가 가능해진 점, $InformationPreference 환경설정 변수와 -InformationAction 공통 매개변수가 Write-Host의 메시지에는 영향을 주지 않으며(예외는 -InformationAction Ignore), 그 때문에 기본으로도 화면에 표시된다는 점, -ForegroundColor / -BackgroundColor를 통한 꾸미기, 출력이 파이프라인에 전달되지 않는다는 점에 대해. 관련하여 Write-Information은 정보 스트림에 대한 명시적인 기록과 -Tags를 통한 분류에 대해.  2 3 4 5 6 7 8

  3. Microsoft Learn, Write-Output. 객체를 성공 스트림(파이프라인)으로 보내는 것, 명시적으로 호출하지 않아도 식의 결과가 동일하게 출력되는 것에 대해.  2

  4. Microsoft Learn, about_Return. PowerShell의 함수가 return의 유무와 관계없이, 함수 안에서 출력된 모든 객체를 호출한 쪽으로 반환한다는 점, return은 값을 반환하면서 현재 스코프를 빠져나가기 위한 구문이라는 점에 대해.  2 3

  5. Microsoft Learn, Write-Progress. 명령의 진행 상황을 호스트의 진행 상황 표시로 출력하는 것, $ProgressPreference로 표시를 제어할 수 있는 것, PowerShell 7.4 이후는 -ProgressAction 공통 매개변수로도 제어할 수 있는 것에 대해.  2 3 4

  6. Microsoft Learn, about_Functions_CmdletBindingAttribute. [CmdletBinding()] 속성을 붙인 고급 함수가 컴파일된 커맨드릿과 마찬가지로 동작하며, 공통 매개변수가 자동으로 사용 가능해진다는 점에 대해.  2

  7. Microsoft Learn, about_CommonParameters. -Verbose / -Debug / -WarningAction / -InformationAction / -ErrorAction 및 대응하는 -*Variable 매개변수의 동작, 환경설정 변수와의 관계에 대해.  2 3 4 5

  8. Microsoft Learn, about_Preference_Variables. $VerbosePreference·$DebugPreference·$InformationPreference의 기본값이 SilentlyContinue, $WarningPreference와 $ErrorActionPreference의 기본값이 Continue라는 점, $ProgressPreference를 통한 진행 상황 표시 제어, 이들이 스코프와 자식 스코프에 적용된다는 점에 대해.  2 3

  9. Microsoft Learn, Start-Transcript. 세션의 명령과 콘솔 출력을 텍스트 파일로 기록하는 것, -Append를 통한 추가 기록, Stop-Transcript를 통한 정지에 대해.  2 3

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

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

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

자주 묻는 질문

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

Write-Host는 사용해서는 안 되나요?
금지가 아니라 용도가 한정되어 있다고 이해하는 것이 정확합니다. PowerShell 5.0 이후의 Write-Host는 정보 스트림(6번)에 기록하기 때문에, 6>를 통한 리다이렉트나 -InformationVariable로 포착하는 것이 가능해졌습니다. 다만 기본적으로 항상 화면에 표시된다는 전제의 명령이므로, 값을 파이프라인에 흘려보내고 싶을 때는 사용할 수 없습니다. 대화형 도구에서 사람이 읽도록 꾸민 표시라면 Write-Host, 처리의 진행 상황이나 보충 정보라면 Write-Verbose나 Write-Information, 후속 처리에 넘길 값이라면 Write-Output(또는 맨 값 출력)이라는 식으로 구분해서 사용합니다.
함수의 반환값에 의도하지 않은 값이 섞여 버립니다.
PowerShell의 함수는 명시적인 return이 없어도, 함수 안에서 출력된 모든 객체를 반환하기 때문입니다. New-Item이나 StringBuilder의 .Append()처럼 값을 반환하는 명령이나 메서드를 그냥 호출만 하면, 그 반환값이 성공 스트림으로 흘러 들어가 호출한 쪽에 전달됩니다. 반대로 List[T]의 .Add()처럼 반환값이 void인 메서드는 아무것도 출력하지 않으므로 억제할 필요가 없습니다. 대처법은 불필요한 출력을 $null = ...로 버리거나, | Out-Null을 붙이거나, [void]로 캐스트하는 것입니다. 성능 면에서는 $null = ...이 가장 가벼운 방식입니다.
스크립트의 상세 로그를 실행 시점에 전환할 수 있게 하고 싶습니다.
처리의 진행 경과를 Write-Verbose로 기록하고, 함수에 [CmdletBinding()]을 붙이세요. 그것만으로 호출 측이 -Verbose를 지정했을 때만 표시되도록 할 수 있습니다. 상시 출력하고 싶다면 $VerbosePreference = 'Continue'를 스크립트 앞부분에서 설정합니다. 마찬가지로 Write-Debug는 -Debug, Write-Warning은 -WarningAction으로 호출 측에서 제어할 수 있습니다. 자체적인 로그 레벨 변수를 만드는 것보다, PowerShell 표준 체계에 올리는 편이 다른 사람이 읽었을 때 의도가 잘 전달됩니다.
상세 로그나 경고를 포함해서 통째로 파일에 남기려면 어떻게 하면 되나요?
용도에 따라 3가지 방법이 있습니다. 단순히 모든 스트림을 파일로 떨어뜨리려면 *>로 리다이렉트합니다. 실행의 증거로서 화면에 나온 것을 통째로 남기고 싶다면 Start-Transcript가 간편합니다. 프로그램으로 나중에 분석하고 싶다면, 한 줄에 하나의 JSON으로 된 구조화 로그를 자체 로그 함수로 기록하는 것이 확실하며, 이 경우에도 보험 삼아 트랜스크립트를 함께 사용할 가치가 있습니다.
Write-Progress의 표시는 로그 파일에 남길 수 있나요?
남길 수 없습니다. 진행 상황 표시는 호스트의 표시 기능이며, 리다이렉트할 수 있는 데이터 스트림과는 별개로 취급되기 때문입니다. 무인 실행에서는 표시할 곳이 없으므로, 진행 상황은 로그에 남길 정보와 별개로 생각하십시오. 무인 실행에서는 $ProgressPreference = 'SilentlyContinue'로 진행 상황 표시 자체를 멈추면, 환경에 따라 눈에 띄게 빨라지는 경우도 있습니다. 진행 상황을 로그에 남기고 싶다면, Write-Verbose로 「100건 중 50건 완료」와 같은 마디만 기록하는 편이 실용적입니다.

저자 프로필

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

Go Komura

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

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

블로그 목록으로 돌아가기