Microsoft MVP성태의 닷넷 이야기
.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [링크 복사], [링크+제목 복사]
조회: 27487
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 7개 있습니다.)

관리자 권한이 필요한 작업을 COM+에 대행

얼마 전 답변한 내용에서,

관리자 권한과 ClickOnce, 그리고 Bootstrapper 문제
; https://www.sysnet.pe.kr/3/0/1050

관리자 권한의 코드를 COM+에 대행해 보는 것이 어떨까 하는 의견을 냈었는데요. 저도 사실 이론적으로만 알고 있었을 뿐 해본 적은 없었는데, 그 답변을 계기로 직접 한번 해보았습니다.

우선, COM+ 코드를 만들어야 겠지요. 기본적인 COM+ 생성 강좌는 다음에 있으니 참고하시고,

Deploy the component as a Shared Assembly and Configure it in the COM+ Catalog
; http://gsraj.tripod.com/dotnet/complus/complus.net_accountmanager.html

여기서는 위에 설명된 내용을 중복할 필요없이 곧바로 "ClassLibraryAsAdmin"이라는 프로젝트를 생성해서 다음과 같이 작성했습니다.

[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(false)]
[assembly: ApplicationName("ClassLibraryAsAdmin")]
[assembly: ComVisible(true)]

namespace ClassLibraryAsAdmin
{
    [System.Runtime.InteropServices.Guid("41AC8568-9230-4E63-B7C5-CAAD997EE207")]
    public class AdminCode : ServicedComponent, IAdminCode
    {
        public void SetRegistryValue()
        {
            using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey("SYSTEM", true)) // 관리자 권한 필요
            {
                regKey.CreateSubKey("Test");
            }
        }
    }
}

IAdminCode 인터페이스는 위의 클래스 라이브러리를 직접 참조를 하지 않도록 별도의 DLL 프로젝트(ComBaseClass)를 하나 만들어서 정의했고,

[assembly: ComVisible(true)]

namespace ComBaseClass
{
    [Guid("23172f2f-a3d3-4180-97ae-7805f74a5a46")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IAdminCode
    {
        void SetRegistryValue();
    }
}

이제 "ClassLibraryAsAdmin" 프로젝트를 빌드하고 gacutil.exe와 regsvcs.exe를 이용해서 시스템에 등록해 줍니다. 등록과 관련해서 이전에 쭉 정리를 했었지요. ^^

gacutil.exe로 어셈블리 등록 시 시스템 변경 사항
; https://www.sysnet.pe.kr/2/0/1285

regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU
; https://www.sysnet.pe.kr/2/0/1286

regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0
; https://www.sysnet.pe.kr/2/0/1287

regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library
; https://www.sysnet.pe.kr/2/0/1288

regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항
; https://www.sysnet.pe.kr/2/0/1289

그렇습니다. 위의 글들을 쓰게 된 동기는... ^^ 바로 이 글을 쓰다가 등록 과정을 정리해야 할 필요가 있겠다 싶은 생각이 든 때문이었습니다. 위의 글 이상으로 자세히 소개할 수 없으니 여기서는 그냥 아래와 같이 등록했다고만 이야기 하고 지나가겠습니다. ^^

D:\temp\net20\AnyCPU>"..\..\net20\gacutil.exe" /i ComBaseClass.dll
Microsoft (R) .NET Global Assembly Cache Utility.  Version 3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

Assembly successfully added to the cache

D:\temp\net20\AnyCPU>"..\..\net20\gacutil.exe" /i ClassLibraryAsAdmin.dll
Microsoft (R) .NET Global Assembly Cache Utility.  Version 3.5.30729.1
Copyright (c) Microsoft Corporation.  All rights reserved.

Assembly successfully added to the cache

D:\temp\net20\AnyCPU>C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\regsvcs.exe ClassLibraryAsAdmin.dll
Microsoft (R) .NET Framework Services Installation Utility Version 2.0.50727.3053
Copyright (c) Microsoft Corporation.  All rights reserved.

Auto exporting 'ComBaseClass, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c91e971f6240da9f' as 'C:\WINDOWS\assembly\GAC_MSIL\ComBaseClass\1.0.0.0__c91e971f6240da9f\ComBaseClass.tlb'.
Installed Assembly:
        Assembly: D:\temp\net20\AnyCPU\ClassLibraryAsAdmin.dll
        Application: ClassLibraryAsAdmin
        TypeLib: D:\temp\net20\AnyCPU\ClassLibraryAsAdmin.tlb

참고로, 쓸모없을 것 같다고 해서 Type Library 파일을 지우면 다음과 같은 예외가 발생할 수 있습니다. ^^

System.InvalidCastException was unhandled
HResult=-2147467262
Message=Unable to cast COM object of type 'System.__ComObject' to interface type 'ComBaseClass.IAdminCode'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{23172F2F-A3D3-4180-97AE-7805F74A5A46}' failed due to the following error: Error loading type library/DLL. (Exception from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)). Source=mscorlib
StackTrace:
Server stack trace:
at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease)
at ComBaseClass.IAdminCode.SetRegistryValue()
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg)
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at ComBaseClass.IAdminCode.SetRegistryValue()
at ConsoleApplication1.Program.Main(String[] args) in D:\...[생략]...\Program.cs:line 15
InnerException:





