지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]

[강좌로 보는 서비스 프로그래밍] 외적으로 보여지는 서비스 [2/?]
[강좌로 보는 서비스 프로그래밍] 설치와 제거 [3/?]

지난 장에서는 실제 서비스 몸통을 만들기 전에 만들어진 서비스를 설치/제거하는 아주 초간단 구현을 살펴보았다.
원래는 이번장에서 서비스를 중지, 일시중지, 재시작 상태를 변경시키는 기능에 대하여 진행하려고 하였으나
몇몇 분의 요청에 의하여 지금까지 열거된 기능중에 필요한 함수 몇개를 먼저 소개하고자 한다.

1. 사용자 계정에 권한을 할당하는 UserPrivileges 함수.
#define TARGET_SYSTEM_NAME L"."
LSA_HANDLE GetPolicyHandle()
{
    LSA_OBJECT_ATTRIBUTES ObjectAttributes;
    WCHAR SystemName[] = TARGET_SYSTEM_NAME;
    USHORT SystemNameLength;
    LSA_UNICODE_STRING lusSystemName;
    NTSTATUS ntsResult;
    LSA_HANDLE lsahPolicyHandle;

    // Object attributes are reserved, so initialize to zeros.
    ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));

    //Initialize an LSA_UNICODE_STRING to the server name.
    SystemNameLength = wcslen(SystemName);
    lusSystemName.Buffer = SystemName;
    lusSystemName.Length = SystemNameLength * sizeof(WCHAR);
    lusSystemName.MaximumLength = (SystemNameLength+1) * sizeof(WCHAR);

    // Get a handle to the Policy object.
    ntsResult = LsaOpenPolicy(
       &lusSystemName,    //Name of the target system.
       &ObjectAttributes, //Object attributes.
       POLICY_ALL_ACCESS, //Desired access permissions.
       &lsahPolicyHandle  //Receives the policy handle.
        );

    if (ntsResult != ERROR_SUCCESS)
        return NULL;
    return lsahPolicyHandle;
}

BOOL InitLsaString(PLSA_UNICODE_STRING pLsaString, LPCWSTR pwszString)
{
    DWORD dwLen = 0;

    if (NULL == pLsaString)
        return FALSE;

    if (NULL != pwszString)
    {
        dwLen = wcslen(pwszString);
        if (dwLen > 0x7ffe)   // String is too large
            return FALSE;
    }

    // Store the string.
    pLsaString->Buffer = (WCHAR *)pwszString;
    pLsaString->Length =  (USHORT)dwLen * sizeof(WCHAR);
    pLsaString->MaximumLength= (USHORT)(dwLen+1) * sizeof(WCHAR);

    return TRUE;
}

BOOL GetUserSID(PSID sid, const char* username)
{
   DWORD SidBufSz;
   char DomainNameBuf[256] = {0};
   DWORD DomainNameBufSz;
   SID_NAME_USE SNU;
   DomainNameBufSz = 256;

   if(!LookupAccountName(NULL, username, sid, &SidBufSz, DomainNameBuf, &DomainNameBufSz, &SNU))
       return FALSE;
    return TRUE;
}

BOOL UserPrivileges(PSID AccountSID, LSA_HANDLE PolicyHandle, wchar_t* pszPrivilege)
{
    LSA_UNICODE_STRING lucPrivilege;
    NTSTATUS ntsResult;

    // Create an LSA_UNICODE_STRING for the privilege names.
    if (!InitLsaString(&lucPrivilege, pszPrivilege))
        return FALSE;

    ntsResult = LsaAddAccountRights(
        PolicyHandle,  // An open policy handle.
        AccountSID,    // The target SID.
        &lucPrivilege, // The privileges.
        1              // Number of privileges.
        );

    if (ntsResult == ERROR_SUCCESS)
        return TRUE;
    else
        return FALSE;
}
MSDN에 있는 샘플 소스를 가져다가 추려서 사용하고 있는 기능입니다. 윈도우에서 계정이나 보안에 상관있는 API 등을 호출하거나 사용하려면
권한에 관한 문제에 자주 걸리는데, 저것 말고도 알아야 할 기능들이 참 많죠.

