пятница, 2 ноября 2012 г.

Программно выключить/перезагрузить компьютер

Добрый день, сегодняшняя статья будет описывать реализацию класса для выключения или перезагрузки компьютера пользователя. Вы возможно думаете, зачем это нужно. Я вам скажу что в некоторых весьма специфичных моментах нужно, например: после изменения реестра перезагрузить машину, все надо будет сделать это показать сообщение пользователю и по результату ответа, вызвать или не вызвать метод который мы напишем сегодня чуть позже.

Кстати, выполнить эту задачу на C# можно несколькими способами:
  • С помощью WinAPI.
  • WMI -  инструментарий управления Windows. Кто не знает, вам сюда (рассматривать в данной статье не буду).
  • CMD команда.
Я опущу начала о том что надо создать проект типа "Библиотека классов".

WinAPI

Для начала рассмотрим самый объемный вариант - WinAPI.
В наш класс добавим API функции:
//импортируем API функцию InitiateSystemShutdown
        [DllImport("advapi32.dll", EntryPoint = "InitiateSystemShutdownEx")]
        static extern int InitiateSystemShutdown(string lpMachineName, string lpMessage, int dwTimeout, bool bForceAppsClosed, bool bRebootAfterShutdown);
        //импортируем API функцию AdjustTokenPrivileges
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
        ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
        //импортируем API функцию GetCurrentProcess
        [DllImport("kernel32.dll", ExactSpelling = true)]
        internal static extern IntPtr GetCurrentProcess();
        //импортируем API функцию OpenProcessToken
        [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
        internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
        //импортируем API функцию LookupPrivilegeValue
        [DllImport("advapi32.dll", SetLastError = true)]
        internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
        //импортируем API функцию LockWorkStation
        [DllImport("user32.dll", EntryPoint = "LockWorkStation")]
        static extern bool LockWorkStation();
Теперь инициализируем константы для работы API функций в соответствии с MSDN
internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
internal const int TOKEN_QUERY = 0x00000008;
internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
internal const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
ОК, все что нужно у нас есть, хотя не хватает одной мелочи - функции повышения привилегий нашего процесса. Давайте ее напишем, но сначала объявим для нее структуру:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
        internal struct TokPriv1Luid
        {
            public int Count;
            public long Luid;
            public int Attr;
        }
Ну и сама функция:
private static void SetPriv()
        {
            TokPriv1Luid tkp; //экземпляр структуры TokPriv1Luid 
            IntPtr htok = IntPtr.Zero;
            //открываем "интерфейс" доступа для своего процесса
            if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok))
            {
                //заполняем поля структуры
                tkp.Count = 1;
                tkp.Attr = SE_PRIVILEGE_ENABLED;
                tkp.Luid = 0;
                //получаем системный идентификатор необходимой нам привилегии
                LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tkp.Luid);
                //повышем привилигеию своему процессу
                AdjustTokenPrivileges(htok, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero);
            }
        }
Тут много чего не понятного, немного поясню. Этот метод нужен для того чтобы гарантировать выполнения команды включения/выключения. Без нее, может просто ничего не произойти. Для управления конкретными привилегиями в WinAPI существует специальная структура TokPriv1Luid. Заполняя ее поля так:
tkp.Count = 1;
                tkp.Attr = SE_PRIVILEGE_ENABLED;
                tkp.Luid = 0;
Мы указываем на необходимость ВКЛЮЧЕНИЯ привелегии. Какой именно, в данном случае не указывается. Но перед тем как управлять привилегиями процесса, необходимо получить особый указатель на процесс - IntPtr.
В данной строчке мы получаем указатель на наш процесс:
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok)
Привилегии в системе могут быть самые разнообразные, на нужна одна - позволяющая производить перезагрузку/выключение, ее имя - SeShutdownPrivilege (подробнее о всех привилегиях можно почитать в MSDN). Для того, что бы получить возможность работать с этой привилегией - нужно получить ее идентификатор:
LookupPrivilegeValue(null, SE_SHUTDOWN_NAME, ref tkp.Luid);
Ну и на конец, когда мы заполнили структуру "выключателя", получили идентификатор привилегии - применям все это к уже известному идентификатору процесса:
AdjustTokenPrivileges(htok, false, ref tkp, 0, IntPtr.Zero, IntPtr.Zero);
Ну вот почти все, почти перезагрузились. Добавьте этот простой код:
//публичный метод для перезагрузки/выключения машины
        public static int halt(bool RSh, bool Force)
        {
            SetPriv(); //получаем привилегия
            //вызываем функцию InitiateSystemShutdown, передавая ей необходимые параметры
            return InitiateSystemShutdown(null, null, 0, Force, RSh);
        }
        //публичный метод для блокировки операционной системы
        public static int Lock()
        {
            if (LockWorkStation())
                return 1;
            else
                return 0;
        }
Собственно класс имеет два метода: halt() - для выключения/перезагрузки, и не большим бонусом Lock() - для блокировки ОС (но не выходу из системы!)
Для использования:
halt(true, false) //мягкая перезагрузка
halt(true, true) //жесткая перезагрузка
halt(false, false) //мягкое выключение
halt(false, true) //жесткое выключение
Lock() //блокировка ОС
Ну вот и все с первым способом разобрались :). Готовую библиотеку реализующую данный метод можно скачать на этой странице под названием AReboot. Поехали дальше...

CMD команда:

Это вариант на любителя, по объему работы он гораздо меньше, давай те я покажу как выключить машину:
System.Diagnostics.Process p = new System.Diagnostics.Process();
              p.StartInfo.FileName = "cmd.exe";
              p.StartInfo.Arguments = "/c shutdown -s -t 00";
             p.Start();
Перезагрузка:
System.Diagnostics.Process p = new System.Diagnostics.Process();
              p.StartInfo.FileName = "cmd.exe";
              p.StartInfo.Arguments = "/c reboot-s -t 00";
             p.Start();
Вот и все :) Используйте тот метод который вам больше нравиться, и отвечает вашей идеологии. Существует еще много способов выполнить поставленную задачу, те что были озвучены - основные. Пробуйте, экспериментируйте, успехов вам.

P.S. Озвученный тут метод с WMI я опишу будущей статье, когда будет время.