C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[120] [팁] Windows NT의 ShutDown
박지훈.임프 [cbuilder] 8502 읽음    2001-07-26 15:03
이 팁은 담비님(천리안 FREKBS)님이 99년 3월 29일에 천리안 프로그래머포럼에 올리신 것입니다.
담비님으로부터는 전제하여 올리는 데 대해 허락을 받았습니다.
좋은 정보를 공유하도록 허락해주신 담비님께 감사드립니다.

-----------------------------------------------------------------------
안녕하세요! 담비입니다.

아직 해석이 미흡합니다. 이점 양해해주십시요.

출처 :http://www.inprise.co.jp/tips/cbuilder/cb013/index.html

Windows NT의 ShutDown
───────────

Windows NT는 대단히 사용하기 쉬운 OS이지만 동시에 Reboot가 필요한 OS이기도
합니다. 이 문서에는 WindowsNT를 ShutDown하는 Programming을 소개합니다.

Windows NT는, Windows OS의 Server를가지고,  최근 주목을 받고있다.
Windows NT Server에 Oracle 8 등을 사용하여 Client/Server System(이하 C/S)을
구축시키는 방법도 꽤 많은 사람들이 있을것이라고 생각한다.
그럼에도불구하고 Windows NT는 대단히 사용하기 쉬운 OS이지만 안전성에 관해서는
약간의 의문이 또다시 남는다.
C/S System을 설계하는 단계에,사양서에 Reboot를 원한다는 말이 나타난 경우
Reboot가 필요한 OS이다. 그래서 이번에는, 시스템의 신뢰성을 향상하기 위하여
Windows NT를 ShutDown하는 프로그래밍을 소개한다.

1. Local ShutDown
Local System을 ShutDown하는데는 ExitWindowsEx함수를 사용한다.

1.1 ExitWindowEx
    이 함수는 Windows System을 ShutDown 또는 LogOff를 실행한다. 또, Reboot를
    실행하는것도 가능하다.
    BOOL ExitWindowEx( UINT uFlags, DWORD dwReserved );

         uFlags:
           EWX_FORCE       실행중인 Process를 강제 종료
                           실행중인 Application의 Data는 보증할수 없다.
           EWX_LOGOFF      User의 LogOff
           EWX_POWEROFF    전원 절전
           EWX_REBOOT      ShutDown후 Reboot
           EWX_SHUTDOWN    ShutDown
         DwReserved:
           예약되어있으며, 현재는 무시한다.

           * 세부적인 것은 Windows SDK Online Help를 참조한다.

    UFlags의 Parameter( EWX_POWEROFF, EWX_REBOOT, EWX_SHUTDOWN )를 Windows NT에
    사용하는데는 Security 특권이 있는 SE_SHUTDOWN_NAME을 프로그램상에서 취득할
    필요가 있다.

    * Security에 관한 세부적인 것은 Windows SDK Online Help를 참조한다.

    프로그램상에서 Security를 취득하려면 OpenProcessToken 함수를 사용하여
    Access Token을 열고 LookupPrivilegeValue 함수에 System에 지정한 Security
    명칭을 표시하는 식별자(LUID)를 취득하고, Security특권을 사용할수 있기 때문에
    AdjustTokenPrivileges함수를 호출할 필요가 있다.

    * 세부적인 것은 Windows SDK Online Help를 참조한다.

