이 글의 목적은 PowerShell 7 설치 이후 $Env:PSModulePath가 꼬이면서 모듈이 안 보이거나 잘못 로드되는 문제를 원인별로 진단하고, 현장에서 바로 적용 가능한 복구 절차와 재발 방지 기준을 정리하는 것이다.
1. PSModulePath 충돌이 왜 생기는지 이해하다
1) PSModulePath의 역할을 정확히 정의하다
$Env:PSModulePath는 PowerShell이 모듈을 찾기 위해 검색하는 폴더 목록이다. PowerShell은 이 목록을 순서대로 탐색하며, 같은 이름의 모듈이 여러 경로에 존재할 경우 먼저 발견되는 경로의 모듈이 우선되는 구조이다.
2) PowerShell 7 설치 후 “합쳐진 경로”가 충돌을 만들다
Windows PowerShell 5.1과 PowerShell 7은 기본적으로 모듈 저장 위치가 다르다. PowerShell 7은 호환 목적을 위해 Windows PowerShell 경로와 PowerShell 7 경로를 함께 PSModulePath에 포함하도록 설계되어 충돌 가능성이 구조적으로 증가하다.
3) 시작 시점에 동적으로 경로가 추가되며 예측이 어려워지다
Windows에서 PowerShell은 시작 과정에서 특정 시스템 경로를 PSModulePath에 자동 포함하는 동작을 수행하다. 환경 변수 창에 보이는 값과 세션 내부에서 확인되는 값이 달라 보이는 현상이 여기에서 발생하다.
2. 증상으로 먼저 분류하고, 그 다음에 원인을 확정하다
1) 대표 증상을 체크하다
- Install-Module 이후에도 Get-Module -ListAvailable에서 모듈이 보이지 않다라는 상황이 반복되다.
- Import-Module이 되지만 의도하지 않은 “구버전”이 로드되다.
- 동일 모듈을 설치했는데 PowerShell 5.1에서는 보이고 PowerShell 7에서는 보이지 않다.
- VS Code PowerShell 확장과 터미널 pwsh가 서로 다른 모듈을 바라보는 것처럼 보이다.
2) 현재 상태를 숫자와 경로로 증명하다
아래 코드는 현재 세션에서의 PowerShell 버전과 PSModulePath 목록을 “줄 단위”로 분해하여 중복과 순서를 확인하는 절차이다.
$PSVersionTable
Windows는 세미콜론(;) 구분이 기본이다
$paths = $env:PSModulePath -split ';' | Where-Object { $_ -and $_.Trim() }
보기 좋게 번호를 붙여 출력하다
$paths | ForEach-Object -Begin { $i = 0 } -Process {
$i++
"{0:D2} {1}" -f $i, $_
}
중복 경로를 찾다
$dup = $paths | Group-Object | Where-Object { $_.Count -gt 1 }
$dup | Select-Object Name, Count
아래 코드는 “같은 이름의 모듈이 여러 경로에 존재하는지”를 ModuleBase로 확정하는 절차이다.
# 예시로 Az, Pester, PSReadLine 중 하나를 넣어 점검하다 $moduleName = "Pester"
Get-Module -ListAvailable -Name $moduleName |
Sort-Object Version -Descending |
Select-Object Name, Version, PSEdition, ModuleBase
3. 충돌 유형별로 바로 복구하다
유형 A. PSModulePath가 “덮어쓰기” 되어 기본 경로가 누락되다
가장 위험한 유형은 시스템 또는 사용자 환경 변수에서 PSModulePath를 한 줄로 고정하면서 기본 경로 일부가 누락되는 유형이다. 이 유형은 모듈 탐색 자체가 깨지기 쉬우므로 “복구 우선순위”가 가장 높다.
아래 코드는 현재 값을 백업 파일로 저장하는 절차이다.
$backupDir = Join-Path $env:USERPROFILE "Desktop" $backupFile = Join-Path $backupDir ("PSModulePath_backup_{0:yyyyMMdd_HHmmss}.txt" -f (Get-Date))
$env:PSModulePath | Out-File -FilePath $backupFile -Encoding UTF8
$backupFile
가장 안전한 복구는 “환경 변수에서 사용자/시스템 PSModulePath 사용자 지정 값을 제거한 뒤 새 세션에서 재생성되도록 하는 방식”이다. GUI로는 시스템 속성(sysdm.cpl)에서 환경 변수 편집 화면으로 진입하여 사용자 변수와 시스템 변수의 PSModulePath 항목을 확인하고, 정책이나 업무 규정상 허용되는 범위에서 사용자 지정 값을 제거하는 방식이 우선이다.
유형 B. 경로 중복과 순서 꼬임으로 “원치 않는 버전”이 먼저 잡히다
같은 모듈 이름이 여러 경로에 존재하면 PSModulePath의 앞쪽 경로가 우선하다. 이때 WindowsPowerShell용 모듈이 PowerShell 7보다 먼저 발견되면 구버전 또는 비호환 모듈이 먼저 로드되는 구조적 문제가 발생하다.
아래 코드는 “현재 세션에서만” 중복을 제거하고, 사용자 Documents 경로를 상단에 배치하여 테스트하는 절차이다.
# 세션 임시 정리이다 $paths = $env:PSModulePath -split ';' | Where-Object { $_ -and $_.Trim() }
중복 제거(순서 보존)이다
$unique = [System.Collections.Generic.List[string]]::new()
foreach ($p in $paths) {
if (-not $unique.Contains($p)) { $unique.Add($p) }
}
사용자 PowerShell 7 모듈 경로를 최우선으로 올리다
$myDocs = [Environment]::GetFolderPath("MyDocuments")
$ps7UserModules = Join-Path $myDocs "PowerShell\Modules"
if ($unique.Contains($ps7UserModules)) {
$unique.Remove($ps7UserModules)
}
$unique.Insert(0, $ps7UserModules)
$env:PSModulePath = ($unique -join ';')
$env:PSModulePath
테스트 후 문제가 해결되면 “영구 반영”을 사용자 범위(User)에서 수행하는 방식이 안전하다. 아래 코드는 사용자 환경 변수(User)로 PSModulePath를 저장하는 절차이다.
# 사용자 환경 변수(User)에 저장하다 [Environment]::SetEnvironmentVariable("PSModulePath", $env:PSModulePath, "User") 유형 C. 모듈 설치 위치 자체가 PowerShell 5.1과 PowerShell 7에서 다르다
PowerShell 5.1은 사용자 모듈 경로가 Documents\WindowsPowerShell\Modules인 경우가 일반적이다. PowerShell 7은 사용자 모듈 경로가 Documents\PowerShell\Modules인 구조가 일반적이다. 같은 모듈을 설치해도 설치된 위치가 달라 “한쪽에서만 보이는 것처럼 보이는 착시”가 발생하다.
모듈 설치 위치를 의도대로 고정하려면 Install-Module에서 -Scope를 명확히 지정하는 습관이 필요하다. 또한 가능하면 PSModulePath에 포함된 경로로 설치하는 것이 권장이다.
# PowerShell 7에서 사용자 범위로 설치하다 Install-Module -Name Pester -Scope CurrentUser # 관리자 권한 환경에서 전체 사용자 범위로 설치하다 Install-Module -Name Pester -Scope AllUsers 유형 D. OneDrive 문서 리디렉션, UNC 문서 경로로 인해 모듈 경로가 의도치 않게 바뀌다
기업 환경에서 Documents가 OneDrive 또는 네트워크 경로로 리디렉션되면 Documents\PowerShell\Modules 자체가 UNC 경로가 되기도 하다. 이때 네트워크 지연, 권한, 오프라인 파일 정책으로 인해 모듈 탐색이 불안정해지기 쉽다.
아래 코드는 현재 “내 문서”의 실제 경로를 OS 기준으로 확정하고, 그 하위 모듈 경로 존재 여부를 점검하는 절차이다.
$myDocs = [Environment]::GetFolderPath("MyDocuments") $ps7UserModules = Join-Path $myDocs "PowerShell\Modules" $ps51UserModules = Join-Path $myDocs "WindowsPowerShell\Modules"
$myDocs
Test-Path $ps7UserModules
Test-Path $ps51UserModules
유형 E. 동일 모듈명이 여러 버전으로 공존하여 “어느 버전이 로드되는지” 불명확하다
동일 모듈명이 여러 경로에 공존하는 상황에서는 Import-Module -Name만으로는 원하는 버전을 강제하기 어렵다. 이때는 FullyQualifiedName으로 버전과 경로를 함께 지정하는 방식이 재현성이 높다.
# 설치된 후보를 먼저 확인하다 $moduleName = "PSReadLine" Get-Module -ListAvailable -Name $moduleName | Sort-Object Version -Descending | Select-Object Name, Version, ModuleBase # 원하는 버전을 강제 로드하다 Import-Module -FullyQualifiedName @{ ModuleName = "PSReadLine" ModuleVersion = "2.3.6" } 4. 권장 기본 경로를 기준으로 “누락”과 “과다”를 동시에 잡다
아래 표는 Windows에서 흔히 기대하는 모듈 경로 구성을 “용도 기준”으로 정리한 표이다. 실제 OS 버전과 설치 구성에 따라 차이가 발생할 수 있으므로, 표는 절대값이 아니라 점검 기준으로 활용하다.
| 구분 | 대표 경로 예시 | 역할 | 충돌 시 증상 |
|---|---|---|---|
| PowerShell 7 사용자 모듈 | Documents\PowerShell\Modules | pwsh에서 CurrentUser 설치 기본 경로 역할을 하다 | Install-Module 후에도 모듈이 안 보이는 현상이 발생하다 |
| Windows PowerShell 5.1 사용자 모듈 | Documents\WindowsPowerShell\Modules | powershell.exe(5.1)에서 CurrentUser 설치 기본 경로 역할을 하다 | 5.1에서는 보이고 7에서는 안 보이는 현상이 발생하다 |
| PowerShell 7 전체 사용자 모듈 | %ProgramFiles%\PowerShell\Modules | 관리자 설치 모듈의 공용 경로 역할을 하다 | 권한 문제로 설치 실패가 발생하다 |
| Windows PowerShell 전체 사용자 모듈 | %ProgramFiles%\WindowsPowerShell\Modules | 5.1 공용 모듈 경로 역할을 하다 | 구버전이 먼저 로드되어 버전 꼬임이 발생하다 |
| 기본 내장 모듈 경로 | %WINDIR%\System32\WindowsPowerShell\v1.0\Modules | OS 제공 모듈의 핵심 경로 역할을 하다 | 경로 누락 시 기본 기능 이상이 발생하다 |
5. “안전한 영구 해결”을 위한 표준 복구 스크립트를 적용하다
아래 스크립트는 사용자 범위(User)에서 PSModulePath를 재구성하는 절차이다. 이 스크립트는 기존 값에서 핵심 경로를 보존하면서, 중복 제거와 사용자 PowerShell 7 경로 우선 배치를 수행하다.
# 1) 현재 값을 백업하다 $myDocs = [Environment]::GetFolderPath("MyDocuments") $backupDir = Join-Path $env:USERPROFILE "Desktop" $backupFile = Join-Path $backupDir ("PSModulePath_user_before_{0:yyyyMMdd_HHmmss}.txt" -f (Get-Date)) [Environment]::GetEnvironmentVariable("PSModulePath", "User") | Out-File -FilePath $backupFile -Encoding UTF8 # 2) 현재 세션 기준으로 재구성하다 $raw = $env:PSModulePath -split ';' | Where-Object { $_ -and $_.Trim() } # 2-1) 중복 제거(순서 보존)이다 $unique = [System.Collections.Generic.List[string]]::new() foreach ($p in $raw) { if (-not $unique.Contains($p)) { $unique.Add($p) } } # 2-2) PowerShell 7 사용자 모듈 경로를 최우선으로 배치하다 $ps7UserModules = Join-Path $myDocs "PowerShell\Modules" if (-not (Test-Path $ps7UserModules)) { New-Item -ItemType Directory -Path $ps7UserModules | Out-Null } if ($unique.Contains($ps7UserModules)) { $unique.Remove($ps7UserModules) } $unique.Insert(0, $ps7UserModules) # 2-3) 문자열로 확정하다 $newValue = ($unique -join ';') # 3) 사용자 환경 변수(User)에 저장하다 [Environment]::SetEnvironmentVariable("PSModulePath", $newValue, "User") # 4) 저장 결과를 확인하다 [Environment]::GetEnvironmentVariable("PSModulePath", "User") 6. 운영 관점에서 재발을 막는 기준을 고정하다
1) 모듈 설치 정책을 “CurrentUser 또는 AllUsers”로 통일하다
팀 단위 운영에서는 모듈 설치 범위를 혼용하지 않는 것이 중요하다. 개인 자동화는 CurrentUser로 통일하고, 공용 서버나 빌드 러너는 AllUsers로 통일하는 방식이 관리가 단순하다.
2) 같은 모듈명이 여러 경로에 공존하지 않도록 정리하다
동일 모듈명이 여러 경로에 공존하면 PSModulePath 순서에 따라 결과가 바뀌기 쉽다. 운영 기준은 “모듈명은 한 경로에만 존재하도록 하다”라는 규칙을 두는 것이 안전하다.
3) 문제 재현을 위해 모듈 위치를 기록하다
장애 보고서에는 모듈명, 버전, ModuleBase를 함께 기록하는 습관이 필요하다. 이 기록은 원인 재현과 복구 검증을 빠르게 하다.
$moduleName = "Az.Accounts" Get-Module -ListAvailable -Name $moduleName | Sort-Object Version -Descending | Select-Object Name, Version, ModuleBase | Out-String FAQ
PowerShell 7에서 Windows PowerShell 모듈이 섞여 보이는 현상이 정상인가?
PowerShell 7은 호환을 위해 Windows PowerShell 모듈 경로를 PSModulePath에 포함할 수 있다. 이 동작 자체는 설계 의도에 가깝지만, 동일 모듈명이 공존할 때 충돌 가능성이 증가하므로 운영 기준으로 정리하는 것이 안전하다.
환경 변수 창에는 PSModulePath가 짧게 보이는데 pwsh에서는 더 길게 보이는 이유는 무엇인가?
PowerShell은 시작 시점에 시스템 경로를 포함하는 동작을 수행할 수 있다. 따라서 GUI에서 보이는 값과 세션에서 보이는 값이 완전히 일치하지 않을 수 있으며, 이는 동적 포함 동작으로 설명하다.
PSModulePath를 사용자 범위로 저장했더니 오히려 문제가 생기는 이유는 무엇인가?
사용자 범위에 PSModulePath를 저장하면 자동 구성값이 아닌 고정 문자열이 되기 쉽다. 이때 기본 경로 일부가 누락되면 모듈 탐색 자체가 불안정해질 수 있으므로, 저장 전후로 핵심 경로 포함 여부를 표 기준으로 점검하는 방식이 필요하다.
Install-Module은 성공인데 Import-Module이 실패하는 이유는 무엇인가?
모듈이 설치된 경로가 PSModulePath에 포함되지 않으면 Import-Module은 실패할 수 있다. 설치 경로를 PSModulePath에 포함하거나, PSModulePath에 포함된 경로로 설치하는 방식이 원칙이다.