Python용 윈도 서비스에서 GUI 앱 실행하기

2018. 11. 6. 16:17Windows 개발

윈도 서비스 프로그램을 만들어서 웹 서버 프로그램을 실행시키고, 웹 서버 실행을 감지하면 자동으로 웹 브라우저를 실행해주려고 했다. 웹 서버를 실행시키는 것은 금방 끝났는데, 윈도 서비스에서 웹 브라우저 실행이 마음대로 되지 않는다. 실행을 시키더라도 화면에 보이지 않고 프로세스가 실행된 것만 보인다.


2015년에 만들어진 레퍼런스이지만, 가장 깔끔하게 잘 만들어진 예제이다. 이대로 잘 실행까지 되어준다면 좋겠는데, CreateProcessAsUser까지 Exception 발생하지 않고 실행되는데 실행되자마자 죽는 것 같은 기분이 든다.

https://pastebin.com/NEmJupch


이것은 C/C+용 예제이지만 기본 원리를 이해하는데 도움이 된다. 서비스 계정으로 윈도 어플리케이션을 실행시키는데 문제가 있을 수 있다는 것을 처음 알게 된 레퍼런스이다.

http://duzi077.tistory.com/25


#서비스에서 UI 있는 프로그램을 실행시킬 수는 있지만 SYSTEM 권한으로 실행되어 UI가 나오지 않는 문제 발생

윈도우는 User Interface, GDI, 커널 메인 객체를 제공한다.

커널은 안전하지만, UI, GDI는 그렇지 않기때문에 추가적인 보안성을 제공하기 위해 윈도우는 stations와 desktops를 사용한다.

Station은 클립보드, desktop등을 포함하는 프로세스와 관련이 있는 보안 객체이고, Desktop은 Station내에 존재하며 논리화면, 윈도우, 메뉴, 훅과 같은 User Interface를 포함하는 보안객체이다. 

로그온 화면을 관리하는 winlogon desktop, 화면보호기를 담당하는 screen saver desktop, 평상시 윈도우 화면을 보여주는 interactive desktop 3가지로 나눠진다.


사용자가 로그온을 하게 되면 WinSta0\Winlogon 에서 smss.exe(세션메니져), winlogon.exe, msgina.dll 를 통해 일련의 logon 작업을 거쳐 최종적으로 Winst0\default 라는 interactive window station을 생성한다.

그러므로 사용자는 Winsta0의 station에서만이 UI 및 키입력을 받을 수 있다. ( 즉 서비스의 경우는 Service-0x0-3e7$\default)로 생성됨)

Winlogon 과 default desktop은 서로 메시지나 대화상자를 공유할수 없다. 자신의 desktop(winlogon, default)에서만 사용이 가능하다.

하지만 클립보드는 Station에서 관리하기 때문에 다른 desktop에서 사용이 가능하다.


Service의 경우 LocalSystem 계정(administrator 계정보다 상위개념)으로 실행되어 Service-0x0-3e7$\default 와 같은 station을 생성하므로 UI 및 키 입력을 받을수 없다. 그래서 서비스내에서 UI를 가진 프로그램을 실행시키기 위해서는 STARTUPINFO 구조체의 si.lpDesktop = "WinSta0\\Default"; 와 같이 설정하여 실행시켜야 한다. 서비스 프로그램중에서 mtask.exe와 같은 프로그램은 WinSta0을 사용한다. 

반면 서비스 프로그램을 UI 또는 키 입력을 받기 위해서는 CreateService로 실행시 SERVICE_INTERATIVE_PROCESS 옵션을 사용하거나 서비스 등록정보에서 로그인정보에 "데스크와 상호 작용"을 설정하면 서비스도 UI 및 키입력을 받을 수 있다.


결국 Session-> WindowStation -> Desktop와 같은 구조를 이루는데 다른 Session에서는 같은 winsta0\defualt를 가졌더라도 메시지 및 UI가 공유될수 없다. 즉 Session 1에 logon한 사용자가 실행시킨 프로그램에서 표시된 대화상자는 Session 2로 logon 사용자의 화면에는 나타나지 않는다는 이야기다.



# 테스트

STARTUPINFO si;

