Технология UAC — не лишний компонент безопасности ОС Windows последних версий и пользователи приходят к этой мысли, борясь с malware и вирусами. Программистам, в свою очередь, стоит грамотно подходить к написанию приложений и принимать во внимание наличие такого «обстоятельства».
На хабре и вообще в сети много статей на тему «Как отключить UAC», «Как обойти UAC» и др. Но зачем отключать, функция ведь полезная? Зачем обходить, мы ведь не злоумышленники?
Нужно дружить!
Ниже я расскажу как это делать в Вашем приложении.
Манифест
Для начала рассмотрим самый простой и некрасивый, по моему мнению, вариант — редактирование манифеста. Чем он плох? Тем, что подходит только для тех приложений, которым всегда нужно иметь привелегии администратора. Как это будет выглядеть? Пользователь при запуске вашего приложения получить знакомое окошко, в котором ему нужно будет подтвердить разрешение на выполнение программой действий с привилегиями администратора. И так каждый раз при запуске программы. В принципе, вариант приемлим для программ, которые запускаются нечасто и в одном экземпляре.
Сразу нужно сказать, что в автозапуск (не уверен, что всеми способами, но по крайней мере, через реестр) такие приложения помещать нельзя. Windows их просто прихлопнет на старте, не показав никакого окна UAC. Может быть, в этом случае есть смысл использовать технологии служб Windows.
Итак, реализация (взято отсюда)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
Возможные уровни привилегий:
- asInvoker — уровень прав текущего пользователя (процесса-родителя). Этот вариант устанавливается по-умолчанию.
- highestAvailable — наивысший уровень прав для текущего пользователя. Т.е. администратор получит полные права после подтверждения юзером кнопкой в окошке, обычный же пользователь ничего не получит.
- requireAdministrator — самый интересный вариант. Всегда запрашивает разрешение или ввод авторизационных данных администратора.
Но такое решение подходит далеко не для всех приложений. Лично меня раздражала бы программа, которая все время запрашивает права администратора, хотя они ей нужны, к примеру, только для записи в реестр на этапе настройки, которая проводится один раз.
Другие решения
Проверка на наличе прав
В первую очередь после запуска приложения или в тех местах, где это требуется, нужно проверить не запущены ли мы уже с правами администратора (вдруг юзер уже отключил UAC). Если это так, то большинства последующих манипуляций можно будет избежать. Проверка делается просто (да, кстати, код здесь и дальше на C#):
public static bool IsAdmin()
{
System.Security.Principal.WindowsIdentity id = System.Security.Principal.WindowsIdentity.GetCurrent();
System.Security.Principal.WindowsPrincipal p = new System.Security.Principal.WindowsPrincipal(id);
return p.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
Запуск процесса с запросом прав
Допустим, нет у нас прав. Что же дальше? Приложение, запущенное с какими-либо правами, не может их изменить в процессе своей работы. Для выполнения действий с повышенными привилегиями необходимо запускать новый процесс с запросом прав. Как вообще это сделать:
public static void RunAsAdmin(string aFileName, string anArguments)
{
System.Diagnostics.ProcessStartInfo processInfo = new System.Diagnostics.ProcessStartInfo();
processInfo.FileName = aFileName;
processInfo.Arguments = anArguments;
processInfo.UseShellExecute = true;
processInfo.Verb = "runas"; // здесь вся соль
System.Diagnostics.Process.Start(processInfo);
}
Но какое же приложение запускать? Здесь могут быть варианты:
1. Запуск системного приложения с параметрами. Например, вам нужно стартануть службу или изменить значение в реестре. Можно воспользоватся WinAPI или его оберткой в классах .NET, но если ваше приложение не имеет привилегий, то ничего не получится. Для запуска службы DHCP Client, к примеру, можно воспользоватся командной строкой
sc.exe start dhcp
Это самый простой и приятный вариант для выполнения служебных действий с системой, но не всегда все так просто.
2. Запуск собственного дополнительного приложения с параметрами. Допустим, вам ну очень нравится использовать WinAPI и не хочется разбиратся с системными утилитами или ваша задача не столь банальна, как приведенная выше. В таком случае вы можете написать маленькую консольную утилиту, которая будет выполнять нужные вам действия. Но это и лишние затраты времени, и необходимость поддержки дополнительной утилиты, интерфейсов взаимодействия и т.д.
2а. Дополнительное приложение может запускатся только один раз и продолжать висеть в памяти. Организовав общение с ним из основного приложения, вы можете выполнять нужные вам административные задачи без последующих запросов прав. Но это еще более сложный вариант. Опять же таки, в этом случае есть смысл посмотреть в сторону служб Windows в качетсве дополнительного приложения, которые по-умолчанию запускаются с привилегиями администратора.
3. Запуск еще одного экземпляра основного приложения с параметрами. Допустим, у вас есть пять простых действий, которые нужно выполнять с правами администратора. Забейте для них параметры командной строки вашего же приложения и на запуске проверяйте их наличие. Получив какой-либо из этих параметров, выполните соответствующее действие и завершите работу. Данный вариант, судя по StackOverflow является самым распространенным, по скольку код остается в рамках одного приложения, да и реализуется все просто.
4. Введение двух режимов работы приложения. Если приложению права администратора необходимы редко, то стоит ввести два режима работы: обычный и привилегированый. В обычном режиме (а мы это определяем с помощью указанной выше функции IsAdmin()) операции, требующие права администратора, остаются заблокированными, но у пользователя появляется возможность перезапустить приложение с правами админа и получить доступ к забокированному функционалу. В привилегированном режиме (IsAdmin() возвращает true) мы не блокируем функционал.
Таким образом, запрос появляется перед пользователем только один раз и в процессе подальшей работы с приложением больше не будет отвлекать. Если же пользователь отключил UAC, то об «обычном» режиме он даже не узнает.
Данный подход требует немного усилий на начальном этапе, но дальше позволяет писать приложение без каких-либо ограничений и вынесений кода в другие программы и блоки. Небольшой пример будет рассмотрен ниже.
Оформление приложений
Всем нам хочется писать стильные и красивые оконные приложения и в вопросах использования UAC без фирменного щитка на контролах ну никак не обойтись.
Такой щит размещается на кнопках, link-label'ах или других элементах управления, после нажатия на которые пользователь увидит запрос от UAC. К счастью, нам не придется таскать повсюду картинку, поскольку в системе, как ни странно, она уже есть и ее можно получить.
Для WinForms-приложений можно указать системе разместить иконку щита на кнопке.
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(HandleRef hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
public static void SetButtonShield(Button btn, bool showShield)
{
// BCM_SETSHIELD = 0x0000160C
btn.FlatStyle = FlatStyle.System;
SendMessage(new HandleRef(btn, btn.Handle), 0x160C, IntPtr.Zero, showShield ? new IntPtr(1) : IntPtr.Zero);
}
Для тех, кто уже отказался от WinForms и перешел к разработке приложений с помощью WPF, также есть решение. Для того, чтобы получить ImageSource иконки и указать ее в каком-либо контроле, можно использовать следующий код.
System.Drawing.Icon img = System.Drawing.SystemIcons.Shield;
System.Drawing.Bitmap bitmap = img.ToBitmap();
IntPtr hBitmap = bitmap.GetHbitmap();
ImageSource wpfBitmap =
System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
hBitmap, IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
Пример
Рассмотрим теперь маленький пример, реализующий изложенные выше принципы.
В приложении имеется кнопка, которая должна выполнять действия с правами администратора (у меня она просто отображает MessageBox). Если приложение уже запущено с правами, то мы оставляем ее доступной. Если же нет, кнопку блокируем и отображаем панельку с предупреждением и другой кнопкой, позволяющей перезапустить приложение в привилегированном режиме. На кнопке отображаем православную иконку щита.
Кроме того, окно приложения после перезапуска отображается в том же месте и с теми же размерами благодаря передаче контекста через командную строку.
Конечно, пример простейший, но он в общих чертах демонстрирует самый близкий мне подход к решению проблемы взаимодействия с UAC. Код примера на github.
Буду рад услышать ваши замечания, коментарии и пожелания.
Автор: yan4esko