2. 프로세스에 권한을 할당하는 ProcessPrivilege 함수
BOOL ProcessPrivilege(HANDLE pid, char* pszPrivilege, BOOL bEnable)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;

    // Get a token for this process.
    if (!OpenProcessToken(pid, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    // Get the LUID for the shutdown privilege.
    LookupPrivilegeValue(NULL, pszPrivilege, &tkp.Privileges[0].Luid);

    tkp.PrivilegeCount = 1;  // one privilege to set
    tkp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : SE_PRIVILEGE_REMOVED;

    // Get the shutdown privilege for this process.

    return AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
}
유저도 권한을 가져야 하겠지만, 유저가 실행시킨 프로세스도 권한이 있어야 할때가 많습니다.

3. 사용자 데스크탑에 GUI를 띄워주는 CreateProcessToDesktop 함수.
BOOL CreateProcessToDesktop(char* pszExecute, UINT sampling)
{
    HANDLE hd = OpenProcess(PROCESS_ALL_ACCESS, FALSE, sampling);
    if(hd == NULL)
        return FALSE;

    HANDLE token = NULL;
    if(OpenProcessToken(hd, TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE, &token) == FALSE)
    {
        CloseHandle(hd);
        return FALSE;
    }

    HANDLE newtoken = NULL;
    if(::DuplicateTokenEx(token, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, NULL,
          SecurityImpersonation, TokenPrimary, &newtoken) == FALSE)
    {
        CloseHandle(hd);
        CloseHandle(token);
        return FALSE;
    }

    CloseHandle(token);

    void* EnvBlock = NULL;
    CreateEnvironmentBlock(&EnvBlock, newtoken, FALSE);

    STARTUPINFO si = {0};
    PROCESS_INFORMATION pi = {0};

    if(::CreateProcessAsUser(newtoken,
        pszExecute,
        NULL,
        NULL,
        NULL,
        FALSE,
        NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT |
        CREATE_NEW_CONSOLE  | CREATE_SEPARATE_WOW_VDM |
        CREATE_NEW_PROCESS_GROUP,
        EnvBlock,
        NULL,
        &si,
        &pi) == TRUE)
    {
        CloseHandle(hd);
        CloseHandle(newtoken);
        return TRUE;
    }

    CloseHandle(hd);
    CloseHandle(newtoken);
    return FALSE;
}
예전에는 저걸 구현하기 위해 OpenWindowStation 과 OpenDesktop 을 이용해서 오만가지 필드를 채워가며 굉장히 있어보이는 긴 코드를
이용해서 구현했었습니다. 위 함수는 뛰우고 싶은 윈도우 세션을 구해서, 해당 세션에 있는 프로세스 아무거나 하나 가져온다음에, 그 핸들을
복사해서 그 핸들이 존재하는 세션에 그냥 띄워 버리는 겁니다.

4. 프로그램이 서비스로 구동중인지 알아보는 IsServiceMode 함수
BOOL IsServiceMode()
{
    // need _WIN32_WINNT as 0x0500 or later
    return GetConsoleWindow() ? FALSE : TRUE;
}
서비스 일경우는 해당 GetConsoleWindow 가 NULL 을 리턴하고, 응용프로그램 모드에서 돌아가면 해당 콘솔의 핸들이 리턴됩니다.
이걸 이용해서 서비스로 구동중인지, 그냥 응용프로그램 모드로 구동중인지를 내부적으로 감지해서 다르게 동작하도록 구현하는게 가능합니다.

5. 해당 서비스에 걸린 디펜던시를 찾는 FindDependencyService 함수
UINT FindDependencyService(char* pszServiceName, LPENUM_SERVICE_STATUS& lpDependencies)
{
    SC_HANDLE hScm = OpenSCManager(".", NULL, SC_MANAGER_CREATE_SERVICE);
    if(hScm == NULL)
        return 0;

    SC_HANDLE hSrv = OpenService(hScm, pszServiceName, GENERIC_READ);
    if(hSrv == NULL)
    {
        CloseServiceHandle(hScm);
        return 0;
    }

    DWORD dwBytesNeeded = 0, dwCount = 0;
    if(EnumDependentServices(hSrv, SERVICE_STATE_ALL, NULL, 0, &dwBytesNeeded, &dwCount))
        goto ERROR_LABEL;
    else if(GetLastError() == ERROR_MORE_DATA)
    {
        lpDependencies = (LPENUM_SERVICE_STATUS) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytesNeeded );
        if(lpDependencies == NULL)
            goto ERROR_LABEL;

        if ( !EnumDependentServices( hSrv, SERVICE_STATE_ALL, lpDependencies,
            dwBytesNeeded, &dwBytesNeeded, &dwCount ) )
        {
            HeapFree(GetProcessHeap(), 0, lpDependencies);
            lpDependencies = NULL;
            goto ERROR_LABEL;
        }
    }

ERROR_LABEL:
    CloseServiceHandle(hSrv);
    CloseServiceHandle(hScm);
    return dwCount;
}
그냥 지정된 서비스에 디펜던시가 걸린 목록을 쭉 뽑아주는 기능을 담당합니다.