여기까지 진행했으면 Component Services 관리 콘솔에 다음과 같이 등록된 것을 확인할 수 있습니다.

complus_as_admin_0.png

"관리자 권한"을 필요로 하기 때문에 해당 COM+ 서버의 구동 계정을 "Local SYSTEM"(또는, 그 외의 관리자 계정)으로 변경해 줄 필요가 있습니다. 그런데, 다음과 같이 "Identity" 탭의 "Local System" 계정이 비활성화되어 있는 것을 볼 수 있습니다.

complus_as_admin_1.png

왜냐하면, COM+ 서버에 대해 Local System 권한을 주는 것은 NT 서비스를 통해서만 가능하도록 바뀌었기 때문입니다. 그래서, "Activation" 탭을 이용하여 명시적으로 "Run application as NT Service" 옵션을 설정해 주어야 합니다.

complus_as_admin_2.png

이렇게 바꿔준 후 다시 "Identity" 탭으로 가면 "Local System" 옵션이 활성화되어 있어 설정이 가능합니다.

complus_as_admin_3.png

변경 사항을 적용해 주면, 이제 "서비스 관리자"에서 다음과 같이 NT 서비스로 등록된 것을 확인할 수 있습니다.

complus_as_admin_4.png

이것으로 설정 작업은 모두 끝났습니다.




마지막으로, 이를 직접 사용하는 C# 콘솔 응용 프로그램을 만들어서 다음과 같이 COM+로 등록된 개체를 동적으로 생성할 수 있습니다.

using System;
using ComBaseClass;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Guid guid = new Guid("{23172f2f-a3d3-4180-97ae-7805f74a5a46}");
            Type type = Type.GetTypeFromCLSID(guid);

            IAdminCode adminCode = Activator.CreateInstance(type) as IAdminCode;

            adminCode.SetRegistryValue();
        }
    }
}

여기까지, 실제로 COM+ 개체를 이용하여 '관리자 권한의 코드를 실행'하는 것이 가능한지 알아보았습니다.




모두 좋은데, 딱 한가지 마음에 들지 않는 것이 있다면 COM+ 관리 콘솔에서 해당 COM+ Application에 대해 수동으로 NT 서비스 등록을 해주어야 한다는 점입니다. 사내 시스템에 배포하는 프로그램일지라도 이런 식의 설정 작업을 해주어야 한다는 것은 현실성이 없어보입니다.

다행히, 이런 작업을 코딩으로 대체하는 것이 가능합니다. 이에 대해서는 아래의 글에서 자세히 나와 있습니다.

