about_PowerShell_Editions

about_PowerShell_Editions

같은 이름의 PowerShell인데, 어떤 .NET에서 돌아가느냐에 따라 두 갈래로 나뉜다는 이야기예요. 이 글에서는 PowerShell 에디션(Desktop과 Core)이 뭘 의미하는지, 지금 나의 PowerShell이 어떤 에디션인지, 그리고 모듈이 어느 에디션에서 동작하는지 어떻게 선언하고 확인하는지를 하나씩 짚어 볼게요.

출처: Microsoft Learn - about_PowerShell_Editions

본문

짧은 설명

PowerShell에는 에디션이 있고, 에디션마다 돌아가는 기본 런타임이 달라요.

자세한 설명

PowerShell 5.1부터 에디션이 나뉘기 시작했어요. 각 에디션은 서로 다른 .NET 런타임 위에서 돌아가죠. PowerShell 6.0부터는 딱 두 가지 에디션이 있어요.

첫 번째는 Desktop이에요. .NET Framework 위에서 돌아가는 에디션이고요, PowerShell 4 이하 그리고 5.1이 여기에 속해요. Windows Desktop, Windows Server, Windows Server Core처럼 기능이 꽉 찬 Windows 에디션에서 쓸 수 있고, 대부분의 다른 Windows 운영 체제에서도 동작해요. 이건 최초의 PowerShell 에디션이고, 운영 체제를 기본 설치할 때 함께 포함되죠.

두 번째는 Core예요. .NET Core 위에서 돌아가는 에디션이고, PowerShell 6.0 이상이 여기에 해당해요. 기능이 꽉 찬 Windows 에디션에서는 이전 PowerShell 버전과 나란히(사이드바이사이드) 설치되고, Windows Nano Server나 Windows IoT처럼 설치 공간이 작은 Windows 에디션에서도 돌아가고, Linux나 macOS 같은 Windows가 아닌 플랫폼에서도 돌아가요.

여기서 기억해 둘 점이 있어요. PowerShell의 에디션이 곧 그 .NET 런타임을 뜻하기 때문에, 에디션은 .NET API와 PowerShell 모듈의 호환성을 가늠하는 핵심 지표가 돼요. 어떤 .NET API나 타입, 메서드는 두 .NET 런타임 양쪽에 다 있지 않을 수 있고, 그게 그런 것들에 의존하는 스크립트와 모듈에 영향을 주거든요.

$PSEdition 자동 변수

PowerShell 5.1 이상에서는 $PSEdition 자동 변수로 지금 돌아가는 에디션을 알 수 있어요.

$PSEdition
Core

PowerShell 4 이하에서는 이 변수가 아예 없어요. 이때 $PSEdition이 null이면, 그냥 Desktop 값을 가진 것과 똑같이 취급하면 돼요.

$PSVersionTable 안의 에디션

$PSVersionTable 자동 변수에도 PowerShell 5.1 이상에서는 PSEdition 속성이 들어 있어요.

$PSVersionTable
Name                           Value
----                           -----
PSVersion                      7.3.9
PSEdition                      Core
GitCommitId                    7.3.9
OS                             Microsoft Windows 10.0.22621
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.0.1.1
WSManStackVersion              3.0

여기서 PSEdition 필드는 $PSEdition 자동 변수와 같은 값을 가져요.

모듈 매니페스트의 CompatiblePSEditions 필드

PowerShell 모듈은 모듈 매니페스트의 CompatiblePSEditions 필드로, 자기 자신이 어느 에디션의 PowerShell과 호환되는지 선언할 수 있어요.

예를 들어 DesktopCore 양쪽 에디션과 호환된다고 선언하는 모듈 매니페스트를 볼게요.

@{
ModuleVersion = '1.0'
FunctionsToExport = @('Test-MyModule')
CompatiblePSEditions = @('Desktop', 'Core')
}

이번엔 Desktop 에디션만 호환되는 모듈 매니페스트예요.

@{
ModuleVersion = '1.0'
FunctionsToExport = @('Test-MyModule')
CompatiblePSEditions = @('Desktop')
}

모듈 매니페스트에서 CompatiblePSEditions 필드를 빼면, Desktop으로 설정한 것과 같은 효과가 나요. 이 필드가 도입되기 전에 만들어진 모듈은 원래 이 에디션을 기준으로 써졌으니까요.

Windows의 일부로 배포되지 않는 모듈(즉, 직접 작성했거나 갤러리에서 설치한 모듈)에서는 이 필드가 정보 제공용일 뿐이에요. PowerShell이 CompatiblePSEditions 필드를 보고 동작을 바꾸는 게 아니라, Get-Module이 반환하는 PSModuleInfo 객체에 이 값을 노출해 주니, 필요하면 자기 로직에서 쓸 수 있도록 한 거죠.

$newModuleManifestSplat = @{
Path = '.\TestModuleWithEdition.psd1'
CompatiblePSEditions = 'Desktop', 'Core'
PowerShellVersion = '5.1'
}
New-ModuleManifest @newModuleManifestSplat
$ModuleInfo = Test-ModuleManifest -Path .\TestModuleWithEdition.psd1
$ModuleInfo.CompatiblePSEditions
Desktop
Core

참고

CompatiblePSEditions 모듈 필드는 PowerShell 5.1 이상에서만 쓸 수 있어요. 이 필드를 넣으면 모듈이 PowerShell 4 이하와는 호환되지 않게 되죠. 그리고 이 필드는 순전히 정보 제공용이므로, 이후 PowerShell 버전에서는 안전하게 생략해도 돼요.

