Контакти

 Telegram: Magnumv44

 Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.

 GitHub

 Instagram

Перезапуск служби диспетчера друку за допомогою C#

В одному з своїх постів, я писав про проблему з друком, коли переставала працювати служби «Диспетчер друку», та як можна вирішити цю проблему за допомогою 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
    }
}

Його задача полягає в перевірці, чи було запущено застосунок з правами адміністратора системи, якщо ні, то зробити перезапуск та здійснити запит до користувача на запуск застосунка з правами адміністратора системи. Далі буде виконано перевірку, чи права було надано, і залежно від результату, буде виконано їх надання, або користувача буде проінформовано, що дану дію вже було здійснено раніше.

Примітка: Як ідея, потрібно додати функціонал, запису до системного журналу про виконання дій цього застосунку.

Гадаю на цьому все. Хоча ні, репозиторій даного проекту, можна знайти за цим посиланням.