Контакти

 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
    }
}

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

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

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

We use cookies

We use cookies on our website. Some of them are essential for the operation of the site, while others help us to improve this site and the user experience (tracking cookies). You can decide for yourself whether you want to allow cookies or not. Please note that if you reject them, you may not be able to use all the functionalities of the site.