#. 그외 윈도우 보안과 관련된 링크들
    [자료] 로그온 사용자, 이름과 유저그룹 정보 
    [MSDN] 윈도우 보안 카테고리
    [MSDN] Authentication Functions and Samples
    [MSDN] Authorization Functions and Samples
    [codeguru] 서비스 카테고리
    [codeproject] System Programming - Windows Services

추릴 만한 함수들과 관련 링크들은 해당 페이지에 지속적으로 업데이트를 하겠습니다.

지난 내용
[강좌로 보는 서비스 프로그래밍] 서비스란 무엇인가? [1/?]


지난 내용에서 서비스가 구동되는 순서는 어떠한지, 또한 서비스 프로그래밍의 장/단점은 어떠한지에
대하여 간략하게 살펴보았다. 자세한 기술 관점으로 넘어가기전에 서비스 관리자에서 (services.msc)
살펴볼 수 있는 항목들에 대하여 간략하게 설명하고, 이러한 항목들이 프로그래밍되는 API 와 연관되는지
살펴보도록 하겠다.

전체적으로 외향적인 부분을 살펴보는 것이므로, 주의깊게 살펴보지 않아도 작업하는데 지장은 없다.

<윈도우에서 제공하는 서비스 관리자의 모습>

가장 일반적으로 접하게 되는 GUI로써, 서비스를 시작/중지/일시 정지 시킬 수 있고 서비스의 현재 동작
상태를 살펴볼 수 있다. 해당 서비스가 어떠한 계정으로 구동중인지도 확인이 가능하다.


1. General (일반 속성)

A. 서비스 이름 (Service name)
    외부에서 서비스를 제어하기 위한 고유한 이름이다.
    모든 Pirntable Charcter는 모두 사용가능하다.

B. 표시 이름 (Display name)
    서비스 이름은 고유명사 형태의 간략화된 상태로
    사용되는 이름이지만, 표시 이름은 그 서비스의 특성을
    알려주는데 사용되는 별칭과 같은 형태이다.

C. 설명 (Description)
    해당 서비스의 사용 목적이나 기능에 대하여 알려주는
    내용으로, 핵심적인 내용을 요약하여 보여준다.

D. 실행파일 경로 (Path to executable)
    해당 서비스가 설치되어 있는 전체 경로를 나타낸다.
    단, 실제로 구동될 경우는 Windows\System32 경로에서
    구동되므로, 구동시 상대 경로 설정에 주의해야 한다.

위의 4가지 속성중, 서비스 이름과 실행 경로는 필수 정보이고
나머지 2가지 정보는 부가 정보이므로 생략이 가능하다.

 E. 시작 형태 (Startup type)
    머신이 구동될 때, 해당 서비스가 자동으로 시작될것인지
    사용자에 의하여 수동으로 시작될 것인지를 나타낸다.
    (Disable은 사용을 임시로 막아 놓은 상태)

   F.  서비스 상태 (Servces status)
       해당 서비스의 동작 상태를 시각적으로 보여준다. 다음과 같이 여러단계를 가진다.
       시작중, 시작됨, 중지중, 중지됨, 일시중지중, 일시중지됨, 재시작중...

       //
       // Service State -- for CurrentState
       //
       #define SERVICE_STOPPED                        0x00000001
       #define SERVICE_START_PENDING            0x00000002
       #define SERVICE_STOP_PENDING              0x00000003
       #define SERVICE_RUNNING                        0x00000004
       #define SERVICE_CONTINUE_PENDING     0x00000005
       #define SERVICE_PAUSE_PENDING            0x00000006
       #define SERVICE_PAUSED                           0x00000007

  G. 동작 제어 버튼
      서비스의 동작 상태를 변경하는데 사용되는 버튼들이다.
      SetServiceStatus API를 이용하여, 간단하게 서비스의 동작을 제어할 수 있다.


  H. 시작 파라미터 (Start parameter)
      서비스를 서비스 관리자에서 시작시킬 때 서비스의 메인 함수에 인자를 전달할 수 있도록 방법을 제공해주는
      부분이다. 해당 위치에 적절한 파라미터를 위치 시킨 후에 시작 시키면 서비스는 해당 파라미터를 인식하여
      필요한 처리를 할 수 있다.
      단, 주의 할점으로는 휘발성이므로, 한번 적용하고 나서 다시 시작할 때 적용되지 않는다는 것에 주의하자.

