В одному з своїх постів, я писав про проблему з друком, коли переставала працювати служби «Диспетчер друку», та як можна вирішити цю проблему за допомогою bat-файлу з набором команд.
Але враховуючи, що зараз я працюю програмістом, та ще й в блозі з’явився новий розділ присвячений мові програмування C#, то ж я вирішив написати пару простих застосунків, що вирішували б ту ж проблему, але більш витончено!
Виникає логічне питання «а як саме витончено, в чому це полягає?». Ну наприклад, bat-файл з набором команд просто зупинить службу, а потім її знову запустить, і це якщо не виникне якихось непередбачуваних моментів. А може бути все, хоча все передбачити неможливо =)
Так от, ідея полягала в тому, що залежно від статусу самої служби, буде виконуватись, та чи інша маніпуляція з нею (так, це можна було б заморочитись, та зробити інакше), а потім буде робитись запис до системного журналу. Це в якійсь мірі дало б змогу нашим енікейщикам дізнатись корисну інформацію (принаймі я так гадаю). Хоча, за необхідності можна додати й інший функціонал, але це вже, якщо такий буде потрібен (наприклад, відправка звітів по email).
Примітка: Перш ніж почати, слід пам'ятати, що для коректної робота, на комп'ютері, де планується застосовувати застосунки, необхідно встановити .NET Runtime 6+.
От же приступимо!
Перелік того, що повинен робити застосунок зміни стану служби:
- Перевіряти стан служби
- Виконувати маніпуляції зі службою
- Робити запис до системного журналу зі змістом того, яку саме дію було виконано
Для цього було створено простий проект консольного застосунку на основі .NET 6 з наступним кодом:
using Spooler.Properties;
using System.Diagnostics;
using System.ServiceProcess;
ServiceController spoolService = new ServiceController("spooler");
void EventLogWrite(string message, EventLogEntryType type, int codeEventId)
{
string sourceForLog = "SpoolerRestarter";
EventLog.WriteEntry(sourceForLog, message, type, codeEventId);
}
try
{
switch (spoolService.Status)
{
case ServiceControllerStatus.Running:
spoolService.Stop();
spoolService.WaitForStatus(ServiceControllerStatus.Stopped);
spoolService.Start();
spoolService.WaitForStatus(ServiceControllerStatus.Running);
EventLogWrite(Resources.Restarted, EventLogEntryType.Warning, 150);
break;
case ServiceControllerStatus.Stopped:
spoolService.Start();
spoolService.WaitForStatus(ServiceControllerStatus.Running);
EventLogWrite(Resources.Started, EventLogEntryType.Warning, 150);
break;
case ServiceControllerStatus.Paused:
spoolService.Start();
spoolService.WaitForStatus(ServiceControllerStatus.Running);
EventLogWrite(Resources.Started, EventLogEntryType.Warning, 150);
break;
default:
spoolService.Stop();
spoolService.WaitForStatus(ServiceControllerStatus.Stopped);
spoolService.Start();
spoolService.WaitForStatus(ServiceControllerStatus.Running);
EventLogWrite(Resources.UnknownRestarted, EventLogEntryType.Warning, 150);
break;
}
}
catch (InvalidOperationException)
{
EventLogWrite(Resources.UnknownError, EventLogEntryType.Error, 160);
}
spoolService.Close();
Ніяких дій від користувача від не потребує, що дає змогу автоматично запускати його при виникненні проблем зі службою. Що теж було згадано в попередньому пості.
І на цьому можна було б зупинитись, але ж бажання ще щось зробити, дуже сильне. Тому я також створив застосунок, суть якого майже та сама, що і у bat-файлу, бо нажаль внятної інформації по написанню того ж самого, але виключно за допомогою мови програмування C#, я не знайшов (не виключаю, що погано шукав, тому як знайду, обов’язково перероблю). І скажімо так, в теперішньому вигляді цей застосунок має наступний код:
using System;
using System.Diagnostics;
using System.Security.Principal;
namespace SetServicePermissions
{
internal class Program
{
static void Main(string[] args)
{
if (!IsRunAsAdmin())
{
// Якщо програма не запущена від імені адміністратора системи, робимо перезапуск з запитом цих прав.
RestartAsAdmin();
return;
}
string currentPermissions = GetServicePermissions("spooler");
if (IsServicePermissionSet(currentPermissions))
{
Console.WriteLine("Доступ до служби вже було надано.");
}
else
{
Console.WriteLine("\nВстановлюємо права на роботу зі службою без прав адміністратора.");
SetServicePermissions("spooler");
}
// Викликаємо метод перевірки доступу до джерела подій в журналі
EventLogSourceCheck();
Console.WriteLine("Натисніть клавішу Enter для виходу.");
Console.ReadLine();
}
#region GetServicePermissions
/// <summary>
/// Метод, що отримує поточні значення дозволу доступу до служби.
/// </summary>
/// <param name="serviceName">Назва необхідної служби в форматі string</param>
/// <returns>Строку з набором символів у вигляді синтаксису Security Description Definition Language</returns>
static string GetServicePermissions(string serviceName)
{
string command = $"sc sdshow {serviceName}";
return ExecuteCommand(command);
}
#endregion
#region SetServicePermissions
/// <summary>
/// Метод для встановлення прав на зміну стану служби викристовуючи синтаксис Security Description Definition Language
/// </summary>
/// <param name="serviceName">Назва необхідної служби в форматі string</param>
static void SetServicePermissions(string serviceName)
{
string command = $"sc sdset {serviceName} D:(A;;0x30;;;WD)(A;;CCLCSWLOCRRC;;;AU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWRPWPDTLOCRRC;;;SY)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)";
ExecuteCommand(command);
}
#endregion
#region ExecuteCommand
/// <summary>
/// Метод для виконання команд через командну строку Windows
/// </summary>
/// <param name="command">Код команди в форматі string</param>
/// <returns></returns>
static string ExecuteCommand(string command)
{
ProcessStartInfo processInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
processInfo.RedirectStandardOutput = true;
processInfo.UseShellExecute = false;
processInfo.CreateNoWindow = true;
Process process = new Process();
process.StartInfo = processInfo;
process.Start();
string output = process.StandardOutput.ReadToEnd();
return output;
}
#endregion
#region IsServicePermissionSet
/// <summary>
/// Метод пошуку підстроки, що вказує чи були надані права на зміну стану служби, або ні.
/// </summary>
/// <param name="permissionsOutput"></param>
/// <returns>true - якщо доступ вже надано, false - якщо ні</returns>
static bool IsServicePermissionSet(string permissionsOutput)
{
// Перевіряємо, чи містить вхідна строка необхідну підстроку
return permissionsOutput.Contains("(A;;RPWP;;;WD)");
}
#endregion
#region IsRunAsAdmin
/// <summary>
/// Метод, що перевіряє чи було запущено програму з правами адміністратора системи.
/// </summary>
/// <returns>true - якщо так, false - якщо ні</returns>
static bool IsRunAsAdmin()
{
WindowsIdentity identity = WindowsIdentity.GetCurrent();
WindowsPrincipal principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
#endregion
#region RestartAsAdmin
/// <summary>
/// Метод для для перезапуска програмі за запитом надати права адміністратора системи.
/// </summary>
static void RestartAsAdmin()
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.FileName = Process.GetCurrentProcess().MainModule.FileName;
startInfo.Verb = "runas"; // Запуск процесу з правами адміністратора
try
{
Process.Start(startInfo);
}
catch (Exception ex)
{
Console.WriteLine("Помилка перезапуску програми з правами адміністратора: " + ex.Message);
}
}
#endregion
#region EventLogSourceCheck
/// <summary>
/// Метод перевірки наявності джерела подій в журналі, та в разі необхідності, його створення
/// </summary>
static void EventLogSourceCheck()
{
string sourceForLog = "SpoolerRestarter";
string logName = "Application";
// Перевіряємо, чи існує джерело журналу подій
if (!EventLog.SourceExists(sourceForLog))
{
// Створюємо нове джерело журналу подій
EventLog.CreateEventSource(sourceForLog, logName);
Console.WriteLine("Надано доступ до журналу подій: \"{0}\"", logName);
}
else
{
Console.WriteLine("Джерело журналу подій \"{0}\" вже існує.", logName);
}
}
#endregion
}
}
Його задача полягає в перевірці, чи було запущено застосунок з правами адміністратора системи, якщо ні, то зробити перезапуск та здійснити запит до користувача на запуск застосунка з правами адміністратора системи. Далі буде виконано перевірку, чи права було надано, і залежно від результату, буде виконано їх надання, або користувача буде проінформовано, що дану дію вже було здійснено раніше.
Примітка: Як ідея, потрібно додати функціонал, запису до системного журналу про виконання дій цього застосунку.
Гадаю на цьому все. Хоча ні, репозиторій даного проекту, можна знайти за цим посиланням.