1.2 ShutDown Programming
    Security특권을 취득하는 함수(AccessPrivilege)를 TForm1의 member로 작성한다.
    Button1의 Click에 AccessPrivilege를 호출하여 Security를 취득하고
    ExitWindowsEx로 System을 ShutDown한다.

    1.2.1 Application Image

                     ┏━━━━━━━━━━━━━━━━━┓
     Form1 ------->  ┃ Form1                      _ ㅁX ┃
                     ┠─────────────────┨
                     ┃                                  ┃
                     ┃                                  ┃
                     ┃                                  ┃
                     ┃                                  ┃
                     ┃                                  ┃
                     ┃                                  ┃
                     ┃         ┌───────┐       ┃
           Button1 ------------>│ Shutting Down│       ┃
                     ┃         └───────┘       ┃
                     ┠─────────────────┨
                     ┃                                  ┃<-- StatusBar1
                     ┗━━━━━━━━━━━━━━━━━┛

    1.2.2 Source Code
    //Unit1.h
     class TForm1 : public TForm
    {
     __published:
       TButton *Button1;
       TStatusBar *StatusBar1;
       void __fastcall Button1Click(TObject *Sender);
     private:
     public:
       __fastcall TForm1(TComponent* Owner);
       void __fastcall AccessPrivilege( char *Prvlg,
                       String ServerName, bool Mode ) throw( Exception );
       void __fastcall ErrMsg( String Msg );  // User Message를 StatusBar에 표시
    };

    //Unit1.cpp
    TForm1 *Form1;
     const char *ErrOpenProcess     = "Error OpenProcessToken";
     const char *ErrLookupPrivilege = "Error LookupPrivilegeValue";
     const char *ErrAdjustToken     = "Error AdjustTokenPrivileges";
     const char *ErrExitWindowsEx   = "Error ExitWindowEx";
    //--------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
    {
     StatusBar1->SimplePanel = true;
     }
    //--------------------------------------------------------------------------
    void __fastcall TForm1::ErrMsg( String Msg )
    {
     StatusBar1->SimpleText = Msg;
    }
    //--------------------------------------------------------------------------
    void __fastcall TForm1::AccessPrivilege( char *Prvlg,
                           String ServerName, bool Mode ) throw(Exception)
    {
     HANDLE htoken;
     TOKEN_PRIVILEGES  tknPrvlgs;

     bool success = OpenProcessToken( GetCurrentProcess(),
                               TOKEN_ADJUST_PRIVILEGES : TOKEN_QUERY, &htoken );
     if( !success )
     {
       throw Exception(ErrOpenProcess);
     }

     success = LookupPrivilegeValue( ServerName.c_str(), Prvlg,
                                 &(tknPrvlgs.Privileges[0].Luid) );
     if( !success )
     {
       throw Exception(ErrLookupPrivilege);
     }

     tknPrvlgs.PrivilegeCount = 1;

     if( Mode )
       tknPrvlgs.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
     else
       tknPrvlgs.Privileges[0].Attributes = 0;


     AdjustTokenPrivileges( htoken, false, &tknPrvlgs,
                                      sizeof(TOKEN_PRIVILEGES), 0, 0 );
       if( GetLastError() != ERROR_SUCCESS )
       {
         throw Exception( ErrAdjustToken );
       }

    }

    //--------------------------------------------------------------------------
    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
     ErrMsg( "" );

     try{
       AccessPrivilege( SE_SHUTDOWN_NAME, "", true );
     }catch( Exception &Ex ){
       ErrMsg( Ex.Message );
       return;
     }

     if(!ExitWindowsEx( EWX_SHUTDOWN : EWX_FORCE, 0 ))
       ErrMsg( ErrExitWindowsEx );

     try{
       AccessPrivilege( SE_SHUTDOWN_NAME, "", false );
     }catch( Exception &Ex ){
       ErrMsg( Ex.Message );
       return;
     }

    }
    //--------------------------------------------------------------------------

   이 프로젝트를 Compile하여 Link한 후에 ShutDown 특권이 있는 User가 실행하면
   System을 Shutdown시킬수 있다. 또, GetVersionEx함수를 사용하여 Windows95(98)이
   Windows NT를 판별하는 AccessPrivilege 함수를 Windows95(98)의 경우에는,
   호출하지않고도 Windows95(98)의 Application에서도 사용가능하다

2. Remote ShutDown
소규모인 System 또는 수대(數台)의 NT Server를 ShutDown하는 경우는
ExitWindowsEx를 사용하고, Local Machine을 ShutDown하면된다. 그러나 관리하는
NT Server가, 수십대이고 원격지에 있는 경우의 ShutDown에는 시간이 많이 걸린다.
(왜냐하면 수십대의 Machine에 동일한 환경을 작성해야하고, 현지까지 가서 Setup을
실행할 필요가 있기 때문이다.)
1대의 Machine으로부터 수십대의 NT Server가 ShutDown이 가능하다면 얼마나좋을까?
더불어 User와 Password가 각각 다른 Server群을 1대의 Machine으로부터 ShutDown이
가능한 경우에..
다음은 Local Machine으로부터 원격지의 NT Server를 ShutDown을 실험해본다.
Remote System을 ShutDown하는데는, InitiateSystemShutdown함수를 사용시 Shutdown의
취소는 AbortSystemShutdown을 사용한다.

