PowerShell을 사용하는 .NET Standard 2.0 Class Library 프로젝트는 ClickOnce 배포가 안됩니다.

요즘 ADB(Android Debug Bridge)를 이용한 프로젝트를 주로 하다보니, Windows Terminal 에서 PowerShell을 많이 사용하게 되었습니다.

명령어만 치고 날려버리기 아까워서 ps1 스크립트를 작성하여 C# 프로젝트에 내장하여 사용하고자 했습니다.

PowerShell을 그동안 잘 안써왔던 저 같은 분도 계실텐데, PowerShell은 2가지가 있습니다.

Windows 10 이상에 기본적으로 설치되어 있는 Windows PowerShell 5.1 버전이 있고,
따로 설치해서 사용해야 하는 크로스플랫폼 전용 PowerShell Core 6 가 있습니다. (20240111 기준 최신 PowerShell Core는 7.4 버전입니다.)
보시면 아시겠지만 앞에 Windows가 붙었기 때문에 윈도우 전용이고 후자는 Windows 없이 Core가 붙어서 크로스플랫폼입니다.
당연하게도, Windows PowerShell은 .Net Framework를 사용하지만 PowerShell Core는 .Net Core 이상을 사용합니다.
넘버링은 .NET Framework 4.8이 .NET 5 가 되었듯 계승하여 5.1 이 6이 되었습니다.

.NET Framework 프로젝트에서는 System.Management.Automation.dll 어셈블리가 PowerShell 네임스페이스를 들고 있기에 참조해서 사용하시면 되고, .NET Standard 2.0에서는 Nuget으로 PowerShellStandard.Library 5.1.1 버전을 설치해서 사용하시면 됩니다. 그러면 안에 System.Management.Automation.dll 어셈블리가 내장되어 있습니다.

여기까지 하고나서 visual studio에서 디버깅을 하거나 빌드된 실행프로그램을 실행하면 powershell이 잘 동작합니다.

하지만 ClickOnce로 배포했을 때 문제가 발생합니다.
배포하고 ClickOnce 패키지를 설치를 하려고 하면

플랫폼 버전 정보
	Windows 			: 10.0.19045.0 (Win32NT)
	Common Language Runtime 	: 4.0.30319.42000
	System.Deployment.dll 		: 4.8.9176.0 built by: NET481REL1LAST_B
	clr.dll 			: 4.8.9181.0 built by: NET481REL1LAST_C
	dfdll.dll 			: 4.8.9176.0 built by: NET481REL1LAST_B
	dfshim.dll 			: 10.0.19041.30000 (WinBuild.160101.0800)

원본
	배포 url			: (생략!)
	배포 제공자 url		: (생략!)
	응용 프로그램 url			: (생략!)

ID
	배포 ID		: (생략!), Version=0.1.0.40, Culture=neutral, PublicKeyToken=d5dc1a4088bea489, processorArchitecture=amd64
	응용 프로그램 ID		: (생략!), Version=0.1.0.40, Culture=neutral, PublicKeyToken=d5dc1a4088bea489, processorArchitecture=amd64, type=win32

응용 프로그램 요약
	* 설치 가능한 응용 프로그램입니다.

오류 요약
	다음은 오류에 대한 요약입니다. 이러한 오류의 세부 정보는 나중에 로그에 기록됩니다.
	* (생략!) 활성화로 예외가 발생했습니다. 다음 실패 메시지가 발견되었습니다.
		+ 이 System.Management.Automation.dll 어셈블리의 강력한 이름 서명이 잘못되었습니다.

구성 요소 저장소 트랜잭션 실패 요약
	트랜잭션 오류가 발생하지 않았습니다.

경고
	이 작업을 수행하는 동안 경고가 발생하지 않았습니다.

작업 진행 상태
	* [2024-01-11 오후 8:16:57]: (생략!) 활성화가 시작되었습니다.
	* [2024-01-11 오후 8:16:57]: 배포 매니페스트의 처리가 완료되었습니다.
	* [2024-01-11 오후 8:16:57]: 응용 프로그램 설치가 시작되었습니다.
	* [2024-01-11 오후 8:16:57]: 응용 프로그램 매니페스트 처리가 완료되었습니다.
	* [2024-01-11 오후 8:16:59]: 호환 가능한 런타임 버전 4.0.30319을(를) 찾았습니다.
	* [2024-01-11 오후 8:16:59]: 트러스트 요청과 플랫폼 검색이 완료되었습니다.

오류 정보
	이 작업을 수행하는 동안 다음 오류가 발생했습니다.
	* [2024-01-11 오후 8:17:04] System.Deployment.Application.InvalidDeploymentException(SignatureValidation)
		- 이 System.Management.Automation.dll 어셈블리의 강력한 이름 서명이 잘못되었습니다.
		- 원본: System.Deployment
		- 스택 추적:
			위치: System.Deployment.Application.ComponentVerifier.VerifyStrongNameAssembly(String filePath, AssemblyManifest assemblyManifest)
			위치: System.Deployment.Application.ComponentVerifier.VerifyComponents()
			위치: System.Deployment.Application.DownloadManager.DownloadDependencies(SubscriptionState subState, AssemblyManifest deployManifest, AssemblyManifest appManifest, Uri sourceUriBase, String targetDirectory, String group, IDownloadNotification notification, DownloadOptions options)
			위치: System.Deployment.Application.ApplicationActivator.DownloadApplication(SubscriptionState subState, ActivationDescription actDesc, Int64 transactionId, TempDirectory& downloadTemp)
			위치: System.Deployment.Application.ApplicationActivator.InstallApplication(SubscriptionState& subState, ActivationDescription actDesc)
			위치: System.Deployment.Application.ApplicationActivator.PerformDeploymentActivation(Uri activationUri, Boolean isShortcut, String textualSubId, String deploymentProviderUrlFromExtension, BrowserSettings browserSettings, String& errorPageUrl, Uri& deploymentUri)
			위치: System.Deployment.Application.ApplicationActivator.PerformDeploymentActivationWithRetry(Uri activationUri, Boolean isShortcut, String textualSubId, String deploymentProviderUrlFromExtension, BrowserSettings browserSettings, String& errorPageUrl)
