SimpleIsBest.NET

유경상의 닷넷 블로그
이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다. 저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나 토론을 할 수도 있습니다.

요즘은 매우 바쁘답니다. 그래서... 이제야 글하나를 겨우 쓰게 되었다는... -_-;

Run As Implementation using Process class

친절한 닷넷씨

눈코 뜰새 없이 바쁘다는 말을 들어본 적이 있는가? 아놔... 요즘 필자가 하고 싶은 말이 바로 이 말이 되겠다. 삼성동에서 종로와 분당을 넘나들며 여기저기를 휘젓고 다니는 필자는 Vista 출시로 인한 어플리케이션 호환성 문제에 대한 컨설팅을 위주로 해서 이런저런 프로젝트에 대추 놔라 감 놔라 하면서 졸라 아는 척만 하고 다니고 있는 중이다.

엔지니어에게 눈코 뜰새 없이 바쁘다는 것은 좋지 않은 것이다. 특히 우리들 같이 춥고 배고프고 눈곱 끼고 등 시려운 개발자들에겐 더욱 그러하며, 최근 MS 관련 기술로 밥 벌어 먹고 사는 인간들에게는 더더욱 그렇다. WPF 니 WCF 니 새로운 기술들이 쏟아져 나오는 상황에 새로운 운영체제로 Vista 란 놈까지 나타났으니 준비해야 할 것이 오죽이나 많은가?

엔지니어가 바쁘다는 것은 회사의 입장으로 보았을 때는 더 많은 매출과 더 많은 비즈니스 찬스를 갖게 되어 회사가 기뻐할 수 있을 지는 모른다. 하지만 장기적으로 보았을 때 엔지니어가 지속적으로 바쁜 나날을 보내게 된다면 좋을 것은 하나도 없다고 본다. 그들이 바쁜 나날을 보내게 되면 당연히 새로운 기술을 준비하거나 현재의 기술에 대한 심화를 할 겨를이 없을 것이고, 바쁜 일과에 찌들다 보면 시간이 생기더라도 지쳐버린 몸과 정신에 주저 앉아 버릴 수 있기 때문이다. 이러다 보면 그는 더 이상 엔지니어로서 롱런 하기 힘들게 되어 버릴 것이고 많은 경우에 burn-out 되어 버리는 경우를 종종 보곤 한다. 엔지니어는 refresh할 시간이 필요하다.

안타까운 일이지만 SI 업계에 종사하고 있다면 이러한 현상은 더욱 두드러지곤 한다. 더욱 안타까운 것이 이러한 문제에 대한 특별한 대안이 없다는 것이다. 언젠가는 숙련된 개발자 찾기가 하늘에 별따기 보다 힘든 상황이 올지도 모른다. (그때를 대비해서 겨울잠이 나 자볼까 생각도 하는데... -_-;)

About Run As...

개소리는 집어 치우고 본론으로 들어가 보자.

Run as는 Windows 2000 시절부터 지원되는 윈도우 운영체제의 한 기능이다. 역사(?)를 이야기 하자면 Windows NT가 설계되었을 때 관리자(administrator)와 일반 사용자가 엄격히 구분되어 각기 주어진 권한 내에서 작업을 하기를 기대했다. 결과적으로 이런 기대는 말짱 꽝이 되어 버렸다. 일반 사용자가 할 수 있는 일이라곤 별로 없었기 때문에 클라이언트 버전의 윈도우에서 대부분 아니 거의 전부의 사용자가 관리자 계정에서 작업하기를 바랬기 때문이다. 당연한 것이 서버도 아닌 내 PC를 내 마음대로 사용하지 못한다는 것이 가당키나 한 일인가? 일반 사용자 계정으로 로그온 해서 일반 작업을 하다가 설치를 하거나 시스템을 관리해야 하는 상황이 오면 로그 오프를 하고 다시 관리자 계정으로 로그온 하여 작업을 해야 하니 그 불편함이 이루 말할 수 없었던 것이다.