Set Identity to Local System for Com+ Application
; https://social.msdn.microsoft.com/Forums/en-US/df74247b-d107-41cf-bb10-404f976f7d2c/set-identity-to-local-system-for-com-application?forum=winformssetup

이를 위해서 우선 COM+ 관리 개체를 참조해야 하는데요.

complus_as_admin_5.png

관리의 편이성을 위해 아래의 방법을 사용해서 Interop DLL을 제거할 수 있으니 참고하십시오.

레지스트리 등록 및 Interop DLL 없이 COM 개체 사용하는 방법
; https://www.sysnet.pe.kr/2/0/1180

종합해 보면, 다음과 같은 단계를 거쳐서 COM+ 응용 프로그램을 생성 및 NT 서비스로 등록해 줄 수 있습니다.

string appName = "ClassLibraryAsAdmin";
string folderPath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
string complusPath = Path.Combine(folderPath, appName + ".dll");

// COM+ Catalog 개체 생성
Guid guid = new Guid("{F618C514-DFB8-11D1-A2CF-00805FC79235}");
Type type = Type.GetTypeFromCLSID(guid);

object comObject = Activator.CreateInstance(type);

ICOMAdminCatalog2 catalog = comObject as ICOMAdminCatalog2;

ICatalogCollection apps = catalog.GetCollection("Applications") as ICatalogCollection;
apps.Populate();

// COM+ Application 생성
ICatalogObject catObject = apps.Add() as ICatalogObject;
catObject["Name"] = appName;
catObject["Activation"] = COMAdminActivationOptions.COMAdminActivationLocal;
catObject["ApplicationAccessChecksEnabled"] = false;
catObject["ShutdownAfter"] = 1;
catObject["Identity"] = @"nt authority\system";
apps.SaveChanges();

// COM+ Application 내에 구성 요소 추가
string dllPath = Path.ChangeExtension(modulePath, ".dll");
catalog.InstallComponent(appName, dllPath, string.Empty, string.Empty);

// "Local System" 권한의 NT 서비스로 등록
catalog.CreateServiceForApplication(appName, appName, "SERVICE_DEMAND_START", "SERVICE_ERROR_NORMAL",
    string.Empty, @".\LOCALSYSTEM", null, false);

참고로, ICatalogObject의 속성에 어떤 값들을 집어넣어야 할 지는 다음의 문서를 보시면 됩니다.

Applications collection
; https://learn.microsoft.com/en-us/windows/win32/cossdk/applications

코드로 모두 작성해 보기는 했지만, 사실 대부분의 작업을 "regsvcs.exe"에서 해주기 때문에 복잡하게 할 것 없이 CreateServiceForApplication 메서드만 마지막에 호출하는 식으로 마무리하는 것이 좋습니다.

그래서 최종적으로 정리해 보면, 다음과 같은 등록 절차를 거치면 됩니다.

// GAC 등록
gacutil /i ClassLibraryAsAdmin.dll
gacutil /i ComBaseClass.dll

// 명시적인 regsvcs.exe 호출
C:\Windows\Microsoft.NET\Framework64\v2.0.50727\regsvcs ClassLibraryAsAdmin.dll

// CreateServiceForApplication를 호출해서 NT 서비스로 등록해주는 사용자 정의 exe 파일 실행
ComplusInstaller.exe

등록 과정이 다소 복잡해지는 문제가 있지만, 일단 이렇게 구성해 두면 이후부터는 '관리자 권한의 코드'를 실행하는 데 '사용자 동의' 창을 띄우는 불편함은 없어지니 충분한 가치가 있습니다.

마지막으로... 한가지 더 정리해야 할 것이 있는데요. 그런데, 굳이 기존의 "NT 서비스로 관리 코드를 실행하는 방법"을 쓰지 않고 COM+로 등록해 주는 것이 더 편한 이유가 있을까요?

