Saturday 7 August 2010

Визначення версії ОС та створення Запланованого завдання.

Сьогодні ми поговримо про визначення версії операційної системи на якій запущена програма, та відповідно від версії віндовса визвати створення нового "Запланованого завдання" (Scheduled tasks).
INTRO
На роботі постало просте завдання, добавити нове завдання до Менеджера завдань.
Після отримання завдання та пошуку даних в інтернеті було знайдено два варіанти:
  • Використання сторонніх бібліотек.
  • Створення таску через параметри командної стрічки.
Так як підтягувати сторонні бібліотеки до проекту не рекомендувалось, я зупинився на конфігуруванні таску з командної стрічки. Зважаючи на цпецифіку необхідного нам таску виявилось що в віндовсі ХР, через доступні параметри таск повністю сконфігурувати не вдасться тому що параметр /DISABLE став доступним лише після виходу Вісти. Було прийняте рішення реалізувати створення спираючись на версію ОС і при можливості створити таск повністю а коли такої можливості немає задати максимум можливих параметрів, а ті що залишились юзер введе сам.

Для початку визначаємо версію ОС:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Bats.Common.ServiceAgents
{
  public class OSInfo
  {
    private static OSNames sName = OSNames.Windows_XP;

    public enum OSNames
    {
      Windows_95_OSR2,
      Windows_95,
      Windows_98_Second_Edition,
      Windows_98,
      Windows_Me,
      Windows_NT_3_51,
      Windows_NT_4_0,
      Windows_NT_4_0_Server,
      Windows_2000,
      Windows_XP,
      Windows_Server_2003,
      Windows_Vista,
      Windows_7,
      Windows_Server_2008
    }

    [StructLayout(LayoutKind.Sequential)]
    protected struct OSVERSIONINFOEX
    {
      public int OSVersionInfoSize;
      public int MajorVersion;
      public int MinorVersion;
      public int BuildNumber;
      public int PlatformId;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
      public string SzCSDVersion;
      public short WServicePackMajor;
      public short WServicePackMinor;
      public short WSuiteMask;
      public byte WProductType;
      public byte WReserved;
    }

    [DllImport("kernel32.dll")]
    protected static extern bool GetVersionEx(ref OSVERSIONINFOEX osVersionInfo);

    /// <summary>
    /// Gets the name of the operating system running on this computer.
    /// </summary>
    public static OSNames Name
    {
      get
      {
        //if (sName != null)
        //{
        //  return sName;
        //}

        //string name = "unknown";
        OSNames name = OSNames.Windows_XP;

        OperatingSystem osVersion = Environment.OSVersion;
        OSVERSIONINFOEX osVersionInfo = new OSVERSIONINFOEX();
        osVersionInfo.OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX));

        if (GetVersionEx(ref osVersionInfo))
        {
          int majorVersion = osVersion.Version.Major;
          int minorVersion = osVersion.Version.Minor;

          switch (osVersion.Platform)
          {
            case PlatformID.Win32Windows:
              {
                if (majorVersion == 4)
                {
                  string csdVersion = osVersionInfo.SzCSDVersion;
                  switch (minorVersion)
                  {
                    case 0:
                      if (csdVersion == "B" || csdVersion == "C")
                      {
                        name = OSNames.Windows_95_OSR2;
                      }
                      else
                      {
                        name = OSNames.Windows_95;
                      }
                      break;
                    case 10:
                      if (csdVersion == "A")
                      {
                        name = OSNames.Windows_98_Second_Edition;
                      }
                      else
                      {
                        name = OSNames.Windows_98;
                      }
                      break;
                    case 90:
                      name = OSNames.Windows_Me;
                      break;
                  }
                }
                break;
              }

            case PlatformID.Win32NT:
              {
                byte productType = osVersionInfo.WProductType;

                switch (majorVersion)
                {
                  case 3:
                    name = OSNames.Windows_NT_3_51;
                    break;
                  case 4:
                    switch (productType)
                    {
                      case 1:
                        name = OSNames.Windows_NT_4_0;
                        break;
                      case 3:
                        name = OSNames.Windows_NT_4_0_Server;
                        break;
                    }
                    break;
                  case 5:
                    switch (minorVersion)
                    {
                      case 0:
                        name = OSNames.Windows_2000;
                        break;
                      case 1:
                        name = OSNames.Windows_XP;
                        break;
                      case 2:
                        name = OSNames.Windows_Server_2003;
                        break;
                    }
                    break;
                  case 6:
                    switch (productType)
                    {
                      case 1:
                        switch (minorVersion)
                        {
                          case 0:
                            name = OSNames.Windows_Vista;
                            break;
                          case 1:
                            name = OSNames.Windows_7;
                            break;
                        }
                        break;
                      case 3:
                        name = OSNames.Windows_Server_2008;
                        break;
                    }
                    break;
                }
                break;
              }
          }
        }

        sName = name;
        return name;
      }
    }
  }
}