여기까지 서비스의 속성창 중에서 첫번째에 보여지는 일반 속성에 대하여 간략하게 알아보았다.
그렇다면, 위에 나열된 것들이 프로그램적으로는 어디에서 사용되는지를 간단하게 살펴보고자 한다.

그럼 처음으로 CreateService Function 함수에 대하여, 살펴보고 위에 나온 내용들이 어디에 사용되는지 확인하자.

SC_HANDLE WINAPI CreateService(
  __in       SC_HANDLE hSCManager,              서비스 매니저의 핸들
  __in       LPCTSTR lpServiceName,                A. 서비스 이름
  __in_opt   LPCTSTR lpDisplayName,            B. 표시 이름
  __in       DWORD dwDesiredAccess,               서비스의 액세스 권한
  __in       DWORD dwServiceType,                    서비스의 종류(파일 드라이버, 커널 드라이버, 독립실행 서비스, 프로세스공유 서비스)
  __in       DWORD dwStartType,                         E. 시작 형태 (자동/수동/사용안함)
  __in       DWORD dwErrorControl,                    서비스 시작 실패시의 후속처리(시스템 재시작, 무시, 무시 및 이벤트로그 기록)
  __in_opt   LPCTSTR lpBinaryPathName,      F. 실행파일 경로
  __in_opt   LPCTSTR lpLoadOrderGroup,      @.@ (1)
  __out_opt  LPDWORD lpdwTagId,                  @.@ (2)
  __in_opt   LPCTSTR lpDependencies,           다른 서비스가 시작된 후에 구동할 필요가 있을 경우, 먼저 실행되어야할 서비스 이름들.. (3)
  __in_opt   LPCTSTR lpServiceStartName,    서비스를 시작시킬 때 사용될 유저 이름 (4)
  __in_opt   LPCTSTR lpPassword ,                   유저 이름이 지정되었을 경우 해당 유저의 암호

)

서비스를 생성할 때 위처럼 복잡하게 많은 인자들이 전달되지만 대부분이 생략 가능한 것들이다.
위에서 파란색으로 표기된 인자들은 필수 인자들인데, 설정하는데 전혀 어려울것이 없다.
나머지 아래의 5개가 조금 헷갈리는 것들인데

(1), (2) 은 나도 잘 모르겠다. 항목을 살펴보면 서비스를 그룹으로 묶을 수 있고, 해당 그룹 목록을 작성할 수 있는것을 보면
먼가가 있어보이는데.. 값을 넣거나, NULL로 해놔도 먼가 변화를 찾아볼 수 없다 -_-;;;

아래는 해당 관련된 정보를 담고 있는 레지스트리 키 이름이다.
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\ServiceGroupOrder
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\GroupOrderList

(3) 번은 "LanmanWorkstation\0LanmanServer\0LmHosts\0\0" 과 같이 여러개의 서비스 사이를 \0으로 구분하고 문자열의
마지막을 \0\\으로 구분해 주면 된다.


               <위의 설정을 이용하여 적용된 디펜던시 상태>

(4) 번은 사용자의 컴퓨터 계정 이름을 넣어주면 되는데, 도메인 사용자는 "도메인이름\사용자이름",
로컬 머신 계정은 ".\사용자이름" 과 같이 도메인을 구분해서 넣어주어야 한다.


2. Log on (시작 계정)
일반 속성에서는 말 그대로 서비스를 사용하는데 필요한 몇가지 필수 요소들의 정보를 가지고 있다. 또한 이렇게 생성된
서비스를 제어할 수 있는 방법을 제공한다. 아래의 그림은 시작 계정을 시스템 계정이 아닌 사용자 정의 계정을 적용
시켜놓은 상태의 모습이다. 하나씩 그 의미를 살펴보자.
A. Log on as: Local System account
   위 Radio Button 을 첵크하면 기본적으로 시스템
   (SYSTEM) 계정으로 서비스가 구동된다. 프로그램
   적으로는 계정을 설정 하지 않고 CreateService에서
   NULL 을 입력하면 되는것이다. 일반적으로 서비스는
   SYSTEM 계정으로 구동하길 권장한다.