PROCESS_INFORMATION pi;


si.cb = sizeof(STARTUPINFO); 

si.lpReserved = NULL; 

si.lpTitle = NULL; 

si.lpDesktop = L"WinSta0\\Default"; 

si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L; 

si.dwFlags = 0;; 

si.wShowWindow = SW_SHOW; 

si.lpReserved2 = NULL; 

si.cbReserved2 = 0; 


CreateProcess( lpwzPath, NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);


다음과 같이 테스트를 진행해보았으나 여전히 SYSTEM 권한으로 실행.


Python으로 이렇게 하드코어한 Win32 프로그래밍을 하게 될 줄은 꿈에도 몰랐다.

https://stackoverflow.com/questions/17272572/get-window-handler-from-started-process

https://programtalk.com/python-examples/win32gui.ShowWindow/

https://www.programcreek.com/python/example/104576/win32api.SendMessage

https://www.programcreek.com/python/example/8489/win32process.CreateProcess

https://programtalk.com/python-examples/win32con.STARTF_USESTDHANDLES/

https://programtalk.com/python-examples/win32process.STARTUPINFO/

https://www.programcreek.com/python/example/56721/win32process.STARTUPINFO


이것은 subprocess.Popen으로 실행시킨다음 해당 프로세스의 윈도 핸들을 얻어오는 방법이다. 콜백을 사용하는 것이 마음에 안들고, 실제도 동작되는 형태도 별로지만 이 방법 밖에 없는 듯.

http://ramblings.timgolden.me.uk/2007/09/26/how-do-i-get-the-window-for-a-subprocesspopen-object/


윈도 서비스에서 GUI 앱을 실행 시키려고 하면 계속 (1314, 'SetSystemTime', 'A required privilege is not held by the client.')  이라는 메시지가 떠서 삽질을 오랫동안 했다. 윈도 서비스 권한이 일반 앱을 실행시키는 권한과 달라서 발생하는 문제로 추정된다.

https://mail.python.org/pipermail/python-win32/2009-June/009267.html

https://stackoverflow.com/questions/33816942/why-does-the-createprocessasuser-have-error-1314-in-my-code

https://stackoverflow.com/questions/22615365/using-win32-api-createprocessasuser-in-python

http://python.6.x6.nabble.com/Interacting-with-the-desktop-as-a-service-on-Vista-td1955119.html


굳이 화면에 보이지 않아도 되는 웹 서버는 subprocess.popen으로 쉽게 처리 완료했지만, GUI가 있는 웹 브라우저는 해당 사항이 없다. (윈도 서비스에서 실행하고자 할때만 해당)

https://docs.python.org/2/library/subprocess.html

https://www.programcreek.com/python/example/5376/subprocess.STARTUPINFO

https://docs.python.org/3/library/subprocess.html#subprocess.STARTF_USESHOWWINDOW

https://stackoverflow.com/questions/15899798/subprocess-popen-in-different-console

https://hashcode.co.kr/questions/336/%EC%99%B8%EB%B6%80-%EB%AA%85%EB%A0%B9%EC%96%B4%EB%A5%BC-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EC%9C%BC%EB%A1%9C-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0

  • 프로필사진
    2020.01.06 18:04

    비밀댓글입니다

    • 프로필사진
      나숑2020.01.06 18:20 신고

      제가 생각하기에, 배치 파일은 shell 모드 (코맨드 프롬프트)에서 실행이 가능한 shell script이기 때문에 subprocess.popen으로 실행시킬 수 없을 것 같습니다.

  • 프로필사진
    2020.01.06 18:12

    비밀댓글입니다

    • 프로필사진
      나숑2020.01.06 18:23 신고

      배치 파일을 실행시키는 대신, 직접 java.exe를 실행 시켜보시기 바랍니다. 물론 그러려면 set으로 임시환경변수를 설정하는 것이 아니라, 고정된 위치에 환경변수를 저장하는 파일을 만들고 서비스 프로그램이 실행될 때 참조하도록 수정해야 할 것입니다.

  • 프로필사진
    kky31272020.01.06 20:00 신고

    감사합니다! 참고하고 열심히만들어보겠습니다