그래서 Windows 2000에서는 필요에 따라 로그온 된 사용자 계정이 아닌 특정 계정으로 프로그램(프로세스)를 구동시키는 방법으로써 Run As 기능이 추가된 것이다. 사용자가 사용할 수 있는 RunAs.exe 란 커맨드 라인 유틸리티가 제공되었고 쉘(shell)은 "다음 계정으로 수행..." 이라는 메뉴가 추가 되었으며 당연 빤쑤로 관련 API로서 CreateProcessAsUser() 라는 함수가 제공되었다.

Windows 2000의 노력에도 불구하고 큰 진전은 없었다. 여전히 사용자들은 편리함을 추구했고 관리자 권한을 갖는 계정을 사용하기를 원했다. 덕분에 관리자 계정에 의해 구동되는 쉘과 브라우저 역시 관리자 권한을 갖게 되고, 이들에 의해 다운로드 되고 구동되는 바이러스와 스파이웨어 역시 관리자 권한을 갖게 되는 것이다. 바이러스와 스파이웨어가 시스템에 대해 거의 무한정의 권한을 갖고 시스템을 휘젓는 상황이 자주 발생하게 된 것이다.

그래서... 바이러스와 스파이웨어들에 대해 골머리를 앓던 마이크로소프트는 극단(?)의 조치를 취하게 되는데... 그것이 바로 Windows Vista의 UAC(User Access Control)이란 기능이다. UAC는 관리자 계정이 로그온 하면 관리자 권한을 제한하는 기능으로써 보다 윈도우 시스템을 안전하게 유지하고자 하는 마이크로소프트의 몸부림이라고 볼 수 있겠다. UAC에 대해서 이 글에서는 더 이상 언급하지 않겠다. UAC가 무엇인지 좀 더 알고 싶다면 Windows Vista Application Development Requirements for User Account Control 란 글을 읽어 보기 바란다. 그리고 정성태씨의 홈페이지에 가면 Vista와 UAC에 대한 아주 좋은 글을 많이 접할 수 있다.

필자가 Run As 기능과 관련 API에 관심을 갖게 된 동기도 Vista에서 UAC를 피하는 방법이 있지 않을까 하고 삽질을 하던 차에 관심을 갖게 된 것이다. 결론은 UAC를 꺼버리는 것 외엔 UAC를 피하는 쓸만한 방법이 거의 없다는 것이다(MS가 독하게 맘을 먹은 듯하다). 굳이 피할 이유도 없지만 말이다.

Using P/Invoke

Run As를 구현하기 위해서는 CreateProcessAsUser() 라는 API를 호출하면 된다. 하지만 이 API가 요구하는 특권(priviliege)은 매우 높아서 일반 관리자 계정도 이 권한을 기본적으로 갖고 있지 않다. SYSTEM 계정이라면 모를까... 그렇다면 Runas.exe 커맨드와 쉘은 어떻게 다른 계정으로 작동하는 프로세스를 구동할 수 있을까?

답은 Secondary Logon 이라고 하는 시스템 서비스가 갖고 있다. 이 서비스는 SYSTEM 계정으로 작동하는데, Runas.exe 커맨드나 쉘의 요청을 받아 CreateProcessAsUser() 함수를 호출하여 현재 로그온 된 사용자가 아닌 다른 사용자로서 프로세스를 구동하게 해준다.

삽질 잘하는 필자, 테스트를 위해서 CreateProcessAsUser를 이용하여 Run as를 구현해 보고 싶었다. CreateProcessAsUser를 호출하려면 먼저 LogonUser 라는 API를 먼저 호출해야만 하고 이 함수가 반환하는 액세스 토큰(access token)을 CreateProcessAsUser에게 매개변수로 넘겨주어야 한다.

닷넷에서 이 두 짐승들을 호출하려면 매우 많은 상수와 P/Invoke 선언을 해주어야 한다. LogonUser()에 대한 interop 코드는 필자의 ASP.NET 상에서 폴더 공유를 다루는 글에서 찾을 수 있다. 상당히 복잡타.... CreateProcessAsUser 함수에 비하면 LogonUser 함수는 양반 축에 속한다. CreateProcessAsUser 함수는 자그마치 11개의 매개변수를 취하며 그 중 다수가 구조체이기 때문에 더욱 더 많은 Interop 코드를 요구한다.