B. Allow service to interact with desktop
   제목과 같이 서비스가 사용자가 로그온한 테스크탑과
   서로 연동되도록 설정하는 항목이다. SYSTEM 계정일
   때만 사용이 가능하다.

C. This account
   사용자가 계정을 직접 입력하여 해당 서비스가, 그 계정
   하에서 만 구동하도록 설정하는 것이다.

D. Hardware profile enable/disable
   해당 서비스에 연결가능한 하드웨어 프로파일을 사용하게
   하거나 못하게 하는 것이다. Disable 을 선택하면 서비스의
   사용이 불가능해진다는 점에서 서비스의 시작 타입을
   Disable 시켜 놓은 것과 동일하다.

이번 시작계정 챕터에는 항목은 작지만, 의외로 다루기 까다로운 설정들이 존재하는데... 프로그래밍의 어려움도 있지만
의미를 이해하기 쉽지않은 부분들이 존재한다.

Allow service to interact with desktop (데스크탑과 상호 연동)
해석해보면 데스크탑(사용자가 로그온한 상태의 윈도우 환경)과 연동이 가능하도록 지원해준다는 것이다.
일반적으로 서비스가 구동하게 되면, 어떠한 어플리케이션을 실행시키더라도 화면에 보여주지 못하는 background session 에서 구동된다.
즉, CreateProcess나 ShellExecute 혹은 ShellExecuteEx 를 이용하여, 콘솔이던  GUI이던 실행시키더라도 화면에 보여지지 않는다.
(물론 프로그램은 정상적으로 동작하지만 눈에 보이지 않으니, 프로세스가 떠있다는것 말고는 동작 상태를 확인하기 어렵다.)

 
코딩으로 보면 CreateService 함수에 인자를 전달할 때 SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS
를 전달하기만 하면 위의 그림처럼 첵크되어 해당 기능을 활성화 할 수 있다. 이 자체는 해당 기능을 활성화한것 뿐이지 이것으로
먼가가 자동으로 되는것은 없다.

저 옵션을 사용하면, 프로그램이 실행은 되지만 Console Session 에만 보여진다.
즉, 유저가 로그온한 세션에 프로그램을 띄워주는건 저 방법으로는 불가능하다는 것이다.

그렇다면, 서비스에서 로그온된 유저를 찾아 해당 유저의 세션에 프로그램을 띄워주는 것은 불가능한 것인가?
그 해법으로 CreateProcessAsUser 를 이용하는 방법이 있다. 이 챕터의 설명 범위를 넘어가는 것이기 때문에..
추후에 진행되는 예제중에 해당 처리 방법에 대한 것을 논의 하겠다.

하드웨어 프로파일에 관한건 별 쓸모가 없는 관계로 자세하게 다루지 않을 것이다.

3. Recovery
서비스를 수행하다보면, 여러가지 이유에 의해서 구동이 실패할 수 있다. 컴퓨터가 부팅되는 과정중에 수행되는 서비스의
실패를 어떻게 다룰것인가? 어떠한 이유로 실패하였고, 실패하였다면 그 후속 처리를 어떻게 할 것인가.. 등등.
성공하면 상관없지만, 실패하면 상관있는 그런 항목이다. 중요도가 상당히 높지만 자주 사용되지는 않는다.
사실상 윈도우의 시스템 서비스를 살펴보더라도 99% 쓰지 않거나, 쓴다고 하더라도 서비스 재시작이 고작이다.

왼쪽 그림에서 설명하는 내용도, 서비스가 실패
하였을 경우,

A. 그냥 무시
B. 서비스 재시작
C. 특정 프로그램 실행
D. 컴퓨터 재시작

저기서 C의 특정 프로그램 실행이란 항목을
선택하면 어떤 프로그램을 실행시킬건지 하고
인자를 기입할 수 있는 항목이 생성된다.

최대 3번 까지 재시도 할 수 있도록 제공하고

머 나머지는 실패했을 때 숫자세는데 무한정
셀수는 없으니까.. 언제 초기화 할것인가이다.