개인적인 생각으로 2가지 정도를 꼽아보았습니다.

  • 보다 편리한 통신: NT 서비스 만으로 구성한 경우, 관리 코드를 실행하기 위해 별도로 소켓이나 WCF를 열어두어야 하는데 그런 작업이 제거됩니다.
  • 인스턴스 관리 용이: NT 서비스 만으로 구성한 경우, Start/Stop 등에 대한 관리를 해주어야 하는데 COM+로 해주면 개체 생성과 함께 NT 서비스가 자동 Start 되고 일정 시간의 Idle 시간이 흐른 이후 자동으로 Stop 상태로 전환됩니다.

첨부된 파일은 위에서 설명한 "ComplusInstaller" 코드까지 모두 포함한 테스트 프로젝트입니다.





[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/30/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2012-05-18 09시46분
[ryujh] 안녕하세요.

웹에서 COM+ 컴포넌트 를 사용하다가 필요없어서 일반 참조용으로 모두 바꿔서 잘쓰고 있는데 위의 글을 읽고 COM+ 유용한 점 잘봤습니다. 도움 많이 되었습니다.

본문 중에

'IAdminCode 인터페이스는 위의 클래스 라이브러리를 직접 참조를 하지 않도록 별도의 DLL 프로젝트(ComBaseClass)를 하나 만들어서 정의했고,'

직접 참조라는 것이 IAdminCode 인터페이스 코드와 클래스코드가 같이 있어서 수정시 매번 빌드가 필요하다는 뜻인지요?

인터페이스 부분만 dll 로 분리했다면 다른 컴포넌트에서도 IAdminCode 를 프로젝트 참조로 하여 사용하기 위해서인지요?

인터페이스 사용의 유용한 예를 들자면 어떤것이 있을지요?

질문만 하는것 같습니다.

의견을 듣고(?) 싶습니다.

감사합니다.
[guest]
2012-05-18 10시05분
말씀하신 부분은... 단지 tight coupling을 끊으려는 의도에서 한 것뿐입니다. 최근 들어 Framework 버전이 다양화되면서 직접 프로젝트를 참조하게 되면 불편한 점이 꽤 있었던 것 같습니다. 예를 들어, DLL/EXE 프로젝트가 모두 .NET 2.0 이었다가 DLL 프로젝트를 .NET 4.0으로 올리면 EXE 프로젝트에서 참조가 안되는 등... ^^

그리고, 아래의 글에서 설명을 했지만,

regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0
; http://www.sysnet.pe.kr/2/0/1287

위와 같은 상황에서는 인터페이스 분리가 선택이 아닌 필수였습니다.
정성태
2012-05-18 10시52분
[Lyn] COM Elevation 의 COM+버전인건가요 `-`
[guest]
2012-05-18 11시34분
"Lyn"님 그렇지는 않습니다. COM+의 서버 활성화에서만 가능한 것이고 별도 EXE로 뜨기 때문에 구동 계정을 지정할 수 있을 뿐입니다. 사실 계정을 Administrator 권한에 속하지 않는 것으로도 지정할 수 있으므로 딱히 Elevation과 관련 지을 필요는 없습니다.

어찌 보면, COM+보다 이후에 나온 Vista의 COM Elevation이 COM+ 서버 활성화의 응용이라고 봐야 합니다. ActiveX에서 필요한 관리자 권한의 코드를 별도의 DLL로 분리시키고 그것을 관리자 권한으로 상승한 대리자 process에서 활성화 시키니까요.
정성태
2012-05-18 12시08분
[Lyn] 생각해보니 그렇네요. UAC가 더 나중에나왔지..
[guest]
2013-04-17 03시39분
Local SYSTEM 권한으로 코드를 실행하는 방법
; http://www.sysnet.pe.kr/2/0/1436
정성태
2020-10-19 01시20분
정성태

... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13221정성태1/19/20234162Linux: 57. C# - 리눅스 프로세스 메모리 정보파일 다운로드1
13220정성태1/19/20234313오류 유형: 837. NETSDK1045 The current .NET SDK does not support targeting .NET ...
13219정성태1/18/20233872Windows: 220. 네트워크의 인터넷 접속 가능 여부에 대한 판단 기준
13218정성태1/17/20233795VS.NET IDE: 178. Visual Studio 17.5 (Preview 2) - 포트 터널링을 이용한 웹 응용 프로그램의 외부 접근 허용
13217정성태1/13/20234389디버깅 기술: 185. windbg - 64비트 운영체제에서 작업 관리자로 뜬 32비트 프로세스의 덤프를 sos로 디버깅하는 방법
13216정성태1/12/20234653디버깅 기술: 184. windbg - 32비트 프로세스의 메모리 덤프인 경우 !peb 명령어로 나타나지 않는 환경 변수
13215정성태1/11/20236158Linux: 56. 리눅스 - /proc/pid/stat 정보를 이용해 프로세스의 CPU 사용량 구하는 방법 [1]
13214정성태1/10/20235728.NET Framework: 2087. .NET 6부터 SourceGenerator와 통합된 System.Text.Json [1]파일 다운로드1
13213정성태1/9/20235269오류 유형: 836. docker 이미지 빌드 시 "RUN apt install ..." 명령어가 실패하는 이유
13212정성태1/8/20235028기타: 85. 단정도/배정도 부동 소수점의 정밀도(Precision)에 따른 형변환 손실
13211정성태1/6/20235112웹: 42. (https가 아닌) http 다운로드를 막는 웹 브라우저
13210정성태1/5/20234132Windows: 219. 윈도우 x64의 경우 0x00000000`7ffe0000 아래의 주소는 왜 사용하지 않을까요?
13209정성태1/4/20234028Windows: 218. 왜 윈도우에서 가상 메모리 공간은 64KB 정렬이 된 걸까요?
13208정성태1/3/20233961.NET Framework: 2086. C# - Windows 운영체제의 2MB Large 페이지 크기 할당 방법파일 다운로드1
13207정성태12/26/20224270.NET Framework: 2085. C# - gpedit.msc의 "User Rights Assignment" 특권을 코드로 설정/해제하는 방법파일 다운로드1
13206정성태12/24/20224474.NET Framework: 2084. C# - GetTokenInformation으로 사용자 SID(Security identifiers) 구하는 방법 [3]파일 다운로드1
13205정성태12/24/20224871.NET Framework: 2083. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용 (2)파일 다운로드1
13204정성태12/22/20224152.NET Framework: 2082. C# - (LSA_UNICODE_STRING 예제로) CustomMarshaler 사용법파일 다운로드1
13203정성태12/22/20224285.NET Framework: 2081. C# Interop 예제 - (LSA_UNICODE_STRING 예제로) 구조체를 C++에 전달하는 방법파일 다운로드1
13202정성태12/21/20224661기타: 84. 직렬화로 설명하는 Little/Big Endian파일 다운로드1
13201정성태12/20/20225286오류 유형: 835. PyCharm 사용 시 C 드라이브 용량 부족
13200정성태12/19/20224161오류 유형: 834. 이벤트 로그 - SSL Certificate Settings created by an admin process for endpoint
13199정성태12/19/20224449개발 환경 구성: 656. Internal Network 유형의 스위치로 공유한 Hyper-V의 VM과 호스트가 통신이 안 되는 경우
13198정성태12/18/20224325.NET Framework: 2080. C# - Microsoft.XmlSerializer.Generator 처리 없이 XmlSerializer 생성자를 예외 없이 사용하고 싶다면?파일 다운로드1
13197정성태12/17/20224267.NET Framework: 2079. .NET Core/5+ 환경에서 XmlSerializer 사용 시 System.IO.FileNotFoundException 예외 발생하는 경우파일 다운로드1
13196정성태12/16/20224397.NET Framework: 2078. .NET Core/5+를 위한 SGen(Microsoft.XmlSerializer.Generator) 사용법
... [16]  17  18  19  20  21  22  23  24  25  26  27  28  29  30  ...