about_PSModulePath

about_PSModulePath

PowerShell이 모듈을 찾으러 어디를 뒤져야 할지 어떻게 아는지 궁금하셨나요? 그 역할을 하는 게 바로 $Env:PSModulePath라는 환경 변수예요. 이 변수에는 폴더 위치 목록이 들어 있고, PowerShell은 모듈(.psd1이나 .psm1 파일)을 찾을 때 이 목록에 있는 폴더들을 차례로 살펴봐요. 이 글에서는 이 변수가 정확히 어떤 일을 하는지, 실행할 때 어떤 규칙으로 만들어지는지, 그리고 필요할 때 어떻게 손대는지 차근차근 풀어볼게요.

출처: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_psmodulepath

본문

Long description

$Env:PSModulePath 환경 변수에는 폴더 위치 목록이 담겨 있어요. PowerShell은 각 폴더를 뒤져서 모듈(.psd1 또는 .psm1) 파일을 찾아요.

기본적으로 $Env:PSModulePath에 지정되는 위치는 다음과 같아요.

  • CurrentUser scope로 설치된 모듈 — Windows에서는 $HOME\Documents\PowerShell\Modules에 저장돼요. Documents 폴더의 정확한 위치는 Windows 버전과 폴더 리디렉션 사용 여부에 따라 달라지고, Microsoft OneDrive가 그 위치를 바꿀 수도 있어요. Documents 폴더 위치를 확인하려면 [Environment]::GetFolderPath('MyDocuments') 명령을 실행하면 돼요. non-Windows 시스템에서는 $HOME/.local/share/powershell/Modules 폴더에 저장돼요.
  • AllUsers scope로 설치된 모듈 — Windows에서는 $Env:ProgramFiles\PowerShell\Modules, non-Windows 시스템에서는 /usr/local/share/powershell/Modules에 저장돼요.
  • PowerShell과 함께 제공되는 모듈$PSHOME\Modules에 저장돼요.

참고: PowerShell 모듈을 포함하는 응용 프로그램은 Windows의 Program Files 폴더 같은 다른 디렉터리에 모듈을 설치할 수 있어요. 이때 설치 프로그램이 그 위치를 $Env:PSModulePath에 추가해 주지 않을 수도 있어요.

Windows PowerShell 5.1의 기본 위치는 PowerShell 7과 달라요.

  • CurrentUser scope 모듈은 $HOME\Documents\WindowsPowerShell\Modules에 저장돼요.
  • AllUsers scope 모듈은 $Env:ProgramFiles\WindowsPowerShell\Modules에 저장돼요.
  • Windows PowerShell에 포함된 모듈은 $PSHOME\Modules, 즉 $Env:SystemRoot\System32\WindowsPowerShell\1.0\Modules에 저장돼요.

PowerShell PSModulePath 구성

$Env:PSModulePath의 값은 PowerShell이 시작될 때마다 새로 만들어져요. 그 값은 PowerShell 버전과 시작 방법에 따라 달라져요.

Windows PowerShell 시작 시

Windows PowerShell은 시작할 때 다음 논리로 PSModulePath를 구성해요.

  • PSModulePath가 없으면: CurrentUser, AllUsers, $PSHOME 모듈 경로를 결합해요.
  • PSModulePath가 있으면:
    • PSModulePath에 $PSHOME 모듈 경로가 포함되어 있으면: AllUsers 모듈 경로를 $PSHOME 모듈 경로 앞에 삽입해요.
    • 그 외에는: 사용자가 의도적으로 $PSHOME 위치를 제거한 것이므로 정의된 PSModulePath를 그대로 사용해요.

CurrentUser 모듈 경로는 User scope $Env:PSModulePath가 없을 때만 앞에 붙어요. 있으면 정의된 User scope $Env:PSModulePath 값이 그대로 사용돼요.

PowerShell 7 시작 시