PowerShell 6.1에서는 Get-Module -ListAvailable의 포맷터가 각 모듈의 에디션 호환성까지 표시하도록 업데이트됐어요.

Get-Module -ListAvailable

Directory: C:\Users\me\Documents\PowerShell\Modules

ModuleType Version    Name                   PSEdition ExportedCommands
---------- -------    ----                   --------- ----------------
Script     1.4.0      Az                     Core,Desk
Script     1.3.1      Az.Accounts            Core,Desk {Disable-AzDataCollection, Disable-AzContextAutosave, E...
Script     1.0.1      Az.Aks                 Core,Desk {Get-AzAks, New-AzAks, Remove-AzAks, Import-AzAksCreden...

...

Script     4.4.0      Pester                 Desk      {Describe, Context, It, Should...}
Script     1.18.0     PSScriptAnalyzer       Desk      {Get-ScriptAnalyzerRule, Invoke-ScriptAnalyzer, Invoke-...
Script     1.0.0      WindowsCompatibility   Core      {Initialize-WinSession, Add-WinFunction, Invoke-WinComm...

Windows 일부로 제공되는 모듈의 에디션 호환성

Windows의 일부로 딸려 오는 모듈(또는 역할이나 기능의 일부로 설치되는 모듈)에서는 이야기가 달라져요. 이런 모듈은 Windows PowerShell 시스템 모듈 디렉터리(%windir%\System\WindowsPowerShell\v1.0\Modules)에서 찾을 수 있고요, PowerShell 6.1 이상에서는 이 디렉터리에 대해 CompatiblePSEditions 필드를 다르게 처리해요.

이 디렉터리에서 로드되거나 이 디렉터리에서 찾은 모듈에 대해서는, PowerShell 6.1 이상이 CompatiblePSEditions 필드로 현재 세션과 호환되는지를 판단하고 그에 맞게 동작해요.

Import-Module을 쓸 때는, CompatiblePSEditionsCore가 없는 모듈은 가져오지 않고 오류를 보여줘요.

Import-Module BitsTransfer
Import-Module : Module 'C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\BitsTransfer\BitsTransfer.psd1'
does not support current PowerShell edition 'Core'. Its supported editions are 'Desktop'. Use 'Import-Module
-SkipEditionCheck' to ignore the compatibility of this module.
At line:1 char:1
+ Import-Module BitsTransfer
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo          : ResourceUnavailable: (C:\WINDOWS\system32\u2026r\BitsTransfer.psd1:String)
[Import-Module], InvalidOperationException
+ FullyQualifiedErrorId : Modules_PSEditionNotSupported,Microsoft.PowerShell.Commands.ImportModuleCommand

Get-Module -ListAvailable을 쓸 때는, CompatiblePSEditionsCore가 없는 모듈은 목록에 표시되지 않아요.

Get-Module -ListAvailable BitsTransfer
# No output

두 경우 모두 -SkipEditionCheck [switch] 매개 변수로 이 동작을 무시할 수 있어요.

Get-Module -ListAvailable -SkipEditionCheck BitsTransfer

Directory: C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules

ModuleType Version    Name           PSEdition ExportedCommands
---------- -------    ----           --------- ----------------
Manifest   2.0.0.0    BitsTransfer   Desk      {Add-BitsFile, Complete-BitsTransfer, Get-BitsTransfer,...

경고

Import-Module -SkipEditionCheck가 모듈을 성공적으로 로드하는 것처럼 보여도, 그 모듈을 쓰다 보면 나중에 호환성 문제를 만날 위험이 있어요. 초기 로딩은 성공할지 몰라도, 나중에 어떤 명령이 호환되지 않는 API를 호출하면서 갑자기 실패할 수 있거든요.

에디션 간 호환 모듈 작성하기

DesktopCore 두 에디션 모두에서 동작하는 PowerShell 모듈을 쓸 때, 에디션 간 호환성을 보장하기 위해 할 수 있는 것들이 있어요.

다만 호환성을 확실히 확인하고 계속 검증하는 유일한 확실한 방법은, 스크립트나 모듈에 대해 테스트를 작성하고, 호환성이 필요한 모든 버전과 에디션의 PowerShell에서 그 테스트를 돌려 보는 거예요. 이때 추천하는 테스트 프레임워크가 바로 Pester예요.

PowerShell 스크립트

언어로서의 PowerShell은 에디션 간에 똑같이 동작해요. 에디션 호환성의 영향을 받는 건 사용하는 cmdlet, 모듈, .NET API 쪽이죠.

일반적으로 PowerShell 6.1 이상에서 동작하는 스크립트는 Windows PowerShell 5.1에서도 동작하지만, 예외가 몇 가지 있어요.

PSScriptAnalyzer 1.18+ 버전에는 PSUseCompatibleCommandsPSUseCompatibleTypes 같은 규칙이 있어서, PowerShell 스크립트에서 호환되지 않을 수 있는 명령과 .NET API 사용을 감지해 줘요.

.NET 어셈블리

바이너리 모듈을 쓰거나 소스 코드에서 만든 .NET 어셈블리(DLL)를 포함하는 모듈을 쓴다면, .NET StandardPowerShell Standard를 대상으로 컴파일하세요. 그러면 컴파일 시점에 .NET과 PowerShell API 호환성을 검증할 수 있어요.

다만 이런 라이브러리는 컴파일 시점에 호환성 일부를 확인해 주지만, 에디션 간에 있을 수 있는 동작 차이까지는 잡아 내지 못해요. 그건 여전히 테스트를 직접 작성해서 확인해야 합니다.

더 알아보기