LogonUser 함수와 CreateProcessAsUser 함수를 사용한 Run As 기능의 구현이 매우 복잡하다는 것을 마이크로소프트도 눈치를 챘는지 Windows XP에서는 이들 두 함수를 한방에 처리할 수 있는 CreateProcessWithLogonW 라는 새로운 API를 제공한다. 이 함수는 내부적으로 Run as가 요구하는 모든 더러운 작업들을 처리해 줄 뿐만 아니라, 이 함수를 호출하기 위해 요구하는 특권 역시 거의 없다.

하지만 이 함수도 Win32 API일 뿐이다. 11개의 매개변수를 취하는 CreateProcessWithLogonW()를 닷넷에서 호출하려면 역시 매우 많은 상수들과 구조체, 그리고 함수 자체에 대한 선언을 해주어야만 한다.

필자가 구현해 본 결과(필자가 생각하기에도 필자는 정말 삽질 하나는 끝내게 잘한다... -_-), LogonUser, CreatProcessAsUser, CreateProcessWithLogonW 함수들과 관련 상수들, 구조체들에 대한 Interop '선언'하는 데만 500 여 라인의 코드가 필요했다. 물론 그 중 1/3이 주석이었지만 말이다.

여기서 그 코드를 보이진 않겠다. 보여줄 가치도 없기에...

Using System.Diagnostics.Process class

이번 포스트에서 하고 싶은 이야기는 우리의 친절한 닷넷 프레임워크 2.0은 이미 Run As 기능을 구현하고 있다는 것이다. 조금만 읽어 보면 필자가 앞서 Win32 API들에 대한 Interop 선언을 한 것이 왜 삽질인가를 이제 이해를 할 것이다.

System.Diagnostic.sProcess 클래스는 이미 시스템상에서 구동중인 프로세스의 정보를 제공할 뿐만 아니라 Start 메쏘드를 통해 새로운 프로세스를 구동하는 기능도 제공하고 있다는 것은 이미 잘 알고 있을 것이다. 이 Start 메쏘드는 ProcessStartInfo 클래스를 매개변수로 취하기도 하는데 이 클래스는 새로 시작할 프로세스에 대한 다양한 정보(환경변수, 작업 디렉터리, 윈도우 최대화 여부 등)를 설정하는데 사용하곤 한다.

