Microsoft MVP성태의 닷넷 이야기
.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [링크 복사], [링크+제목 복사],
조회: 27641
글쓴 사람
정성태 (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)
13084정성태6/21/20226706개발 환경 구성: 644. Windows - 파이썬 2.7을 msi 설치 없이 구성하는 방법
13083정성태6/20/20227332.NET Framework: 2024. .NET 7에 도입된 GC의 메모리 해제에 대한 segment와 region의 차이점 [2]
13082정성태6/19/20226362.NET Framework: 2023. C# - Process의 I/O 사용량을 보여주는 GetProcessIoCounters Win32 API파일 다운로드1
13081정성태6/17/20226423.NET Framework: 2022. C# - .NET 7 Preview 5 신규 기능 - System.IO.Stream ReadExactly / ReadAtLeast파일 다운로드1
13080정성태6/17/20227027개발 환경 구성: 643. Visual Studio 2022 17.2 버전에서 C# 11 또는 .NET 7.0 preview 적용
13079정성태6/17/20224668오류 유형: 814. 파이썬 - Error: The file/path provided (...) does not appear to exist
13078정성태6/16/20226781.NET Framework: 2021. WPF - UI Thread와 Render Thread파일 다운로드1
13077정성태6/15/20227089스크립트: 40. 파이썬 - PostgreSQL 환경 구성
13075정성태6/15/20226042Linux: 50. Linux - apt와 apt-get의 차이 [2]
13074정성태6/13/20226381.NET Framework: 2020. C# - NTFS 파일에 사용자 정의 속성값 추가하는 방법파일 다운로드1
13073정성태6/12/20226610Windows: 207. Windows Server 2022에 도입된 WSL 2
13072정성태6/10/20226891Linux: 49. Linux - ls 명령어로 출력되는 디렉터리 색상 변경 방법
13071정성태6/9/20227505스크립트: 39. Python에서 cx_Oracle 환경 구성
13070정성태6/8/20227304오류 유형: 813. Windows 11에서 입력 포커스가 바뀌는 문제 [1]
13069정성태5/26/20229531.NET Framework: 2019. C# - .NET에서 제공하는 3가지 Timer 비교 [2]
13068정성태5/24/20228013.NET Framework: 2018. C# - 일정 크기를 할당하는 동안 GC를 (가능한) 멈추는 방법 [1]파일 다운로드1
13067정성태5/23/20227316Windows: 206. Outlook - 1년 이상 지난 메일이 기본적으로 안 보이는 문제
13066정성태5/23/20226663Windows: 205. Windows 11 - Windows + S(또는 Q)로 뜨는 작업 표시줄의 검색 바가 동작하지 않는 경우
13065정성태5/20/20227319.NET Framework: 2017. C# - Windows I/O Ring 소개 [2]파일 다운로드1
13064정성태5/18/20226918.NET Framework: 2016. C# - JIT 컴파일러의 인라인 메서드 처리 유무
13063정성태5/18/20227348.NET Framework: 2015. C# - 인라인 메서드(inline methods)
13062정성태5/17/20228069.NET Framework: 2014. C# - async/await 그리고 스레드 (4) 비동기 I/O 재현파일 다운로드1
13061정성태5/16/20226897.NET Framework: 2013. C# - FILE_FLAG_OVERLAPPED가 적용된 파일의 읽기/쓰기 시 Position 관리파일 다운로드1
13060정성태5/15/20229404.NET Framework: 2012. C# - async/await 그리고 스레드 (3) Task.Delay 재현파일 다운로드1
13059정성태5/14/20227826.NET Framework: 2011. C# - CLR ThreadPool의 I/O 스레드에 작업을 맡기는 방법 [1]파일 다운로드1
13058정성태5/13/20227720.NET Framework: 2010. C# - ThreadPool.SetMaxThreads 사용법
... 16  17  18  19  20  21  [22]  23  24  25  26  27  28  29  30  ...