2.1 InitiateSystemShutdown
    이 함수는 WindowsNT System의 Shutdown을 실행한다. 또, Remote를 행하는것도
    가능하다. 함수의 Prototype과 인수는 다음과 같다.
    BOOL InitiateSystemShutdown (
         LPTSTR lpMachineName,
         LPTSTR lpMessage,
         DWORD  dwTimeout,
         BOOL   bForceAppsClosed,
         BOOL   bRebootAfterShutdown
         );

    lpMachineName : Shutdown을 실행할 Remote Server명
    lpMessage     : Shutdown DialogBox에 표시할 Message
    dwTimeout     : Shutdown을 실행할 때 까지의 시간(秒)
    bForceAppsClosed : 실행중인 Process를 강제 종료
                       실행중인 Application의 Data를 보증할수 없다.
    bRebootAfterShutdown : Shutdown후에 Reboot

    * 세부적인 것은 Windows SDK Online Help를 참조한다.

2.2 AbortSystemShutdown
    이 함수는 InitateSystemShutdown으로 개시시킨 Shutdown처리를 취소시킨다.
    함수의 Prototype과 인수는 다음과 같다.
    BOOL AbortSystemShutdown( LPTSTR lpMacnineName );
    lpMacnineName : Shutdown 처리를 취소할 Remote Server명

    * 세부적인 것은 Windows SDK Online Help를 참조한다.

2.3 Server 인증(認證)
    Remote하는 WindowsNT의 Shutdown을 실행하는데는 User의 인증이라는것에 대한
    개념이 필요하다.
    InitateSystemShutdown과 AbortSystemShutdown을 실행하는데는 Remort Sever측에
    실행하는 User ID와 Password 그리고 그 User ID에의 Remote Shutdown 특권이
    필요할것이다.

   Remote Machine                               Local Machine
    ┏━┓       Remote Shutdown을 실행          ┏━━━━┓
    ┃  ┃  <------------------------------------┃        ┃
    ┃  ┃                                       ┗━┯┯━┛
    ┃  ┃                                   ┏━━━┷┷━━━━┓
    ┃  ┃                                   ┃ ===============  ┃
  ━┻━┻━                                 ┗━━━━━━━━━┛
   UserID : Inprise                          UserID : Inprise
   Password : Borland                        Password : Borland

   위의 그림의 경우에 Local Machine측의 UserID와 Password가 Remote측과 공통으로
   있고, User의 인증이 확립된 Remote Shutdown이 가능하게 된다.

   * Local DataBase의 정보와 Remote DataBase의 정보를 조회하여 일치한다면,
     인증이 확립된다. 상세한것은 WindowsNT Resource Kit등을 참조.

   그럼에도 위의 상태에는, Local Machine에 모든 Remote Machine의 UserID와
   Password를 작성할 필요가 있다. 그렇게되면 상당한 수고를 해야만할것이다.
   그러므로 Remote LogOn 인증의 Technic을 사용한다.
   [네트워크 드라이브의 할당]의 net use comment를 사용하여 Remote의 Resource를
   공유하는 경우, Local UserID와 Password가 틀릴 경우에 다시 입력해야할 필요가
   있다. 이 UserID와 Password의 입력이 올바른 경우 Local Machine과 Remote
   Machine간에 인증이 확립되게 될것이다. 이러한 인증 후에는
   InitiateSystemShutdown을 실행하고, Resource를 공유하고있는 User에 인증을
   행하며 Shutdown Process가 실행되게된다.


   Remote Machine                                      Local Machine
    ┏━┓       Remote Shutdown을 실행                ┏━━━━┓
    ┃  ┃  <----------------------------------------- ┃        ┃
    ┃  ┃                                             ┗━┯┯━┛
    ┃  ┃  =========+---------------------+=======┏━━━┷┷━━━━┓
    ┃  ┃           :  Resource 공유      :       ┃ ===============  ┃
  ━┻━┻━         : UserID : Inprise    :       ┗━━━━━━━━━┛
UserID : Inprise    : Password : Borland  :         UserID : Inprise
Password : Borland  +---------------------+         Password : Borland

2.4 Shutdown Programming
    Local Shutdown에 사용된, Security 특권을 취득하는 함수(AccessPrivilege)를
    다시 이용한다.
    Edit1에 Server 명을 지정하고, Memo1에 Shutdown시의 Message를 읽어들인다.
    Butt1에서는 Shutdown Process를 시작하고, Button2에서는 Shutdown Process를
    취소한다.

    이번에는, Remote Resource의 공유에 IPC$를 사용한다.
    이 IPC$는, Remote Process간의 통신에 사용되는 공유 Resource이다.

    2.4.1 Application Image

    * See 명령을 사용하여 그림을 참조하세요.

    2.4.2 Source Code
