윈도우 작업 스케줄러에 등록해 매일 돌리는 PowerShell 스크립트가 있습니다. 어느 날 보니 실행 로그의 한글이 전부 깨져 있었고, 스크립트에 넘긴 옵션 하나는 경고 한 줄만 남기고 사라져 있었습니다. 오류 메시지는 없었습니다.
원인을 찾으려고 같은 조건을 만들어 재현해 보니 더 곤란한 경우도 나왔습니다. 스크립트가 아무 일도 하지 않았는데 작업 스케줄러에는 0x0(성공)으로 남는 경우입니다.
원인은 세 가지였습니다.
- PowerShell 5.1이 BOM 없는 스크립트 파일을 한국어 코드 페이지(CP949)로 읽는다
- node 같은 외부 프로그램의 출력도 CP949로 해석한다
- PowerShell에서
npm은 npm.ps1 로 실행되고, 이때--가 사라진다
세 가지 모두 오류 메시지 없이 틀어집니다. 작업 스케줄러에서 하나씩 재현한 결과와 해결 방법을 정리합니다.

테스트 환경
- Windows 10 Pro 22H2 (빌드 19045), 시스템 ANSI 코드 페이지 949
- Windows PowerShell 5.1.19041.6456
- 작업 스케줄러 동작:
powershell.exe -NoProfile -File 스크립트.ps1, 옵션은 기본값인 「사용자가 로그온할 때만 실행」 - Node.js v22.15.0 (설치 폴더에 딸린 npm 10.9.2), 전역으로 올린 npm 11.10.0
코드 페이지는 아래 명령으로 확인할 수 있습니다.
(Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage').ACP
# 949
원인 1. BOM 없는 스크립트는 한글이 다른 글자로 읽힌다
재현
경로에 한글 폴더가 들어간 스크립트를 준비했습니다.
$path = "C:\...\보고서\data.txt"
if (Test-Path -LiteralPath $path) { Write-Output "found"; exit 0 }
Write-Output "not found"
exit 1
파일은 분명히 그 자리에 있습니다. 내용은 똑같고 저장 방식만 다른 두 파일을 작업 스케줄러에 각각 등록해 실행한 뒤, Get-ScheduledTaskInfo 로 결과를 읽었습니다.
path-nobom.ps1 (UTF-8, BOM 없음) → LastTaskResult = 1 마지막 실행 결과 0x1
path-bom.ps1 (UTF-8, BOM 있음) → LastTaskResult = 0 마지막 실행 결과 0x0
BOM 없이 저장한 쪽은 파일을 못 찾고 스크립트의 exit 1 로 끝났습니다. 문법 오류도 경고도 없었습니다.
더 나쁜 경우: 실패했는데 0x0
위 스크립트는 실패하면 exit 1 을 하도록 짜 두어서 0x1이라도 남았습니다. 실패 처리가 없는 짧은 스크립트라면 어떨까요?
$path = "C:\...\보고서\data.txt"
Copy-Item -LiteralPath $path -Destination "$PSScriptRoot\copied.txt"
BOM 없이 저장해 작업 스케줄러로 실행한 결과입니다.
LastTaskResult = 0 (마지막 실행 결과 0x0)
copied.txt 있음? False
파일은 복사되지 않았는데 작업 스케줄러에는 성공으로 남습니다.
같은 스크립트를 콘솔에서 직접 실행해 봐도 오류가 한 줄도 나오지 않습니다. 왜 오류조차 없는지 읽힌 경로 문자열을 뜯어 봤습니다.
원래 경로 끝 : 보고서\data.txt
읽힌 경로 끝 : 蹂닿퀬??data.txt
보고서\ 네 글자가 蹂닿퀬?? 다섯 글자로 바뀌었습니다. 폴더를 나누던 \ 가 사라지고 그 자리에 ? 가 들어간 것입니다. 그리고 PowerShell 5.1의 Copy-Item 은 -LiteralPath 로 받은 경로에 ? 가 있으면 오류를 내지 않고 아무것도 하지 않았습니다. 한글이 없는 스크립트로 따로 비교해 봤습니다.
없는 경로 C:\no-such-dir\data.txt → "Cannot find path" 오류, 끝까지 실행, 종료 코드 0
같은 경로 + $ErrorActionPreference = "Stop" → 같은 오류에서 멈춤, 종료 코드 1
? 가 든 경로 C:\no-such-dir\da?ta.txt + "Stop" → 오류 없음, 끝까지 실행, 종료 코드 0
평소라면 $ErrorActionPreference = "Stop" 으로 잡을 수 있는 실패가, 한글이 깨지면 오류 자체가 사라져 버립니다.
원인: PowerShell 5.1은 BOM 없는 파일을 ANSI 코드 페이지로 읽는다
Microsoft 문서 about_Character_Encoding (PowerShell 5.1)에 그대로 적혀 있습니다.
If you need to use non-Ascii characters in your scripts, save them as UTF-8 with BOM. Without the BOM, Windows PowerShell misinterprets your script as being encoded in the legacy "ANSI" codepage.
스크립트에 ASCII가 아닌 문자를 쓰려면 BOM이 있는 UTF-8로 저장하라는 내용입니다. BOM이 없으면 레거시 "ANSI" 코드 페이지로 잘못 해석한다고 합니다. 한국어 윈도우의 ANSI 코드 페이지가 앞에서 확인한 949입니다.
한글 두 글자로 확인해 봤습니다. 스크립트 안에 "한글" 을 넣고, 읽힌 문자열의 길이와 문자 코드를 출력했습니다.
BOM 없음 → len=3 codes=63,50323,63
BOM 있음 → len=2 codes=54620,44544
BOM이 있으면 54620(한), 44544(글) 두 글자로 제대로 읽힙니다. BOM이 없으면 ?쒓? 라는 세 글자짜리 다른 문자열이 됩니다. 따옴표가 깨지지 않는 한 문법상 문제가 없으니 PowerShell은 아무 말도 하지 않습니다. 경로 문자열이 이렇게 바뀌었으니 파일을 찾을 수 없었던 것입니다.
VS Code로 스크립트를 만들었다면 특히 이 경우에 해당합니다. VS Code와 PowerShell의 파일 인코딩 문서에 "VS Code's default encoding is UTF-8 without BOM." 이라고 나옵니다.
해결: 스크립트를 UTF-8 BOM으로 저장
편집기에서 인코딩을 "UTF-8 with BOM"으로 골라 다시 저장하면 됩니다. VS Code라면 설정(settings.json)에 PowerShell 파일만 BOM으로 저장하도록 적어 둘 수 있습니다.
"[powershell]": {
"files.encoding": "utf8bom"
}
이미 만든 파일이 많으면 PowerShell로 바꿀 수도 있습니다.
$f = "C:\scripts\job.ps1"
$text = [IO.File]::ReadAllText($f, [Text.Encoding]::UTF8)
[IO.File]::WriteAllText($f, $text, (New-Object System.Text.UTF8Encoding $true))
UTF8Encoding $true 의 $true 가 "BOM을 붙인다"는 뜻입니다. 저장한 뒤 파일 앞 3바이트가 EF BB BF 면 BOM이 붙은 것입니다.
[IO.File]::ReadAllBytes("C:\scripts\job.ps1")[0..2] | ForEach-Object { '{0:X2}' -f $_ }
PowerShell 7(
pwsh.exe)은 VS Code 인코딩 문서에 "In PowerShell 6+, the default encoding is UTF-8 without BOM on all platforms." 라고 되어 있어 BOM 없는 파일도 UTF-8로 읽을 것으로 보입니다. 다만 이번 글에서 PowerShell 7은 직접 테스트하지 않았습니다. 그리고 작업 스케줄러에powershell.exe라고 적으면 PowerShell 7을 설치했어도 5.1이 실행됩니다.
원인 2. 외부 프로그램 출력이 CP949로 해석된다
재현
스크립트 파일을 BOM으로 고친 뒤에도 로그의 한글이 깨지는 곳이 남았습니다. 스크립트 안에서 node 같은 외부 프로그램을 실행하고 그 출력을 받아 로그에 적는 부분이었습니다.
node가 한글 을 UTF-8 바이트(ED 95 9C EA B8 80)로 내보내게 하고, PowerShell이 받은 문자열을 확인했습니다. 역시 작업 스케줄러로 실행했습니다.
$o = & node -e "process.stdout.write(Buffer.from([237,149,156,234,184,128,10]))"
기본 설정 → OutputEncoding=ks_c_5601-1987 len=3 codes=63,50323,63
UTF-8로 바꾼 뒤 → OutputEncoding=utf-8 len=2 codes=54620,44544
원인 1과 똑같은 ?쒓? 가 나옵니다.
원인: [Console]::OutputEncoding 기본값이 CP949
PowerShell은 외부 프로그램이 내보낸 바이트를 [Console]::OutputEncoding 에 맞춰 문자열로 바꿉니다. 한국어 윈도우에서 기본값은 ks_c_5601-1987(CP949)입니다. node는 UTF-8로 보냈는데 CP949로 읽으니 글자가 틀어집니다.
이 동작을 한 문장으로 적어 둔 Microsoft 공식 문서는 찾지 못했습니다. 위 재현 결과를 근거로 적었습니다.
이름이 비슷한 $OutputEncoding 은 방향이 반대입니다. about_Preference_Variables 문서의 설명입니다.
Determines the character encoding method that PowerShell uses when piping data into native applications.
PowerShell이 외부 프로그램에게 파이프로 넘길 때 쓰는 인코딩이라는 뜻입니다. 외부 프로그램에게서 받는 쪽은 [Console]::OutputEncoding 입니다.
실제로 작업 스케줄러에서 $OutputEncoding 만 UTF-8로 바꾸고 같은 테스트를 해 보면 여전히 깨집니다.
$OutputEncoding 만 UTF-8 → Console=ks_c_5601-1987 OutputEncoding=utf-8 len=3 codes=63,50323,63
해결: 스크립트 맨 위에서 출력 인코딩을 UTF-8로
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch { }
콘솔이 없는 환경에서 실행될 때를 대비해 try 로 감쌌습니다.
로그를 파일로 남길 때도 인코딩을 적어 두는 게 좋습니다. about_Character_Encoding 문서에 따르면 Windows PowerShell 5.1에서 Out-File 과 >, >> 리디렉션은 UTF-16LE 파일을 만듭니다. 다른 도구로 로그를 읽을 생각이라면 인코딩을 명시하세요.
"작업 시작" | Out-File -FilePath C:\logs\job.log -Append -Encoding utf8
이번 테스트는 「사용자가 로그온할 때만 실행」 옵션으로 했습니다. 「사용자의 로그온 여부에 관계없이 실행」으로 등록한 작업은 따로 확인하지 않았습니다.
원인 3. npm run 에 넘긴 옵션이 사라진다
재현
스크립트에서 npm run 작업이름 -- --from abc 처럼 옵션을 넘겼는데 실제로는 전달되지 않았습니다. 넘어온 인자만 출력하는 show 스크립트로 확인했습니다.
> npm run show -- --from abc
npm warn "abc" is being parsed as a normal command line argument.
npm warn Unknown cli config "--from". This will stop working in the next major version of npm.
["abc"]
> npm.cmd run show -- --from abc
["--from","abc"]
> npm run show --% -- --from abc
npm warn Unknown cli config "--%". ...
[]
npm 으로 부르면 --from 이 사라지고 abc 만 남습니다. npm.cmd 로 부르면 제대로 넘어갑니다. 인자를 그대로 넘기라는 --% 를 붙여 봤더니 오히려 전부 사라졌습니다.
원인: PowerShell에서 npm 은 npm.ps1 로 실행된다
> Get-Command npm -All | ForEach-Object { $_.Source }
C:\Program Files\nodejs\npm.ps1
C:\Program Files\nodejs\npm.cmd
C:\Program Files\nodejs\npm
PowerShell에서 npm 이라고 치면 같은 폴더의 npm.cmd 가 아니라 npm.ps1 이 실행됩니다.
그리고 -- 는 PowerShell 자신에게도 의미가 있는 기호입니다. about_Parsing 문서의 설명입니다.
The end-of-parameters token (
--) indicates that all arguments following it are to be passed in their actual form as though double quotes were placed around them. (…) This behavior only applies to PowerShell commands. If you use the--token when calling an external command, the--string is passed as an argument to that command.
npm.cmd 같은 외부 프로그램에는 -- 가 그대로 넘어갑니다. 하지만 npm.ps1 은 PowerShell 스크립트라서, PowerShell이 -- 를 "여기부터는 매개변수가 아니다"라는 표시로 쓰고 지워 버립니다. Node 설치 폴더의 npm.ps1 은 남은 인자($args)를 그대로 node에 넘깁니다.
& $NODE_EXE $NPM_CLI_JS $args
그래서 npm이 받는 명령은 npm run show --from abc 가 되고, npm은 --from 을 자기 옵션으로 해석합니다. 경고에 나온 Unknown cli config "--from" 이 그 흔적입니다.
npm을 최신으로 올려도 그대로인 이유
이 문제는 npm 쪽에서 고쳐졌습니다. npm 11.4.0(2025년 5월)과 10.9.3(2025년 6월)부터 npm.ps1 이 원래 입력한 명령줄을 다시 읽어 -- 를 살립니다(PR #8278). 그런데 이 PC는 npm이 11.10.0인데도 증상이 그대로였습니다.
찾아보니 npm.ps1 이 세 군데 있었습니다.
| 위치 | 어떤 파일인가 | -- |
|---|---|---|
C:\Program Files\nodejs\npm.ps1 |
Node 22.15.0 설치 때 들어온 파일 (npm 10.9.2 시절 코드) | 사라짐 |
%APPDATA%\npm\npm.ps1 |
전역 npm 설치 때 만들어진 연결 파일 | 사라짐 |
%APPDATA%\npm\node_modules\npm\bin\npm.ps1 |
npm 11.10.0 패키지 안의 고쳐진 파일 | 제자리에서는 실행되지 않음 |
PowerShell이 부르는 건 첫 번째 파일입니다. 이 파일은 코드가 옛날 그대로이고, 실제 실행은 전역으로 올린 npm 11.10.0에 넘깁니다. 그래서 npm -v 는 11.10.0인데 -- 는 여전히 사라집니다.
세 번째의 고쳐진 파일을 Node 설치 폴더와 같은 구조로 옮겨 실행해 보니 제대로 넘어갔습니다.
> npm.ps1 run show -- --from abc # npm 11.10.0 의 고쳐진 npm.ps1
["--from","abc"]
전역으로 npm을 올려도 Node 설치 폴더의 npm.ps1 은 그대로였습니다. 이 파일까지 바꾸려면 고쳐진 npm이 들어 있는 Node 설치 파일로 다시 설치해야 합니다.
해결: npm.cmd 로 부르기
Node나 npm 버전과 상관없이 확실한 방법은 npm.cmd 를 직접 쓰는 것입니다.
npm.cmd run show -- --from abc
# 또는 스크립트를 node로 직접 실행
node .\show.js --from abc
작업 스케줄러에 넣는 스크립트라면 npm 대신 npm.cmd 로 적어 두는 편이 안전합니다. 나중에 Node를 다시 설치하거나 다른 PC로 옮겨도 이 부분은 동작이 달라지지 않습니다.
작업 스케줄러 「마지막 실행 결과」 읽는 법
0x1을 "잘못된 함수"나 "권한 문제"로 설명하는 글도 있습니다. 하지만 PowerShell 스크립트를 등록한 작업이라면 결과 코드는 powershell.exe 의 종료 코드입니다. exit 3 한 줄짜리 스크립트를 등록해 확인했습니다.
exit3.ps1 → LastTaskResult = 3 (0x3)
path-nobom.ps1 → LastTaskResult = 1 (0x1) 스크립트의 exit 1
nobom-noexit.ps1 → LastTaskResult = 0 (0x0) 복사가 안 됐지만 오류도 exit 도 없음
"잘못된 함수"는 윈도우 시스템 오류 코드 1번(ERROR_INVALID_FUNCTION)의 이름과 숫자가 같아서 생긴 오해로 보입니다.
PowerShell 문서(about_PowerShell_exe)의 -File 설명에도 이렇게 나옵니다.
When the script file terminates with an
exitcommand, the process exit code is set to the numeric argument used with theexitcommand.
작업 스케줄러 자체의 상태 코드는 따로 있습니다(Task Scheduler Error and Success Constants).
| 코드 | 뜻 |
|---|---|
| 0x41300 | 다음 예약 시간에 실행할 준비가 됨 |
| 0x41301 | 지금 실행 중 |
| 0x41303 | 아직 한 번도 실행되지 않음 |
| 0x41306 | 마지막 실행을 사용자가 종료함 |
실패를 결과 코드로 드러내기
작업 스케줄러에서 실패가 보이려면 스크립트가 실패했을 때 0이 아닌 종료 코드로 끝나야 합니다.
1. 스크립트 맨 위에 $ErrorActionPreference = 'Stop'
명령이 오류를 내면 그 자리에서 멈추고 종료 코드 1로 끝납니다(위 비교의 두 번째 줄).
2. 입력 파일은 먼저 확인하고 직접 끝내기
원인 1처럼 오류 자체가 나지 않는 경우는 Stop 으로 못 잡습니다. 꼭 있어야 하는 파일은 미리 확인하세요.
$ErrorActionPreference = 'Stop'
$path = "C:\...\보고서\data.txt"
if (-not (Test-Path -LiteralPath $path)) {
Write-Output "입력 파일 없음: $path"
exit 2
}
Copy-Item -LiteralPath $path -Destination "D:\backup\data.txt"
종료 코드를 2처럼 따로 정해 두면 작업 스케줄러의 0x2만 보고도 어디서 멈췄는지 알 수 있습니다. (이 스크립트도 한글이 있으니 UTF-8 BOM으로 저장해야 합니다.)
3. 외부 프로그램의 결과는 넘겨주기
PowerShell 5.1은 Stop 을 켜 둬도, node 같은 외부 프로그램이 0이 아닌 코드로 끝나는 것을 오류로 보지 않습니다.
Stop + node -e "process.exit(5)" → 끝까지 실행, 종료 코드 0
위에 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } 추가 → 종료 코드 5
그래서 결과를 직접 확인해 넘겨줘야 합니다.
node .\job.js
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
정리
| 증상 | 원인 | 해결 |
|---|---|---|
| 한글 경로를 못 찾음 (0x1 또는 0x0) | BOM 없는 스크립트를 PowerShell 5.1이 CP949로 읽음 | 스크립트를 UTF-8 BOM으로 저장 |
| 외부 프로그램 출력의 한글이 깨짐 | [Console]::OutputEncoding 기본값이 CP949 |
스크립트 맨 위에서 UTF-8로 설정 |
npm run … -- --옵션 이 사라짐 |
Node 설치 폴더의 옛 npm.ps1 이 -- 를 지움 |
npm.cmd 로 호출 |
| 실패했는데 0x0 | 오류가 나도 끝까지 실행되거나, 깨진 경로라 오류조차 안 남 | Stop + 입력 파일 확인 후 exit |
작업 스케줄러에서 "실행은 됐는데 결과가 이상하다" 싶으면 이 순서로 확인해 보세요.
- 스크립트 파일 앞 3바이트가
EF BB BF인가 - 외부 프로그램 출력을 받기 전에
[Console]::OutputEncoding을 UTF-8로 바꿨나 npm이 아니라npm.cmd로 불렀나- 실패했을 때 0이 아닌 종료 코드로 끝나나
참고한 문서
- about_Character_Encoding (PowerShell 5.1)
- Understanding file encoding in VS Code and PowerShell
- about_Preference_Variables (PowerShell 5.1)
- about_Parsing (PowerShell 5.1)
- about_PowerShell_exe (PowerShell 5.1)
- Task Scheduler Error and Success Constants
- npm/cli PR #8278 — fix(powershell): use Invoke-Expression to pass args
'윈도우꿀팁' 카테고리의 다른 글
| 윈도우10 꿀팁 - 파일탐색기 즐겨찾기 폴더 및 최근에 사용한 파일 및 폴더 삭제 방법 (0) | 2021.03.08 |
|---|