YOGYUI

MFC::프로그램으로 PC 전원 끄기 (Windows OS) 본문

Software/C, C++

MFC::프로그램으로 PC 전원 끄기 (Windows OS)

요겨 2022. 2. 23. 14:13
반응형

Turn Off / Restart PC Power by Programming Code

 

MS Windows 환경에서 프로그래밍 코드(Visual C++, MFC)를 통해 PC의 전원을 끄는 방법을 알아보자

용도) Windows OS에 접근하지 못하게 프로그램을 구성한 뒤, PC의 전원을 끄거나 재시작하고자 할 경우

※ Windows 10, Visual Studio 2019에서 구현 및 테스트함

1. API 사용 - ExitWindowsEx

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-exitwindowsex

<WinUser.h>에 선언되어 있는 ExitWindowsEx 함수를 호출하면 된다

함수 원형은 다음과 같다

BOOL ExitWindowsEx(
  [in] UINT  uFlags,
  [in] DWORD dwReason
);

uFlags의 인자로 다음 값들 중 하나를 사용할 수 있다 (각 인자에 대한 자세한 설명은 위 문서 참고)

  • EWX_HYBRID_SHUTDOWN (0x00400000)
  • EWX_LOGOFF (0x00000000)
  • EWX_POWEROFF (0x00000008)
  • EWX_REBOOT (0x00000002)
  • EWX_RESTARTAPPS (0x00000040)
  • EWX_SHUTDOWN (0x00000001)

이름을 보면 알겠지만, PC 파워 종료뿐만 아니라 재시작, 로그오프 등의 기능도 동일 함수로 수행할 수 있다

또한, uFlags 인자에 다음 두 값중 하나를 or 연산으로 조합할 수 있다 

    • EWX_FORCE (0x00000004)
      실행중인 다른 프로세스들에 WM_QUERYENDSESSION 메시지를 보내지 않음
    • EWX_FORCEIFHUNG (0x00000010)
      실행중인 다른 프로세스들이 WM_ENDSESSION 메시지 혹은 WM_QUERYENDSESSION 메시지에 응답하는 것을 기다림

즉, EWX_FORCE를 조합하면 다른 프로세스(ex: 워드프로세서 문서 등)가 저장되지 않은 내역이 있음에도 불구하고 사용자 확인 메시지 없이 강제로 셧다운시켜버리게 되니, 호출 시 주의해야 한다

 

또한, 함수가 호출되는 이유로 dwReason 인자를 입력할 수 있는데, Major-Minor를 or 연산으로 조합하여 다양한 셧다운 원인을 조합할 수 있다 (자세한 내용은 링크 참고)

예를 들어

SHTDN_REASON_MAJOR_APPLICATION 

SHTDN_REASON_MINOR_UPGRADE

SHTDN_REASON_FLAG_PLANNED

와 같이 조합하면 '어플리케이션(SW)이 계획에 의해 업그레이드됨에 따라' 이 함수를 호출하게 된다는 것을 의미한다 (특정 프로그램 업그레이드 후 재시작이 필요하다고 나오는 메시지를 봤다면 익숙한 개념)

[구현]

단순히 위 함수만 호출한다고 해서 전원이 종료되거나 재시작되는 것은 아니고, 이 함수를 호출하는 프로세스에 Shutdown에 대한 권한(Privilege)을 부여받아야 한다 (마이크로소프트 제공 예시)

https://docs.microsoft.com/en-us/windows/win32/shutdown/how-to-shut-down-the-system

 

How to Shut Down the System - Win32 apps

The following example uses the ExitWindowsEx function to shut down the system.

docs.microsoft.com

현재 프로세스에 권한을 부여하는 코드는 다음과 같이 구성할 수 있다

(Visual Studio 설치 시 자동으로 설치되는 라이브러리들을 활용하기 때문에, 추가적으로 설치할 건 없다)

#include <windows.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "advapi32.lib")

BOOL GetShutdownPrivilege() {
    HANDLE hCurrentProcess, hToken;
    TOKEN_PRIVILEGES* tkp = NULL;

    // 현재 프로세스 핸들 얻기
    hCurrentProcess = GetCurrentProcess();
    
    // 현재 프로세스에 대해 'Adjust Privelege' 토큰 발급 
    if (!OpenProcessToken(hCurrentProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    // 토큰 구조체 할당
    tkp = new TOKEN_PRIVILEGES;
    if (!tkp)
        return FALSE;

    // Shutdown 권한에 대한 LUID 구조체 쿼리
    LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp->Privileges[0].Luid);

    tkp->PrivilegeCount = 1;
    tkp->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    // Adjust Privelege 토큰 적용 
    AdjustTokenPrivileges(hToken, FALSE, tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
    delete tkp;

    if (GetLastError() != ERROR_SUCCESS)
        return FALSE;

    return TRUE;
}

이 함수는 어플리케이션 초기화 시 한번만 호출해도 되고, 혹은 ExitWindowsEx 함수 호출 시 다음과 같이 구현해도 된다

void PowerOffByAPI()
{
    if (GetShutdownPrivilege())
        ExitWindowsEx(
            EWX_POWEROFF | EWX_FORCE,
            SHTDN_REASON_MAJOR_APPLICATION | SHTDN_REASON_MINOR_OTHER
        );
}

 

※ Visual Studio에서 작업 시 C28159 경고 메시지를 확인할 수 있다

(Legacy API. Rearchitect to avoid Reboot)

어차피 시스템 종료를 원해서 구현한 것이니 경고를 무시해도 좋고, 아니면 InitiateSystemShutdownEx 함수로 구현해도 좋다 (사용법이 조금 더 복잡하니 이 글에서는 무시)

2. shutdown 실행파일 호출

Windows 설치 경로에 있는 /system32 디렉터리 내에는 shutdown 이라는 이름의 실행파일이 존재한다

(사용자가 PC 종료나 재시작, 로그오프 등의 명령을 내릴 때 실제로 호출되는 실행파일)

이 실행파일을 코드상에서 다음과 같이 호출하면 된다

#include <stdlib.h>

void PowerOffByExec() {
    system("shutdown /f /s");
}

system 함수는 <stdlib.h>에 선언되어 있다

_DCRTIMP int __cdecl system(
        _In_opt_z_ char const* _Command
        );

※ 환경변수 PATH에 "{윈도우 설치 경로}/system32"가 추가되어 있어야 한다

만약 환경변수에 없는데 추가하는게 귀찮다면, shutdown.exe의 전체 경로를 함께 기입해주면 된다

system("C:\\Windows\\System32\\shutdown /f /s");

 

system 명령어에 대한 자세한 내용 (인자 입력 등)은 다음 문서 참고

https://docs.microsoft.com/ko-kr/windows-server/administration/windows-commands/shutdown

 

shutdown

로컬 또는 원격 컴퓨터를 한 번에 하나씩 종료 하거나 다시 시작 하는 데 사용할 수 있는 shutdown 명령에 대 한 참조 문서입니다.

docs.microsoft.com

 

[참고]

https://stackoverflow.com/questions/846576/is-there-a-c-function-to-turn-off-the-computer

https://docs.microsoft.com/en-us/windows/win32/secauthz/enabling-and-disabling-privileges-in-c--

반응형