* This source code was highlighted with Source Code Highlighter.

Далі пишемо класс для додавання таску:

using System;
using System.Diagnostics;
using System.Text;

namespace Bats.Common.ServiceAgents
{
  public static class TaskScheduler
  {
    /// <summary>
    /// Greate new Schelder task, wich will start every hour
    /// </summary>
    /// <param name="user">User name with domain</param>
    /// <param name="pass">User password</param>
    /// <param name="pathToFile">Path to the executable file</param>
    /// <returns>True if operation completed successful </returns>
    public static bool CreateTask(string user, string pass, string pathToFile)
    {
      Process schedule = new Process();
      schedule.StartInfo.FileName = "schtasks";

      StringBuilder commandLineParams = new StringBuilder();

      if (OSInfo.Name == OSInfo.OSNames.Windows_XP || OSInfo.Name == OSInfo.OSNames.Windows_2000 || OSInfo.Name == OSInfo.OSNames.Windows_Server_2003)
      {
        commandLineParams.AppendFormat("/Create /TN PMReadyTask /SC HOURLY /TR \"\\\"{2}\\\" auto \" /ST 06:00:00 /RU {0} /RP {1} ", user, pass, pathToFile);
        schedule.StartInfo.Arguments = commandLineParams.ToString();
      }
      else if (OSInfo.Name == OSInfo.OSNames.Windows_Vista || OSInfo.Name == OSInfo.OSNames.Windows_7 || OSInfo.Name == OSInfo.OSNames.Windows_Server_2008)
      {
        commandLineParams.AppendFormat("/Create /TN PMReadyTask /SC HOURLY /TR \"\\\"{2}\\\" auto \" /ST 06:00:00 /RU {0} /RP {1} /DISABLE", user, pass, pathToFile);
        schedule.StartInfo.Arguments = commandLineParams.ToString();
      }
      schedule.Start();

      schedule.WaitForExit();
      if (0 != schedule.ExitCode)
      {
        return false;
      }
      return true;

    }

    /// <summary>
    /// Remove an existing Task by name
    /// </summary>
    /// <param name="taskName">Name of the task to remove</param>
    public static void RemoveTask(string taskName)
    {
      Process schedule = new Process();
      schedule.StartInfo.FileName = "schtasks";

      schedule.StartInfo.Arguments = " /delete /tn " + taskName + "/f";

      schedule.Start();
    }

  }
}

* This source code was highlighted with Source Code Highlighter.

По суті два класи повністю робочі і їх можна добавляти до проекту такими як вони є, але дещо для джуніорів, все таки обяснити не помішає.

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

class OSInfo
Клас містить статичну властивість яка саме й повертає нам імя операційної системи. Імена у нас винесені в енам OSNames. OSVERSIONINFOEX та GetVersionEx нам необхідні для роботи з бібліотекою kernel32.dll, яка відноситься до неуправляємого коду і відповідно C# не може працювати з її функціями на пряму. На даний час вам необхідно зрозуміти лише роботу властивості OSNames. про роботу з некерованим (unmanaget) кодом я постараюсь написати невеличкий титоріал.


class TaskScheduler
Цей клас містить всього два методи обидва вони статичні тобто для їх виклику не потрібно створювати обєкт класу, CreateTask служить для створення завдання, а RemoveTask для його видалення.
CreateTask приймає три параметри необхідних в моєму випадку, це імя та пароль користувача від якого запускатиметься програма вказана третім параметром.
Далі створюємо процес, та запускаємо в ньому программу schtasks з відповідними параметрами. Після закінчення її роботи аналізуємо повернутий код і в залежності від нього повертаємо true або false.

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

2 comments:

  1. Що таке "PMReadyTask". Я так розумію що то назва таску. Якщо то дійсно так, то у твоєму дизайні є гапа.

    CreateTask(string user, string pass, string pathToFile) не приймає назву таска :)

    і ще не дуже ясно що має робити pathToFile, оскільки воно вроді стоїть за параметром для паролю....


    Дай знати якщо я щось не так розумію.

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

    "і ще не дуже ясно що має робити pathToFile, оскільки воно вроді стоїть за параметром для паролю...."
    якщо ти про наступне то тут все працює добре, але розміщення параметрів дійсно не по порядку (2, 0, 1)

    "/Create /TN PMReadyTask /SC HOURLY /TR \"\\\"{2}\\\" auto \" /ST 06:00:00 /RU {0} /RP {1} /DISABLE", user, pass, pathToFile

    Сама змінна "pathToFile" містить шлях до файлу який необхідно запускати.

    ПС. Мені дійсно не подобається те як я це реалізував, хотілось би зробити це через АПІ менеджера задач, ну або якось більш розумно а то дивлюсь на цей код і ... )) Але в даному випадку мабуть це дійсно оптимальний варіант так як від нього ніякої особливої фінкціональності не вимагалось.

    ReplyDelete