99.9%의 대열에 합류하면 이 항목은 전혀 의미
없는 항목이 되겠고, 먼지 몰라도 일단 해본다...
라고 하면, 헤딩을 좀 해야한다.


저기 항목에 있는 것을 설정하려면 ChangeServiceConfig2 API를 이용하여 편집하는 것이 가능하다. 단, 다른 것은
별 문제가 없는데 Restart th Computer 를 처리하려면, 즉, 문제가 생겼을 때 컴퓨터를 재 시작하려면 한가지 권한을
 필요로 한다. 머신을 리부팅 시킬수 있는 권한 즉, SE_SHUTDOWN_NAME 를 필요로 한다는 것이다.

서비스는 SYSTEM 계정으로  구동하므로 필요하다면 언제든지 저 권한을 할당할 수 있으므로 저것이 필요하다는 것을
알고만 있으면 별 문제 없다. AdjustTokenPrivileges 를 이용하여 추가하면 그만이다. 복잡하게 이야기 하면 끝이 없고
간단하게 샘플코드 일부를 정리함으로 이내용은 나중 예제를 위하여 남겨 놓는다.

  SERVICE_FAILURE_ACTIONS sfa = {0};
  SC_ACTION sa[3];
  
  sa[0].Delay = 2000;
  sa[0].Type = SC_ACTION_RESTART;
  sa[1].Delay = 2000;
  sa[1].Type = SC_ACTION_RUN_COMMAND;
  sa[2].Delay = 2000;
  sa[2].Type = SC_ACTION_REBOOT;

  sfa.dwResetPeriod = 600;
  sfa.lpRebootMsg = "";
  sfa.lpCommand = "";
  sfa.cActions = 3;
  sfa.lpsaActions = (SC_ACTION *)sa;

  ChangeServiceConfig2(hSrv, SERVICE_CONFIG_FAILURE_ACTIONS, &sfa);

정말 간단하게 끝나넹.. 요걸 수행하기 이전에  SE_SHUTDOWN_NAME 권한만 주면 되는 것이다.
알고서 진행하면 별거 아닌 것이지만, 모르고 찾을 때는 별 귀찬은 문제가 다 덤비는 것이 이 세계인가 보다.

4. Dependencies
머 이름만 거창하게 디펜던시즈라고 되어있는데.. 짧게 간추려 보면, 내가 만든 프로그램이 특정 서비스에 영향을 받는가? 혹은 주는가?
이러한 것이 있을 경우는 정확하게 명시해라.. 머 이런 의미이다.

내 프로그램을 특정 서비스 동작 이후에 구동되도록 하고 싶다면..
1. General 에서 설명한 (3) 번 처럼 영향을 받을 서비스 목록을 그냥 주르륵 넣어서 서비스를 생성하면 된다. 아주 쉽다.

그렇다면 내 서비스 프로그램에 디펜던시가 걸려있는 다른 서비스 목록이 있다면 이걸 어떻게 찾을 것인가?
EnumDependentServices 를 이용하여 그냥 긁어오면 된다.

이러한 작업이 왜 필요하냐면.. 내가 만약 특정 서비스를 중지 시켜야할 일이 있다고 가정했을 때, 디펜던시 걸린 서비스를 확인하지 않고
그냥 그 서비스를 중지 시키면 절대 죽지 않는다. 프로세스를 kill 한다면 모를까..
디펜던시가 걸린 하부 서비스를 모두 확인하고 그 서비스의 하부를 확인해서 아래부터 차례로 하나식 죽이고 마지막에 목표로한 서비스를
중지시켜야 정상적으로 중지된다.

-----------------------------------------------------------------------------------------------------------------------

지금 까지 서비스 매니저에서 볼 수 있는 몇몇 항목에 대하여 간단하게 기술하여 보았습니다. 모르고 넘어가도 되는 항목들도
많이 있으나, 그런게 있다는걸 알아두면 나중에 도움이 될까 싶어서 그림과 함께 적어 놓았으니 참고가 되셨으면 좋겠네요..

자, 다음으로는 구체적인 서비스보다는 서비스를 설치/제거/제어(시작-중지)에 대하여 정리하여 보도록 하겠습니다.
위 항목도 API를 최대한 간소하게 사용하면 아주 쉽고요, API의 인자들을 모두 채우려면 꽤나 복잡하겠죠? ^^;;;

간단하게 할겁니다 ㅡ,.ㅡ;;;

+ Recent posts