성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>관리자 권한이 필요한 작업을 COM+에 대행</h1> <p> 얼마 전 답변한 내용에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 관리자 권한과 ClickOnce, 그리고 Bootstrapper 문제 ; <a target='tab' href='http://www.sysnet.pe.kr/3/0/1050'>http://www.sysnet.pe.kr/3/0/1050</a> </pre> <br /> 관리자 권한의 코드를 COM+에 대행해 보는 것이 어떨까 하는 의견을 냈었는데요. 저도 사실 이론적으로만 알고 있었을 뿐 해본 적은 없었는데, 그 답변을 계기로 직접 한번 해보았습니다.<br /> <br /> 우선, COM+ 코드를 만들어야 겠지요. 기본적인 COM+ 생성 강좌는 다음에 있으니 참고하시고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Deploy the component as a Shared Assembly and Configure it in the COM+ Catalog ; <a target='tab' href='http://gsraj.tripod.com/dotnet/complus/complus.net_accountmanager.html'>http://gsraj.tripod.com/dotnet/complus/complus.net_accountmanager.html</a> </pre> <br /> 여기서는 위에 설명된 내용을 중복할 필요없이 곧바로 "ClassLibraryAsAdmin"이라는 프로젝트를 생성해서 다음과 같이 작성했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>[assembly: ApplicationActivation(ActivationOption.Server)]</span> [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() { <span style='color: blue; font-weight: bold'>using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey("SYSTEM", true)) // 관리자 권한 필요</span> { regKey.CreateSubKey("Test"); } } } } </pre> <br /> IAdminCode 인터페이스는 위의 클래스 라이브러리를 직접 참조를 하지 않도록 별도의 DLL 프로젝트(ComBaseClass)를 하나 만들어서 정의했고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [assembly: ComVisible(true)] namespace ComBaseClass { [Guid("23172f2f-a3d3-4180-97ae-7805f74a5a46")] [InterfaceType(ComInterfaceType.InterfaceIsDual)] public interface IAdminCode { void SetRegistryValue(); } } </pre> <br /> 이제 "ClassLibraryAsAdmin" 프로젝트를 빌드하고 gacutil.exe와 regsvcs.exe를 이용해서 시스템에 등록해 줍니다. 등록과 관련해서 이전에 쭉 정리를 했었지요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > gacutil.exe로 어셈블리 등록 시 시스템 변경 사항 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1285'>http://www.sysnet.pe.kr/2/0/1285</a> regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1286'>http://www.sysnet.pe.kr/2/0/1286</a> regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1287'>http://www.sysnet.pe.kr/2/0/1287</a> regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1288'>http://www.sysnet.pe.kr/2/0/1288</a> regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1289'>http://www.sysnet.pe.kr/2/0/1289</a> </pre> <br /> 그렇습니다. 위의 글들을 쓰게 된 동기는... ^^ 바로 이 글을 쓰다가 등록 과정을 정리해야 할 필요가 있겠다 싶은 생각이 든 때문이었습니다. 위의 글 이상으로 자세히 소개할 수 없으니 여기서는 그냥 아래와 같이 등록했다고만 이야기 하고 지나가겠습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > D:\temp\net20\AnyCPU>"..\..\<span style='color: blue; font-weight: bold'>net20\gacutil.exe" /i ComBaseClass.dll</span> Microsoft (R) .NET Global Assembly Cache Utility. <span style='color: blue; font-weight: bold'>Version 3.5.30729.1</span> Copyright (c) Microsoft Corporation. All rights reserved. <span style='color: blue; font-weight: bold'>Assembly successfully added to the cache</span> D:\temp\net20\AnyCPU>"..\..\<span style='color: blue; font-weight: bold'>net20\gacutil.exe" /i ClassLibraryAsAdmin.dll</span> Microsoft (R) .NET Global Assembly Cache Utility. <span style='color: blue; font-weight: bold'>Version 3.5.30729.1</span> Copyright (c) Microsoft Corporation. All rights reserved. <span style='color: blue; font-weight: bold'>Assembly successfully added to the cache</span> D:\temp\net20\AnyCPU>C:\WINDOWS\Microsoft.NET\<span style='color: blue; font-weight: bold'>Framework64\v2.0.50727\regsvcs.exe</span> ClassLibraryAsAdmin.dll Microsoft (R) .NET Framework Services Installation Utility <span style='color: blue; font-weight: bold'>Version 2.0.50727.3053</span> 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 </pre> <br /> 참고로, 쓸모없을 것 같다고 해서 Type Library 파일을 지우면 다음과 같은 예외가 발생할 수 있습니다. ^^<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> System.InvalidCastException was unhandled<br /> HResult=-2147467262<br /> 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}' <span style='color: blue; font-weight: bold'>failed due to the following error: Error loading type library/DLL. (Exception from HRESULT: 0x80029C4A (TYPE_E_CANTLOADLIBRARY)).</span> Source=mscorlib<br /> StackTrace:<br /> Server stack trace: <br /> at System.StubHelpers.StubHelpers.GetCOMIPFromRCW(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget, Boolean& pfNeedsRelease)<br /> at ComBaseClass.IAdminCode.SetRegistryValue()<br /> at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Object[]& outArgs)<br /> at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg)<br /> Exception rethrown at [0]: <br /> at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)<br /> at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)<br /> at ComBaseClass.IAdminCode.SetRegistryValue()<br /> at ConsoleApplication1.Program.Main(String[] args) in D:\...[생략]...\Program.cs:line 15<br /> InnerException: <br /> </div><br /> <br /> <hr style='width: 50%' /><br /> <br /> 여기까지 진행했으면 Component Services 관리 콘솔에 다음과 같이 등록된 것을 확인할 수 있습니다.<br /> <br /> <img alt='complus_as_admin_0.png' src='/SysWebRes/bbs/complus_as_admin_0.png' /><br /> <br /> "관리자 권한"을 필요로 하기 때문에 해당 COM+ 서버의 구동 계정을 "Local SYSTEM"(또는, 그 외의 관리자 계정)으로 변경해 줄 필요가 있습니다. 그런데, 다음과 같이 "Identity" 탭의 "Local System" 계정이 비활성화되어 있는 것을 볼 수 있습니다.<br /> <br /> <img alt='complus_as_admin_1.png' src='/SysWebRes/bbs/complus_as_admin_1.png' /><br /> <br /> 왜냐하면, COM+ 서버에 대해 Local System 권한을 주는 것은 NT 서비스를 통해서만 가능하도록 바뀌었기 때문입니다. 그래서, "Activation" 탭을 이용하여 명시적으로 "Run application as NT Service" 옵션을 설정해 주어야 합니다.<br /> <br /> <img alt='complus_as_admin_2.png' src='/SysWebRes/bbs/complus_as_admin_2.png' /><br /> <br /> 이렇게 바꿔준 후 다시 "Identity" 탭으로 가면 "Local System" 옵션이 활성화되어 있어 설정이 가능합니다.<br /> <br /> <img alt='complus_as_admin_3.png' src='/SysWebRes/bbs/complus_as_admin_3.png' /><br /> <br /> 변경 사항을 적용해 주면, 이제 "서비스 관리자"에서 다음과 같이 NT 서비스로 등록된 것을 확인할 수 있습니다.<br /> <br /> <img alt='complus_as_admin_4.png' src='/SysWebRes/bbs/complus_as_admin_4.png' /><br /> <br /> 이것으로 설정 작업은 모두 끝났습니다. <br /> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로, 이를 직접 사용하는 C# 콘솔 응용 프로그램을 만들어서 다음과 같이 COM+로 등록된 개체를 동적으로 생성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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(); } } } </pre> <br /> 여기까지, 실제로 COM+ 개체를 이용하여 '관리자 권한의 코드를 실행'하는 것이 가능한지 알아보았습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 모두 좋은데, 딱 한가지 마음에 들지 않는 것이 있다면 COM+ 관리 콘솔에서 해당 COM+ Application에 대해 수동으로 NT 서비스 등록을 해주어야 한다는 점입니다. 사내 시스템에 배포하는 프로그램일지라도 이런 식의 설정 작업을 해주어야 한다는 것은 현실성이 없어보입니다.<br /> <br /> 다행히, 이런 작업을 코딩으로 대체하는 것이 가능합니다. 이에 대해서는 아래의 글에서 자세히 나와 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Set Identity to Local System for Com+ Application ; <a target='tab' href='https://social.msdn.microsoft.com/Forums/en-US/df74247b-d107-41cf-bb10-404f976f7d2c/set-identity-to-local-system-for-com-application?forum=winformssetup'>https://social.msdn.microsoft.com/Forums/en-US/df74247b-d107-41cf-bb10-404f976f7d2c/set-identity-to-local-system-for-com-application?forum=winformssetup</a> </pre> <br /> 이를 위해서 우선 COM+ 관리 개체를 참조해야 하는데요.<br /> <br /> <img alt='complus_as_admin_5.png' src='/SysWebRes/bbs/complus_as_admin_5.png' /><br /> <br /> 관리의 편이성을 위해 아래의 방법을 사용해서 Interop DLL을 제거할 수 있으니 참고하십시오.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 레지스트리 등록 및 Interop DLL 없이 COM 개체 사용하는 방법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1180'>http://www.sysnet.pe.kr/2/0/1180</a> </pre> <br /> 종합해 보면, 다음과 같은 단계를 거쳐서 COM+ 응용 프로그램을 생성 및 NT 서비스로 등록해 줄 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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); <span style='color: blue; font-weight: bold'>// "Local System" 권한의 NT 서비스로 등록 catalog.CreateServiceForApplication(appName, appName, "SERVICE_DEMAND_START", "SERVICE_ERROR_NORMAL", string.Empty, @".\LOCALSYSTEM", null, false);</span> </pre> <br /> 참고로, ICatalogObject의 속성에 어떤 값들을 집어넣어야 할 지는 다음의 문서를 보시면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Applications collection ; <a target='tab' href='https://learn.microsoft.com/en-us/windows/win32/cossdk/applications'>https://learn.microsoft.com/en-us/windows/win32/cossdk/applications</a> </pre> <br /> 코드로 모두 작성해 보기는 했지만, 사실 대부분의 작업을 "regsvcs.exe"에서 해주기 때문에 복잡하게 할 것 없이 CreateServiceForApplication 메서드만 마지막에 호출하는 식으로 마무리하는 것이 좋습니다.<br /> <br /> 그래서 최종적으로 정리해 보면, 다음과 같은 등록 절차를 거치면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 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 </pre> <br /> 등록 과정이 다소 복잡해지는 문제가 있지만, 일단 이렇게 구성해 두면 이후부터는 '관리자 권한의 코드'를 실행하는 데 '사용자 동의' 창을 띄우는 불편함은 없어지니 충분한 가치가 있습니다.<br /> <br /> 마지막으로... 한가지 더 정리해야 할 것이 있는데요. 그런데, 굳이 기존의 "NT 서비스로 관리 코드를 실행하는 방법"을 쓰지 않고 COM+로 등록해 주는 것이 더 편한 이유가 있을까요?<br /> <br /> 개인적인 생각으로 2가지 정도를 꼽아보았습니다.<br /> <br /> <ul> <li>보다 편리한 통신: NT 서비스 만으로 구성한 경우, 관리 코드를 실행하기 위해 별도로 소켓이나 WCF를 열어두어야 하는데 그런 작업이 제거됩니다.</li> <li>인스턴스 관리 용이: NT 서비스 만으로 구성한 경우, Start/Stop 등에 대한 관리를 해주어야 하는데 COM+로 해주면 개체 생성과 함께 NT 서비스가 자동 Start 되고 일정 시간의 Idle 시간이 흐른 이후 자동으로 Stop 상태로 전환됩니다.</li> </ul> <br /> <a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=721&boardid=331301885'>첨부된 파일은 위에서 설명한 "ComplusInstaller" 코드까지 모두 포함한 테스트 프로젝트</a>입니다.<br /> </p><br /> <br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1168
(왼쪽의 숫자를 입력해야 합니다.)