AI가 준 PowerShell 명령, 실행하기 전에 확인할 것
AI가 준 PowerShell 명령을 실행하기 전에는 한 줄 전체가 무엇을 하는지 확인하세요. 처음이 조회 명령처럼 보여도 뒤에서 파일을 저장하거나 삭제하고, 설정을 바꾸거나 다른 곳으로 정보를 보낼 수 있습니다.
이 글은 PowerShell을 조금 아는 독자를 위한 검토 안내입니다. 명령 뒤에 붙은 부분과 결과를 넘겨받는 다음 명령, 불러오는 스크립트까지 살펴봅니다. 내용을 알 수 없는 명령은 실행하지 말고, 전체 명령과 스크립트 내용을 확인할 때까지 보류하세요.
아래 위험 제안은 검토용 문자열입니다. PowerShell 창에 붙여넣거나 실행하지 마세요. Python 예제는 합성 검토표의 정답을 대조하며, PowerShell 명령을 실행하거나 Windows 동작을 검증하는 코드는 아닙니다.

한 명령에 여러 영향을 기록하기
조회와 쓰기는 서로 배타적인 분류가 아닙니다. 파일을 읽어 외부 서버에 보내면 조회와 전송을 모두 기록합니다. 아래 항목은 검토를 돕는 분류이며 명령 이름만으로 자동 판정하는 규칙이 아닙니다.
| 영향 | 찾을 부분 | 실행 전 확인할 것 |
|---|---|---|
| 조회 | 목록·내용·속성을 읽는 동작 | 실제 대상 경로, 읽을 범위, 내용 노출 여부 |
| 쓰기 | 새 파일·덮어쓰기·추가·이동 | 목적지, 기존 파일 충돌, 복구 사본 |
| 삭제 | Remove-Item 등 대상 제거 | 구체적인 대상 목록, 복구 가능 여부, 승인 |
| 보안 설정 | 실행 정책·권한·보안 설정 변경 | 변경 이유, 원래 값, 조직 정책, 승인 담당자 |
| 전송 | 웹 요청 본문·원격 전달·업로드 | 실제 수신자, 전송 데이터, 민감정보, 승인 |
| 미확인 | 별칭·함수·동적 호출·숨은 스크립트 | 실제 정의와 전체 본문 확보 전 보류 |
Microsoft 파이프라인 문서는 앞 명령의 결과가 다음 명령의 입력으로 이어진다고 설명합니다. 앞부분을 읽기 명령으로 검토했더라도 뒤쪽 명령의 영향은 별도로 확인해야 합니다. 표에 적은 영향 이름과 보류 정책은 이 글이 정한 검토 기준입니다.
리디렉션과 저장 명령은 조회 뒤에서도 쓰기입니다
Microsoft 리디렉션 문서는 >가 파일로 출력하고 >>가 내용을 추가하는 동작을 설명합니다. 파일로 쓰는 >는 기존 내용을 경고 없이 덮어쓸 수 있습니다. 2>&1처럼 스트림을 합치는 표현과 실제 파일 목적지를 지정하는 표현도 구분합니다.
목록 뒤에 > files.txt가 있으면 콘솔에 보기만 하는 제안이 아닙니다. Export-Csv 문서는 객체를 CSV 문자열로 변환해 파일에 저장한다고 설명합니다. Tee-Object나 Out-File 등 저장을 맡는 명령도 빠뜨리지 않습니다. 파일명만 보지 말고 현재 위치에서 어느 경로로 해석되는지 확인하세요.
별칭과 스크립트 본문이 비어 있으면 보류하기
Microsoft 별칭 문서는 별칭이 cmdlet뿐 아니라 함수·스크립트·실행 파일을 가리킬 수 있음을 설명합니다. 따라서 짧은 이름을 기억만으로 풀지 않습니다. 아래 A04의 del도 현재 세션의 정의를 확인하지 않았으므로 unknown_alias로 남깁니다.
명령 우선순위 문서는 이름이 같을 때 별칭·함수 등이 원래 명령을 가릴 수 있고 Get-Command -All로 같은 이름의 명령들을 확인할 수 있다고 설명합니다. 믿을 수 있는 기본 PowerShell 환경에서 정의를 조회하고 표준 모듈·전체 이름을 확인합니다. 표준 모듈을 명시해도 설치된 모듈 자체가 신뢰할 수 있다는 전제가 필요합니다.
스크립트 파일 이름이 cleanup.ps1이라는 사실은 어떤 파일을 지우는지 알려주지 않습니다. & $step 같은 호출은 변수의 실제 값이 필요합니다. 함수·스크립트 블록·외부 실행 파일·인코딩된 명령·다운로드한 코드를 읽지 않은 상태에서는 “읽기 전용”이라고 표시하지 않습니다. 숨은 동작의 본문을 실행해서 확인하려고 하지 마세요.
아홉 합성 제안을 검토표로 분리하기
다음 표는 실행 순서가 아닙니다. A02부터 A09는 복사해 실행할 실습 명령이 아니며, 파일·정책·전송 동작을 실제로 수행하지 않습니다. A01의 조회 후보도 표준 cmdlet 정의와 로컬 파일 시스템 대상이 확인됐다는 가정 아래 검토한 의도입니다. 실제 현재 세션의 안전성은 확인하지 않았습니다.
| ID | 제안의 핵심 | 기록할 영향 | 판정과 이유 |
|---|---|---|---|
| A01 | Get-ChildItem, -LiteralPath ., -File | 조회 | 조회 후보. 대상과 표준 명령 정의 확인 필요 |
| A02 | A01 뒤에 > files.txt | 조회·쓰기 | 변경 보류. 파일 출력·기존 내용 영향 확인 |
| A03 | A01 다음 Export-Csv .\files.csv | 조회·쓰기 | 변경 보류. 파이프 끝의 저장 동작 확인 |
| A04 | del .\practice.txt | 삭제 의심·별칭 미확인 | 미확인 보류. 실제 별칭 정의 필요 |
| A05 | Set-ExecutionPolicy, CurrentUser, Bypass | 보안 설정 | 변경 보류. 정책 변경 필요성·승인 확인 |
| A06 | Invoke-RestMethod의 POST 본문에 Get-Content 결과 | 조회·전송 | 변경 보류. 수신자와 전달 데이터 확인 |
| A07 | .\cleanup.ps1 | 스크립트 본문 미확인 | 미확인 보류. 모든 호출 본문 확보 |
| A08 | & $step | 동적 호출 미확인 | 미확인 보류. 변수 값과 호출 대상 확보 |
| A09 | 목록 뒤 ForEach-Object에서 & $step $_ | 조회·동적 호출 미확인 | 미확인 보류. 반복 대상별 영향 확인 |
A06의 example.com 주소와 practice.txt는 합성 문자열입니다. 이 주소로 요청을 보내지 않았고 실제 사용자 자료를 읽지 않았습니다. Invoke-RestMethod 문서는 HTTP 요청과 본문·메서드 인수를 설명합니다. 실제 요청이 성공하는지와 어떤 서버가 받는지는 이 예제의 결론이 아닙니다.
A05는 문제 해결을 위해 실행 정책을 낮추라는 권고가 아닙니다. 실행 정책 문서는 실행 정책이 보안 경계 자체는 아니라고 설명하며 Windows와 비Windows의 동작도 구분합니다. Linux에서 검사한 표를 Windows 정책 변경 시험으로 확대하지 않습니다.
조회 전용으로 좁힌 PowerShell 예제
다음 예제는 자신이 확인한 로컬 연습 폴더의 파일명과 크기를 콘솔에 보여주는 코드입니다. 폴더를 만들거나 파일을 저장하지 않고, 재귀 조회·삭제·외부 전송도 넣지 않았습니다. C:\AI-Practice는 예시 경로이므로 실제로 존재하며 읽어도 되는 로컬 연습 폴더로 바꾸어 검토합니다. 이 글에서는 실행하지 않았습니다.
# 문서로 검토한 조회 예제. 이 글에서는 PowerShell로 실행하지 않음
$Folder = 'C:\AI-Practice'
Microsoft.PowerShell.Management\Get-ChildItem -LiteralPath $Folder -File |
Microsoft.PowerShell.Utility\Select-Object -Property Name, Length
Get-ChildItem 문서는 지정한 위치의 항목을 조회하며 LiteralPath가 와일드카드로 해석되지 않음을 설명합니다. 이 명령은 파일 시스템 외의 공급자도 다룰 수 있으므로 실제 대상이 로컬 폴더인지 확인해야 합니다. 파일명·크기 역시 업무 정보를 드러낼 수 있습니다. 결과를 외부 채팅이나 메일로 복사하는 판단은 조회 코드 검토와 별도입니다.
Python으로 검토표 누락과 복구를 확인하기
아래 코드를 check_impact_review.py로 저장하고 python check_impact_review.py를 실행합니다. PowerShell 문자열은 Python 데이터에 들어 있을 뿐입니다. eval, exec, subprocess, PowerShell 호출, 네트워크 요청이 없습니다. 검토표의 누락을 고정 정답표와 비교하는 코드이며 임의의 PowerShell을 분석하는 파서가 아닙니다.
"""Manual fixture comparison only. Never evaluates any PowerShell text."""
import copy
EXPECTED = [
{'id':'A01', 'command':r'Get-ChildItem -LiteralPath . -File', 'effects':['read'], 'gate':'조회 후보'},
{'id':'A02', 'command':r'Get-ChildItem -LiteralPath . -File > files.txt', 'effects':['read','write'], 'gate':'변경 보류'},
{'id':'A03', 'command':r'Get-ChildItem -LiteralPath . -File | Export-Csv .\files.csv', 'effects':['read','write'], 'gate':'변경 보류'},
{'id':'A04', 'command':r'del .\practice.txt', 'effects':['delete','unknown_alias'], 'gate':'미확인 보류'},
{'id':'A05', 'command':r'Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass', 'effects':['security_setting'], 'gate':'변경 보류'},
{'id':'A06', 'command':r'Invoke-RestMethod -Uri https://example.com/ingest -Method Post -Body (Get-Content -LiteralPath .\practice.txt -Raw)', 'effects':['read','transfer'], 'gate':'변경 보류'},
{'id':'A07', 'command':r'.\cleanup.ps1', 'effects':['unknown_script'], 'gate':'미확인 보류'},
{'id':'A08', 'command':r'& $step', 'effects':['unknown_dynamic'], 'gate':'미확인 보류'},
{'id':'A09', 'command':r'Get-ChildItem . | ForEach-Object { & $step $_ }', 'effects':['read','unknown_dynamic'], 'gate':'미확인 보류'},
]
def check_review(rows):
if [row['id'] for row in rows] != [row['id'] for row in EXPECTED]:
return ['ID_ORDER_OR_COUNT']
errors = []
for reference, row in zip(EXPECTED, rows):
sid = reference['id']
if row['command'] != reference['command']:
errors.append('COMMAND_CHANGED:' + sid)
if sorted(row['effects']) != sorted(reference['effects']):
errors.append('EFFECTS:' + sid)
if row['gate'] != reference['gate']:
errors.append('GATE:' + sid)
return errors
def run_demo():
assert check_review(EXPECTED) == []
print('manual_review_normal PASS; rows=9')
bad = copy.deepcopy(EXPECTED)
bad[1]['effects'] = ['read']
bad[2]['effects'] = ['read']
bad[4]['effects'] = ['read']
bad[5]['effects'] = ['read']
bad[6]['gate'] = '조회 후보'
result = check_review(bad)
expected = ['EFFECTS:A02', 'EFFECTS:A03', 'EFFECTS:A05', 'EFFECTS:A06', 'GATE:A07']
assert result == expected, result
print('manual_review_bad', result)
recovered = copy.deepcopy(EXPECTED)
assert check_review(recovered) == []
assert bad[1]['effects'] == ['read']
print('manual_review_recovery PASS; bad_review_preserved=True')
print('powershell_commands_executed=0; network_requests=0')
print('This compares a fixed human review table; it is not a PowerShell parser.')
if __name__ == '__main__':
run_demo()
실제 로컬 실행에서 정상 검토표 9행은 통과했습니다. 쓰기·보안설정·전송 영향을 일부러 지우고 숨은 스크립트를 조회 후보로 바꾼 사본은 다음 다섯 오류를 냈습니다.
manual_review_normal PASS; rows=9
manual_review_bad ['EFFECTS:A02', 'EFFECTS:A03', 'EFFECTS:A05', 'EFFECTS:A06', 'GATE:A07']
manual_review_recovery PASS; bad_review_preserved=True
powershell_commands_executed=0; network_requests=0
This compares a fixed human review table; it is not a PowerShell parser.
복구는 원래 정답 기준을 다시 연결한 새 검토표에서 했습니다. 위험 명령을 실행했다가 시스템을 되돌린 복구 실험이 아닙니다. 일반적인 AI 제안을 받으면 이 정답표를 그대로 적용하지 말고 해당 코드의 실제 본문·인수·대상에 맞는 검토 근거를 새로 작성해야 합니다.
실행 전 남길 기록과 중단 기준
명령 원문 식별자 / 전체 줄 수:
현재 PowerShell·OS 버전 / 실제 명령 정의·모듈:
읽는 대상 / 쓰는 목적지 / 삭제 대상:
보안 설정의 변경 전후 / 전송 데이터·수신자:
별칭·함수·스크립트·동적 호출의 전체 본문 확인:
리디렉션·파이프 다음 단계·반복문의 영향:
미확인 항목 / 필요한 승인 / 복구 사본 위치:
판정: 조회 후보 / 변경 보류 / 미확인 보류
전체 본문·실제 대상·수신자 중 하나라도 모르면 중단합니다. 조회 후보는 실행 권한을 부여하는 상태가 아닙니다. 변경이나 전송이 필요하면 해당 대상·데이터·영향에 대한 승인을 먼저 확인합니다. 관리자 권한으로 실행해서 오류를 없애는 방법은 이 글의 범위에 없습니다.
-WhatIf를 붙이는 것만으로 미확인 스크립트를 시험하지 않습니다. Microsoft ShouldProcess 안내는 해당 지원이 올바르게 구현돼야 하고 호출되는 명령에 처리가 전달되는지도 확인해야 한다고 설명합니다. 검토되지 않은 초기화 코드나 외부 프로그램까지 무변경을 보장하는 스위치로 취급하지 마세요.
권한을 문서로 남길 때에는 API 키 보안 운영표의 메타데이터 구분을 참고할 수 있습니다. 이 글의 표에는 비밀 키를 적지 않습니다. 실제 데이터를 AI로 전달할 범위는 엑셀 데이터 AI 입력 검토에서 별도로 확인합니다.