--- 예외가 throw된 이전 위치의 스택 추적 끝 ---
			위치: System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
			위치: System.Deployment.Application.ApplicationActivator.PerformDeploymentActivationWithRetry(Uri activationUri, Boolean isShortcut, String textualSubId, String deploymentProviderUrlFromExtension, BrowserSettings browserSettings, String& errorPageUrl)
			위치: System.Deployment.Application.ApplicationActivator.ActivateDeploymentWorker(Object state)

이런 ClickOnce 설치 오류 메시지가 발생합니다.

문제를 찾아봤더니

c# - .Net Framework 프로젝트에서 사용하기 위해 .Net Standard 라이브러리의 PowerShell 클래스를 사용할 수 있습니까? (stackoverflow.com)

이런 문서를 발견했고, 이 문서 안에

PowerShell 표준 라이브러리: Windows PowerShell 및 PowerShell Core에서 작동하는 단일 모듈 빌드 - PowerShell Team (microsoft.com)

위 문서가 링크되어 있습니다.
문서를 요약하면 새로운 Visual Studio Project Template이 필요한 것이었습니다.

dotnet new -i Microsoft.PowerShell.Standard.Module.Template

를 입력하면,

이런 프로젝트 템플릿이 생성됩니다.
이 PoweShell C# 프로젝트를 Build 하면 출력되는 .NET 어셈블리를 Ipmo(Import-Module)라는 명령어로 Windows PowerShell 5.1과 PowerShell Core 6 이상에서 모두 사용할 수 있습니다.

Ipmo 명령어로 추가된 모듈은

Remove-Module -Name {모듈이름}

rmo(Remove-Module) 으로 제거할 수 있으며, 모듈명은

Get-Module

gmo(Get-Module)을 입력하여 나오는 Name 부분을 입력하면 됩니다. (확장자 없음)

추가적으로 프로젝트에서 Nuget을 확인해보니,

image
PowerShellStandard.Library 5.1.1이 아니라 5.1.0-preview-06 을 사용하고 있었습니다.
PowerShellStandard.Library 5.1.0-preview-06 만 설치해서 ClickOnce 배포를 해서 설치해보지는 않았습니다.
일단 이 프로젝트를 이용해서 ClickOnce 배포&설치는 성공했습니다.

더 나가실 분들은 Visual Studio PowerShell Standard Module 프로젝트 템플릿 없이 PowerShellStandard.Library 5.1.0-preview-06 이것만 설치해서 ClickOnce를 배포해보시면 될 것 같습니다.

또한, 이 Visual Studio PowerShell Standard Module 프로젝트 템플릿이던, 일반 .NET Standard Class Library 프로젝트이던, UnitTest 에서 참조해서 사용할 때는 UnitTest 프로젝트 측에서 별도로 Microsoft.PowerShell.SDK 를 종속성에 맞는 버전을 설치해서 사용했습니다.

UnitTest 프로젝트는 .NET 5 xunit 유닛테스트 프로젝트를 사용했습니다.
Microsoft.PowerShell.SDK은 종속성이 .NET 메이저 버전별로 빡쌔게 되어 있기 때문에 꼭 맞는 버전을 설치해서 사용하시면 될 것 같습니다. 상위 버전의 패키지가 하위 버전의 패키지를 전혀 지원하지 않습니다.

이상입니다.

8 Likes

ClickOnce! 정말 오랫만에 들어보네요. ㅎㅎ 주제와는 관련이 없지만 다른 경험담 하나가 생각나서 댓글 달아봅니다.

개인적으로 앱 설치 배포는 가장 고전적인 방식으로 만들어서 배포하는게 가장 성공률이 높다고 생각합니다. ClickOnce가 압축을 해제하고 설치를 시도하는 과정에서 '임시 디렉터리’를 사용하는데, 애석하게도 여기를 공략하는 (?) 안티바이러스 제품들이나 보안 에이전트들이 제법 많다보니 ClickOnce로 애플리케이션 설치가 잘 안된다는 문의가 많이 들어오는 편입니다.

제 기억으로 닷넷 프로젝트는 아니지만 예전에 Google Chrome의 Windows 버전을 ClickOnce로 배포하던 시기도 오래전에 있었던 것으로 기억합니다. 하지만 지금은 그냥 EXE 파일로 묶어서 배포하는 방식을 사용하고 있는데, 임시 디렉터리를 거치지 않고 곧바로 목적지에 파일 콘텐츠를 기록하도록 만드는 편이 여러모로 설치 안정성 면에서는 장점이 있는 것 같습니다.

닷넷 기반의 인스톨러를 만드는 스탠스를 계속 유지하면서도 ClickOnce보다 안정적인 설치 방법을 찾고 계시다면, 개인적으로는 WiX를 검토해보시면 더 좋지 않을까 생각합니다. WiX의 경우 MSI 패키지로 제작하는 것으로 설치 패키지를 만들며, ClickOnce랑 마찬가지로 관리자 권한을 요구하지 않고 설치하게끔 하는 것도 가능합니다. :smiley:

덧. 식탁보도 WixSharp을 기반으로 인스톨러를 제조하고 있습니다.

6 Likes

경험을 공유해주셔서 감사합니다 ㅎㅎ

1 Like