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

관리자 권한이 필요한 작업을 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("96A2754F-0F5A-446B-A974-3EC47C04B27F")]
    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
; http://social.msdn.microsoft.com/Forums/en/winformssetup/thread/df74247b-d107-41cf-bb10-404f976f7d2c

이를 위해서 우선 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
; http://msdn.microsoft.com/en-us/library/ms686107.aspx

코드로 모두 작성해 보기는 했지만, 사실 대부분의 작업을 "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" 코드까지 모두 포함한 테스트 프로젝트입니다.





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

[연관 글]





[최초 등록일: ]
[최종 수정일: 8/8/2018 ]

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

비밀번호

댓글 쓴 사람
 



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

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

본문 중에

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

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

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

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

질문만 하는것 같습니다.

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

감사합니다.

 

[손님]
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+버전인건가요 `-`
[손님]
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가 더 나중에나왔지..
[손님]
2013-04-17 03시39분
Local SYSTEM 권한으로 코드를 실행하는 방법
; http://www.sysnet.pe.kr/2/0/1436
정성태
2020-10-19 01시20분
COM Surrogate (DLLHOST.EXE)
; https://medium.com/@nasbench/what-is-the-dllhost-exe-process-actually-running-ef9fe4c19c08
정성태

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12433정성태11/29/20209Windows: 179. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (3) - SO_PORT_SCALABILITY파일 다운로드1
12432정성태11/29/202046Windows: 178. 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (2) - SO_REUSEADDR파일 다운로드1
12431정성태11/27/202057.NET Framework: 976. UnmanagedCallersOnly + C# 9.0 함수 포인터 사용 시 x86 빌드에서 오동작하는 문제파일 다운로드1
12430정성태11/27/202014오류 유형: 686. Ubuntu - E: The repository 'cdrom://...' does not have a Release file.
12429정성태11/25/202052디버깅 기술: 175. windbg - 특정 Win32 API에서 BP가 안 걸리는 경우
12428정성태11/25/202031VS.NET IDE: 154. Visual Studio - .NET Core App 실행 시 dotnet.exe 실행 화면만 나오는 문제
12427정성태11/25/2020107.NET Framework: 975. .NET Core를 직접 호스팅해 (runtimeconfig.json 없이) EXE만 배포해 실행파일 다운로드1
12426정성태11/24/202024오류 유형: 685. WinDbg Preview - error InitTypeRead
12425정성태11/24/202022VC++: 141. Visual C++ - "Treat Warnings As Errors" 옵션이 꺼져 있는데도 일부 경고가 에러 처리되는 경우
12424정성태11/24/202054VC++: 140. C++의 연산자 동의어(operator synonyms), 대체 토큰
12423정성태11/22/2020101.NET Framework: 974. C# 9.0 - (16) 제약 조건이 없는 형식 매개변수 주석(Unconstrained type parameter annotations)파일 다운로드1
12422정성태11/21/202070.NET Framework: 973. .NET 5, .NET Framework에서만 허용하는 UnmanagedCallersOnly 사용예파일 다운로드1
12421정성태11/23/202073.NET Framework: 972. DNNE가 출력한 NE DLL을 직접 생성하는 방법파일 다운로드1
12420정성태11/19/202034오류 유형: 684. Visual C++ - MSIL .netmodule or module compiled with /GL found; restarting link with /LTCG; add /LTCG to the link command line to improve linker performance
12419정성태11/23/202092VC++: 139. Visual C++ - .NET Core의 nethost.lib와 정적 링크파일 다운로드1
12418정성태11/19/202032오류 유형: 683. Visual C++ - error LNK2038: mismatch detected for 'RuntimeLibrary': value 'MT_StaticRelease' doesn't match value 'MDd_DynamicDebug'파일 다운로드1
12417정성태11/19/202035오류 유형: 682. Visual C++ - warning LNK4099: PDB '...pdb' was not found with '...lib(pch.obj)' or at '...pdb'; linking object as if no debug info
12416정성태11/19/202023오류 유형: 681. Visual C++ - error LNK2001: unresolved external symbol _CrtDbgReport
12415정성태11/19/202081.NET Framework: 971. UnmanagedCallersOnly 특성과 DNNE 사용파일 다운로드1
12414정성태11/20/2020112VC++: 138. x64 빌드에서 extern "C"가 아닌 경우 ___cdecl name mangling 적용 [4]파일 다운로드1
12413정성태11/17/2020115.NET Framework: 970. .NET 5 / .NET Core - UnmanagedCallersOnly 특성을 사용한 함수 내보내기파일 다운로드1
12412정성태11/21/202081.NET Framework: 969. .NET Framework 및 .NET 5 - UnmanagedCallersOnly 특성 사용파일 다운로드1
12411정성태11/12/202071오류 유형: 680. C# 9.0 - Error CS8889 The target runtime doesn't support extensible or runtime-environment default calling conventions.
12410정성태11/12/202096디버깅 기술: 174. windbg - System.TypeLoadException 예외 분석 사례
12409정성태11/12/2020121.NET Framework: 968. C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...