//클래스측의 선언 Unit1.h
  class TForm1 : public TForm
  {
  __published:    // IDE 관리 콤포넌트
    TButton *Button1;
    TEdit *Edit1;
    TLabel *Label1;
    TLabel *Label2;
    TEdit *Edit2;
    TButton *Button2;
    TUpDown *UpDown1;
    TCheckBox *CheckBox2;
    TCheckBox *CheckBox3;
    TCheckBox *CheckBox4;
    TStatusBar *StatusBar1;
    TMemo *Memo1;
    TLabel *Label5;
    TPanel *Panel1;
    TLabel *Label3;
    TLabel *Label4;
    TEdit *Edit3;
    TEdit *Edit4;
    TCheckBox *CheckBox1;
    TButton *Button3;
    void __fastcall Button1Click(TObject *Sender);
    void __fastcall Button2Click(TObject *Sender);
    void __fastcall CheckBox1Click(TObject *Sender);

    void __fastcall CheckBox2Click(TObject *Sender);
    void __fastcall Button3Click(TObject *Sender);
  private:    // User 선언
    void __fastcall AccessPrivilege( char *prvlg, String ServerName,
                                     bool mode ) throw( Exception );
    bool __fastcall LogonExecute( void );
    bool __fastcall LogoffExecute( void );


  public:        // User 선언
    __fastcall TForm1(TComponent* Owner);
    void __fastcall ErrMsg( String Msg );
    String __fastcall UncName( String ServerName );
};


  //Source측의 선언  Unit1.cpp
  TForm1 *Form1;
  const char *ErrOpenProcess      = "Error OpenProcessToken";
  const char *ErrLookupPrivilege  = "Error LookupPrivilegeValue";
  const char *ErrAdjustToken      = "Error AdjustTokenPrivileges";
  const char *ErrInitiateShutdown = "Error InitiateSystemShutdown";
  const char *ErrAbortShutdown    = "Error AbortSystemShutdown";
  const char *ErrSessionConflict  = "Error SessionConflict";
  //---------------------------------------------------------------------------
  __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
  {
  }
  //---------------------------------------------------------------------------
  void __fastcall TForm1::ErrMsg( String Msg )
  {
    StatusBar1->SimpleText = Msg;
  }
  //---------------------------------------------------------------------------
  String __fastcall TForm1::UncName( String ServerName )
  {
    String Tmp = "\\\\"+ServerName+"\\";
    return Tmp;
  }
  //---------------------------------------------------------------------------
  bool __fastcall TForm1::LogonExecute( void )
  {
    NETRESOURCE NetRes;
    char str[MAX_PATH];
    bool  Result = true;

    memset( str, 0, sizeof(str));
    memset( &NetRes, 0, sizeof(NetRes));

    NetRes.dwType = RESOURCETYPE_DISK;

    lstrcpyn( str, UncName(Edit1->Text).c_str(), sizeof(str));
    strcat( str, "IPC$" );
    NetRes.lpRemoteName = str;

    DWORD retn = WNetAddConnection2( &NetRes, Edit4->Text.c_str(),
                                  Edit3->Text.c_str(), CONNECT_UPDATE_PROFILE );

    if( retn != NO_ERROR )
    {
      switch( retn )
      {
        case ERROR_SESSION_CREDENTIAL_CONFLICT :
          ErrMsg( ErrSessionConflict );
          Result = false;
          break;

        default:
          ErrMsg( "Err :"+IntToStr(retn) );
          Result = false;
          break;
      }
    }

    return Result;
  }

  //---------------------------------------------------------------------------
  bool __fastcall TForm1::LogoffExecute( void )
  {
    char str[MAX_PATH];
    bool Result = true;
    memset( str, 0, sizeof(str));

    lstrcpyn( str, UncName(Edit1->Text).c_str(), sizeof(str));
    strcat( str, "IPC$" );
    DWORD retn = WNetCancelConnection2( str, CONNECT_UPDATE_PROFILE, false );

    if( retn != NO_ERROR )
    {
      switch( retn )
      {
        case ERROR_OPEN_FILES :
          Result = true;
          break;

        default:
          ErrMsg( "Err :"+IntToStr(retn) );
          Result = false;
          break;
      }
    }

    return Result;
  }
  //---------------------------------------------------------------------------
  void __fastcall TForm1::AccessPrivilege( char *prvlg,
                          String ServerName, bool mode ) throw( Exception )
  {
    bool success;
    HANDLE token;
    LUID   luid;
    TOKEN_PRIVILEGES  tokenPrivileges;

    success = OpenProcessToken( GetCurrentProcess(),
                               TOKEN_ADJUST_PRIVILEGES : TOKEN_QUERY, &token );
    if( !success )
    {
      throw Exception( ErrOpenProcess );
    }

    success = LookupPrivilegeValue( ServerName.c_str(), prvlg, &luid );
    if( !success )
    {
      throw Exception( ErrLookupPrivilege );
    }

    tokenPrivileges.PrivilegeCount = 1;
    tokenPrivileges.Privileges[0].Luid = luid;

    if( mode )
      tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
      tokenPrivileges.Privileges[0].Attributes = 0;


    AdjustTokenPrivileges( token, false, &tokenPrivileges,
                                     sizeof(TOKEN_PRIVILEGES), 0, 0 );
    if( GetLastError() != ERROR_SUCCESS )
    {
      throw Exception( ErrAdjustToken );
    }

  }
  //---------------------------------------------------------------------------
  void __fastcall TForm1::Button1Click(TObject *Sender)
  {
    ErrMsg("");

    if( !CheckBox2->Checked )
    {
      if( !LogonExecute())
      {
        return;
      }
    }

    try{
      AccessPrivilege( SE_REMOTE_SHUTDOWN_NAME, Edit1->Text, true );
    }
    catch( Exception &Ex ){
      ErrMsg( Ex.Message );
      return;
    }

    if( !InitiateSystemShutdown( UncName(Edit1->Text).c_str(),
                                 Memo1->Lines->Text.c_str(),
                                 Edit2->Text.ToInt(),
                                 CheckBox3->Checked,
                                 CheckBox4->Checked ))
    {
      ErrMsg( ErrInitiateShutdown );
    }


    try{
      AccessPrivilege( SE_REMOTE_SHUTDOWN_NAME, Edit1->Text, false );
    }
    catch( Exception &Ex ){
      ErrMsg( Ex.Message );
      return;
    }

    if( !CheckBox2->Checked )
    {
      if( !LogoffExecute())
      {
        return;
      }
    }

  }
  //---------------------------------------------------------------------------
  void __fastcall TForm1::Button2Click(TObject *Sender)
  {
    ErrMsg("");

    if( !CheckBox2->Checked )
    {
      if( !LogonExecute())
      {
        return;
      }
    }

    try{
      AccessPrivilege( SE_REMOTE_SHUTDOWN_NAME, Edit1->Text, true );
    }
    catch( Exception &Ex ){
      ErrMsg( Ex.Message );
      return;
    }

    if( !AbortSystemShutdown( UncName(Edit1->Text).c_str() ))
    {
      ErrMsg( ErrAbortShutdown );
    }

    try{
      AccessPrivilege( SE_REMOTE_SHUTDOWN_NAME, Edit1->Text, false );
    }
    catch( Exception &Ex ){
      ErrMsg( Ex.Message );
      return;
    }

    if( !CheckBox2->Checked )
    {
      if( !LogoffExecute())
      {
        return;
      }
    }
  }
  //---------------------------------------------------------------------------
  void __fastcall TForm1::CheckBox1Click(TObject *Sender)
  {
    if( CheckBox1->Checked )
      Edit3->Text = "Administrator";
    else
      Edit3->Text = "";
  }
  //---------------------------------------------------------------------------
  void __fastcall TForm1::CheckBox2Click(TObject *Sender)
  {
    if(CheckBox2->Checked)
    {
      CheckBox1->Enabled = false;
      Edit3->Enabled = false;
      Edit4->Enabled = false;
    }
    else
    {
      CheckBox1->Enabled = true;
      Edit3->Enabled = true;
      Edit4->Enabled = true;
    }
  }
  //---------------------------------------------------------------------------

    이 Project를 Compile하여 Link한 후 Shutdown 특권이 있는 User가 실행하면
    System을 Shutdown한다.

    이번의 Program은 WindowsNT에서만 동작한다. Windows95(98)에서는 이용할 수
    없으니 주의하기 바란다. 또한, 이번에는 GUI의 Application이였지만 위의
    Program을 응용하여 Console용의 Application을 작성하면 Batch 처리하여
    Shutdown을 실행하는 것보다 효율적이다.

+ -

관련 글 리스트
120 [팁] Windows NT의 ShutDown 박지훈.임프 8502 2001/07/26
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.