닷넷 프레임워크 2.0에는 ProcessStartInfo 클래스에 새로운 속성들이 추가되었는데 이들 중 UserName 속성, Password 속성, Domain 속성, LoadUserProfile 속성이 Run As를 구동하는데 사용되는 속성들이다. 즉, ProcessStartInfo 클래스의 이들 속성에 프로세스를 구동하는데 사용할 사용자 계정, 암호, 계정의 도메인(Windows 도메인)을 설정하고 Process.Start 메쏘드를 호출하면 해당 계정으로 작동하는 프로세스가 만들어진다는 것이다. 이미 눈치를 챈 독자들도 있겠지만 Process.Start 메쏘드가 특정 계정으로 프로세스를 구동할 때 사용하는 API가 바로 앞서 소개한 바 있는 CreateProcessWithLogonW 라는 API 가 되겠다. 이것이 바로 Run As 기능이 아니고 무엇인가?

    1 // 프로세스 시작 정보 설정

    2 ProcessStartInfo startInfo = new ProcessStartInfo();

    3 startInfo.FileName = txtCommandLine.Text;

    4 // RunAs를 수행하기 위해 사용자 계정 정보를 초기화한다.

    5 startInfo.UserName = txtUserId.Text;

    6 startInfo.Password = password;

    7 startInfo.Domain = txtDomain.Text;

    8 // 사용자의 프로파일(HKEY_USERS 레지스트리 키)의 로드 여부 설정

    9 startInfo.LoadUserProfile = chkLoadProfile.Checked;

   10 // 최대화 하여 시작하도록 설정

   11 // 이 값 역시 UseShellExecute가 false 이면 값을 명시적으로 주더라도 유효하지 않다. 즉, 무시된다.

   12 startInfo.WindowStyle = ProcessWindowStyle.Maximized;

   13 // UserName, Password, Domain 속성이 설정되면 UseShellExecute는 false 이어야 한다.

   14 // UseShellExecute 속성이 false 이면 ErrorDialog 속성값은 무시된다.

   15 startInfo.UseShellExecute = false;

   16 try {

   17     // CreateProcessWithLogonW 를 통해 프로세스 구동

   18     Process.Start(startInfo);

   19 }

   20 catch (Exception ex) {

   21     MessageBox.Show(ex.Message + "\r\n\r\n" + ex.StackTrace.ToString(),

   22         "Process.Start() Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

   23 }

리스트1. ProcessStartInfo 클래스와 Process.Start를 이용한 RunAs 구현

소소스 코드에 주석으로 대부분 설명이 되어 있지만 UserName, Password 속성을 주면 UseShellExecute 속성을 false로 설정해야만 하며(15번째 라인), UserName, Password 속성이 주어짐에 따라 WindowStyle 속성, ErrorDialog 속성 등의 값은 무시된다(12번째 라인)는 점을 알아 두자.

재미삼아 만들어 본 예제 코드 화면은 다음과 같다.

예제 코드 화면
화면1. 닷넷으로 구현한 RunAs 유틸리티 화면

How about C++/CLI ?

이제부터 필자가 얘기할 내용은 지난 포스트에서 소개한 C++/CLI에 대한 내용이다. C++을 전혀 모르거나 관심이 없는 독자들은 시간 낭비하지 말고 다른 일을 하는 것이 좋을 수도 있다. 하지만 한번쯤은 읽어 보아도 손해는 되지 않을 것 같다는 생각이 똥꼬 깊숙한 곳에서 요동을 치는 이유는 무엇일까? 얌... 드러... -_-;

Process 클래스의 Start 메쏘드를 사용할 때의 단점은 새로이 생성하는 프로세스가 생성하는 메인 윈도우의 위치를 지정한다거나 윈도우를 최대화 한다거나 최소화 하는 등, 원래 CreateProcessWithLogonW API 함수가 제공하는 모든 기능을 모두 사용할 수 없다는 것이다. 그렇다고 조낸 복잡하게 CreateProcessWithLogonW 함수와 관련된 상수들, 구조체들을 모두 선언하자니 삽질이고...

그래서 지난 포스트에서 소개한 바 있는 방법을 써먹어 보기로 했다. 즉, CreateProcessWithLogonW를 호출하는 코드를 C++/CLI로 작성하고 이 코드를 닷넷 DLL 어셈블리로서 컴파일 한다. 그리고 UI를 담당하는 코드는 편리(?)한 C#으로 작성하는 것이다. 필자의 예제처럼 간단한 UI 만을 가진다면 전체 코드를 C++/CLI로 작성할 수도 있겠지만 업무용 어플리케이션이나 상업용 유틸리티 등과 같은 복잡한 UI를 갖는 경우에는 생각해 볼만한 방법이기 때문이다.

다음 리스트2 코드는 C++/CLI를 이용하여 CreateProcessWithLogonW API 함수를 호출하는 래퍼 클래스를 보여주고 있다. 복잡한 매개변수를 취하지 않고 필요한 매개변수만을 취하고 있으며, C/C++로 API를 호출하는 코드를 작성하듯이 Win32에서 제공하는 다양한 상수와 구조체를 사용하고 있다. 물론 이들 상수와 구조체에 대한 선언은 windows.h 헤더에 있는 것을 모두 사용할 수 있다. 상세한 설명은 글을 지루하게 만들 뿐만 아니라 필자의 귀차니즘을 심히 자극하는 바... C++/CLI를 정말 공부하고 싶은 독자들은 스스로 공부하길 바란다...

#define WINVER    0x500

#define _WIN32_WINNT 0x500

 

#include <vcclr.h>

#include <windows.h>

#include <strsafe.h>

 

public ref class Win32

{

public:

    // CreateProcessWithLogonW API를 호출하여 프로세스를 생성한다.

    // (필요한 매개변수만을 취하여 복잡도를 낮추었다)

    static void CreateProcessWithLogonW(String ^userId, String ^password,

            String ^domain, String ^commandLine, bool loadProfile)

    {

        PROCESS_INFORMATION pi;

        STARTUPINFO si;

        BOOL result;

 

        // CreateProcessXXX 함수는 commandline 의 문자열을 수정할 수도 있다.

        // 따라서 수정 가능한 버퍼를 전달해야만 한다.

        // 닷넷 문자열 객체의 문자열 포인터를 구해 복사한다.

        TCHAR lpCommandLine[MAX_PATH];

        pin_ptr<const TCHAR> lpPtr = ::PtrToStringChars(commandLine);

        ::StringCchCopy(lpCommandLine, MAX_PATH, lpPtr);

 

        // 사용자 ID, 암호, 도메인 문자열 포인터를 구한다.

        // unmanaged 코드를 호출하기 위해서는 마샬링을 수행하거나

        // fixed(pin) 포인터가 필요하다.

        pin_ptr<const TCHAR> lpUserId = ::PtrToStringChars(userId);        // pin 포인터 예제

        LPCTSTR lpPassword =

            (LPCTSTR)Marshal::StringToHGlobalUni(password).ToPointer();    // 마샬링 예제(메모리 해제 필수)

        pin_ptr<const TCHAR> lpDomain = ::PtrToStringChars(domain);        // pin 포인터 예제

 

        DWORD dwLogonFlags = (loadProfile == true ? LOGON_WITH_PROFILE : 0);

 

        // 구조체 초기화

        ::ZeroMemory(&pi, sizeof(pi));

        ::ZeroMemory(&si, sizeof(si));

 

        // STARTUPINFO 를 초기화 한다.

        si.cb = sizeof(STARTUPINFO);

        si.dwFlags = STARTF_USESHOWWINDOW;    // wShowWindow 필드가 유효함

        si.wShowWindow = SW_MAXIMIZE;            // 윈도우 최대화

 

        // CreateProcessWithLogonW 호출

        try {

            result = ::CreateProcessWithLogonW(

                lpUserId,                        // user account name (user id)

                lpDomain,                        // domain

                lpPassword,                        // password

                dwLogonFlags,                    // logon flags

                NULL,                            // application name

                lpCommandLine,                    // command line

                0,                                // creation flag & process priority (기본값 사용)

                NULL,                            // environment string block (부모 프로세스 상속)

                NULL,                            // current directory (부모 프로세스 상속)

                &si,                            // STARTUPINFO structure

                &pi);                            // PROCESS_INFORMATIOn structure (for result)

            if (result == false) {

                throw gcnew Win32Exception(::GetLastError());

            }

        }

        finally {

            IntPtr ptr((void *)lpPassword);

            Marshal::FreeHGlobal(ptr);

        }

 

        // 호출자가 핸들을 필요로 하다면 반환해야 하지만 이 예제에서는 단순히 핸들을 닫는다.

        // 핸들을 닫는다고 프로세스가 종료하는 것은 아니다.

        ::CloseHandle(pi.hProcess);

        ::CloseHandle(pi.hThread);

    }

};

리스트2. C++/CLI를 이용하여 Win32 API를 호출하는 래퍼 클래스

이제 리스트2 에서 작정한 코드를 컴파일 하여 DLL로 만들고 C# 프로젝트에서 참조하여 Win32 클래스의 메쏘드를 호출하기만 하면 된다. 다음과 같이 말이다.

// C++/CLI를 통해 CreateProcessWithLogonW 호출

Win32.CreateProcessWithLogonW(txtUserId.Text, txtPassword.Text, txtDomain.Text,

    txtCommandLine.Text, chkLoadProfile.Checked);

Conclusion

우리의 친절한 닷넷씨는 Process 클래스와 ProcessStartInfo 클래스를 이용하여, 로그온 한 사용자 계정이 아닌 특정 계정으로 프로세스를 구동하는 Run As 기능을 제공한다. 어떻게 보면 아무짝에도 쓸모가 없을 수 있지만 필요에 따라서 요긴하게 써먹을 수도 있으니 잘 기억해 두었다가 나중에 써먹을 수 있도록 노력해 보자.

덤으로 Process 클래스와 C++/CLI를 사용한 예제 코드가 제공되니 관심 있는 독자는 다운로드 받아보길 바란다.

강대욱!!! 코딩 백번 !!!



Comments (read-only)
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 눈꽃천사 / 2007-02-09 오전 8:26:00
좋은 강좌 잘 보고 있습니다.
내공 향상을 위해 오늘도 으쌰!
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 컴맹 / 2007-02-09 오전 10:15:00
앗.. 오늘도 새로운 강좌가 올라왔군요.^^
잘보겠습니다.^^
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 위시 / 2007-02-09 오전 10:15:00
오늘도 좋은강좌~~ 으쌰~~
오늘도 코딩 백번~!!!
ㅋㅋㅋㅋㅋㅋ
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 정성태 / 2007-02-09 오후 1:00:00
간단하게 댓글로 달으셨던 도구가 바로 이거였군요. 이렇게 깊은 사연이... ^^

참고로, 제 홈페이지 링크는... 비스타에 관련된 토픽만 보고 싶은 경우 "wtype=18" 이라는 인자만 더해 주시면 됩니다. ^^ 다음과 같이. ^^

http://www.sysnet.pe.kr/Default.aspx?mode=2&wtype=18
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 박길선 / 2007-02-11 오후 8:45:00
헛 이런방법이;;
3년전에 런처프로그램에서 메인프로그램 실행권한을 바꿔서 실행해야할 필요가 있어
cmd에 runas.exe를 사용하여 해당 프로그램을 실행하고 암호 넣는것때문에 잠시 골치 아펐던 기억이 나는군요 ;
이리 간단한 방법이 ㅎㅎ
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 이경원 / 2007-02-12 오전 9:59:00
몇번 더 읽어 바야 이해를 할듯.ㅡ.ㅡ 머리가 안좋은 관계로....

좋은 포스트 읽고 갑니다..^^
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 주경진 / 2008-11-28 오후 4:15:00
C++을 사용하여 서비스 응용프로그램을 개발하는 사람인데요...
CreateProcessWithLogonW API는 System계정에서 실행을 하게 되면 안되지 않나요?
요즘 이것 때문에 골머리를 썩고 있답니다.
msdn에 보면 Interactive Logon Account에서만 실행이 가능하다. 뭐 이런건데...
Service를 LOCAL_SYSTEM으로 실행한 후 이 Service응용프로그램이 administrator계정으로 fork를 하고자 할 때 해결방법을 알고 계신가요?
CreateProcessAsUser에서 리턴값 1314 코드의 장벽에 무릎을 꿇고... CreateProcessWithLogonW를 찾았으나...
앞에 말씀드린 것과 같은 문제가 있군요... ^^;;
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 블로그쥔장 / 2008-12-02 오후 4:16:00
주경진//
안녕하세요.
LogonUser를 사용하여 토큰을 생성하신 후
이 토큰에 데스크톱을 액세스할 수 있는 권한을 할당 하신 후에 CreateProcessAsUser를 호출하셔야 하는 것으로
알고 있습니다. 반드시 관리자 권한이 필요하지 않다면 현재 로그온된 데스크톱의 explorer.exe 프로세스의
액세스 토큰을 구하고 이 액세스 토큰을 사용하여 CreateProcessAsUser를 호출하셔도 됩니다만....
#re: 친절한 닷넷씨 : Process 클래스를 이용한 RunAs 구현 / 주경진 / 2008-12-23 오후 3:55:00
쥔장님께...
안녕하세요? 답변 감사드립니다.
다양한 제약 사항이 존제 하여 쉽게 구현하지 못하고 있네요.. ^^;;
반드시 SYSTEM계정으로 실행된 서비스 내에서 특정한 logonuser 계정을 사용하여 프로세스를 실행 할 수 있어야 한답니다.
(꼭 admin이 아닌 다른 계정도 포함)
GUI를 사용할 수도 있기 때문에 때문에 데스크톱과 상호작용도 해야 하고... 부팅시 서비스가 자동으로 실행을 해줘야 하기 때문에
winlogon.exe나 explorer.exe를 사용 할 수는 없었어요... logon을 하지 않은 상태에서는 백그라운드에 process가 뜨질 못합니다.

그런데 또.. LogonUser의 토큰을 사용하여 프로세스를 실행시키면 GUI가 정상적으로 생성되지 않는 문제가 있더라구요.
여기서 세션 문제가 걸리는 듯 합니다. 현재 이 부분에서 고민을 하구 있는 중입니다.
수고 하십시오.. 저는 또 삽질 하러가야 겠어요..... ^^;;