Windows에서는 대부분의 환경 변수가 User-scoped 값만 존재하면 같은 이름의 Machine-scoped 값이 있더라도 새 프로세스는 그 User 값만 사용해요. 하지만 path 환경 변수는 다르게 취급돼요.

Windows에서 PSModulePath는 Path 환경 변수와 비슷한 방식으로 다뤄져요. Path는 다른 환경 변수와 달리, 프로세스가 시작될 때 Windows가 User-scoped Path와 Machine-scoped Path를 결합해 주거든요. PSModulePath도 같은 의미론(semantics)을 따릅니다.

  • User-scoped PSModulePath를 가져와요.
  • 프로세스가 상속한 PSModulePath 환경 변수와 비교해요.
    • 같으면: AllUsers PSModulePath를 PATH 환경 변수의 의미론에 따라 끝에 추가해요. Windows System32 경로는 machine 정의 PSModulePath에서 나오므로 굳이 명시적으로 추가하지 않아도 돼요.
    • 다르면: 사용자가 명시적으로 수정한 것으로 보고 AllUsers PSModulePath를 추가하지 않아요.
  • PS7의 User, System, $PSHOME 경로를 그 순서대로 앞에 붙여요. powershell.config.json에 user scoped PSModulePath가 정의되어 있으면 기본값 대신 그것을 사용하고, system scoped PSModulePath가 정의되어 있으면 그것을 사용해요.

non-Windows 시스템에는 User와 System 환경 변수의 구분이 없어요. PSModulePath를 그대로 상속받고, PS7 전용 경로가 아직 정의되어 있지 않으면 앞에 붙여요.

PowerShell 7에서 Windows PowerShell 시작하기

여기서 말하는 Windows PowerShell은 powershell.exepowershell_ise.exe를 모두 가리켜요.

$Env:PSModulePath 값은 다음 수정을 거쳐 WinPSModulePath에 복사돼요.

  • PS7의 User 모듈 경로 제거
  • PS7의 System 모듈 경로 제거
  • PS7의 $PSHOME 모듈 경로 제거

PS7 경로를 제거하는 이유는 Windows PowerShell이 PS7 모듈을 불러오지 않게 하기 위해서예요. 이 WinPSModulePath 값이 Windows PowerShell을 시작할 때 사용돼요.

이 수정은 PowerShell 7이 직접 시작하는 Windows PowerShell 프로세스에만 적용돼요. PowerShell 7이 띄운 cmd.exe나 Python 프로세스 같은 중간 프로세스를 거쳐 Windows PowerShell이 시작되면, 중간 프로세스는 수정되지 않은 PowerShell 7의 $Env:PSModulePath를 그대로 상속해서 자식 프로세스에 넘겨줘요. 이렇게 시작된 Windows PowerShell은 상속받은 PowerShell 7 모듈 경로를 유지하는데, 그 경로들이 Windows PowerShell 시스템 모듈 경로보다 앞에 있기 때문에 Microsoft.PowerShell.Utility처럼 이름이 겹치는 모듈을 Windows PowerShell이 불러올 수 없는 PowerShell 7 버전으로 해석해 버려요. 이 때문에 모듈 자동 로딩이 깨지고, 해당 모듈의 cmdlet들이 CommandNotFoundException 오류로 실패하게 돼요.

중간 프로세스를 통해 Windows PowerShell을 올바른 모듈 경로로 시작하려면 자식 프로세스의 환경에서 PSModulePath를 제거하면 돼요. 그러면 Windows PowerShell이 시작 시 기본값을 스스로 구성해요. 예를 들면:

  • PowerShell 7.4 이상Start-ProcessEnvironment 매개 변수로 중간 프로세스 환경에서 변수를 제거해요:
Start-Process python harness.py -Environment @{ PSModulePath = $null }
  • cmd.exe — Windows PowerShell을 시작하기 전에 변수를 비워요:
cmd /c "set PSModulePath=&& powershell.exe -File script.ps1"
  • Python — 자식 프로세스에 넘기는 환경에서 변수를 제거해요:
import os
import subprocess

env = {k: v for k, v in os.environ.items() if k.upper() != "PSMODULEPATH"}
subprocess.run(["powershell.exe", "-File", "script.ps1"], env=env)

Windows PowerShell에서 PowerShell 7 시작하기

PowerShell 7 시작은 기존 방식 그대로 진행되되, Windows PowerShell이 추가한 경로를 상속한다는 점만 더해져요. PS7 전용 경로가 앞에 붙어 있기 때문에 기능상 문제는 없어요.

Module search behavior (모듈 검색 동작)

PowerShell은 PSModulePath의 각 폴더를 재귀적으로 뒤져서 모듈(.psd1 또는 .psm1) 파일을 찾아요. 이 검색 방식 덕분에 같은 모듈의 여러 버전을 서로 다른 폴더에 나란히 설치할 수 있어요. 예를 들어:

    Directory: C:\Program Files\WindowsPowerShell\Modules\PowerShellGet

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----           8/14/2020  5:56 PM                1.0.0.1
d----           9/13/2019  3:53 PM                2.1.2

기본적으로 여러 버전이 발견되면 PowerShell은 가장 높은 버전 번호를 불러와요. 특정 버전을 불러오려면 Import-ModuleFullyQualifiedName 매개 변수와 함께 사용하면 돼요. 자세한 내용은 Import-Module 문서를 참고하세요.

Modifying PSModulePath (PSModulePath 수정하기)

대부분의 상황에서는 모듈을 기본 모듈 위치에 설치하는 게 맞아요. 그래도 때로는 PSModulePath 환경 변수의 값을 바꿔야 할 일이 생길 수 있어요.

예를 들어 현재 세션 동안에만 C:\Program Files\Fabrikam\Modules$Env:PSModulePath에 임시로 추가하려면 이렇게 입력하면 돼요:

$Env:PSModulePath = $Env:PSModulePath+";C:\Program Files\Fabrikam\Modules"

이 명령에서 세미콜론(;)은 앞서 있던 경로와 새 경로를 구분해 줘요. non-Windows 플랫폼에서는 콜론(:)이 경로를 구분해요.

non-Windows에서 PSModulePath 수정하기

non-Windows 환경에서 매 세션마다 PSModulePath 값을 바꾸려면 위 명령을 PowerShell 프로필에 추가하면 돼요.

Windows에서 PSModulePath 수정하기

Windows에서 매 세션마다 값을 바꾸려면 PSModulePath 값을 저장하고 있는 레지스트리 키를 편집하면 돼요. PSModulePath 값은 레지스트리에 **확장되지 않은 문자열(unexpanded strings)**로 저장돼요. 값을 영구히 확장된 문자열로 저장하지 않으려면 하위 키에서 GetValue() 메서드를 사용해 값 자체를 직접 편집하는 게 좋아요.

다음 예시는 C:\Program Files\Fabrikam\Modules 경로를 확장되지 않은 문자열을 유지한 채 PSModulePath 환경 변수 값에 추가해요.

$key = (Get-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment')
$path = $key.GetValue('PSModulePath','','DoNotExpandEnvironmentNames')
$path += ';%ProgramFiles%\Fabrikam\Modules'
$key.SetValue('PSModulePath',$path,[Microsoft.Win32.RegistryValueKind]::ExpandString)

사용자 설정에 경로를 추가하려면 다음 코드를 사용하면 돼요.

$key = (Get-Item 'HKCU:\Environment')
$path = $key.GetValue('PSModulePath','','DoNotExpandEnvironmentNames')
$path += ';%ProgramFiles%\Fabrikam\Modules'
$key.SetValue('PSModulePath',$path,[Microsoft.Win32.RegistryValueKind]::ExpandString)

더 알아보기