21 декабря 2011 г.

Удалённая установка программы на компьютер пользователя


Долгое время пишу BAT скриптики, но его возможности не безграничны, да и не всегда есть возможность нормально и правильно обработать исключения. Поэтому, по рекомендации нашего админа, решил установить Visual Studio 2010 C# Express. Бесплатная, а мне больше и не надо))

Собственно, первым делом решил переписать скриптик, который ставил 1С:Предприятие на машины пользователей в тихом режиме, копировал файл настройки hasp-ключа и файл со списком ИБ. Ставится с использованием программы Русиновича PSExec. Собственно, вот что у меня получилось. Корявенько, правда, хотелось бы переписать как должно быть, но оно работает, а мне больше и не надо.
Исходник:
using System;
using System.Net.NetworkInformation;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Win32;

namespace Install_Program
{
 public class WMI
 {
  string WMI_UN;
  int WMI_FS;
  public string WMI_UserName(String CompName)
  {
   // Создаём новое соединение к WMI удалённого компьютера
   ConnectionOptions connection = new ConnectionOptions();

   // Соединяемся с WMI целевого компьютера...
   ManagementScope scope = new ManagementScope(@"\\" + CompName + @"\root\CIMV2", connection);
   scope.Connect();
   // ...и читаем информацию о компьютере
   ObjectQuery UserName = new ObjectQuery("SELECT UserName FROM Win32_ComputerSystem");
   ManagementObjectSearcher sUserName = new ManagementObjectSearcher(scope, UserName);
   // Находим в этой информации текущего пользователя
   foreach (ManagementObject queryObj in sUserName.Get())
   {
    String wmi_un = queryObj["UserName"].ToString();
    // Выделяем в ней часть хоста и часть с именем пользователя
    String[] un = wmi_un.Split(new char[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
    WMI_UN = un[1];
   }
   return WMI_UN;
  }
  public int WMI_FreeSpace(String CompName)
  {
   // Создаём новое соединение к WMI удалённого компьютера
   ConnectionOptions connection = new ConnectionOptions();

   // Соединяемся с WMI целевого компьютера...
   ManagementScope scope = new ManagementScope(@"\\" + CompName + @"\root\CIMV2", connection);
   scope.Connect();
   // ...и читаем информацию о компьютере
   ObjectQuery FreeSpace = new ObjectQuery("SELECT FreeSpace, Name FROM Win32_LogicalDisk WHERE Name = 'C:'");
   ManagementObjectSearcher sFreeSpace = new ManagementObjectSearcher(scope, FreeSpace);
   foreach (ManagementObject queryObj in sFreeSpace.Get())
   {
    // Получаем свободное пространство на диске C:, в ГБ
    long wmi_fs = long.Parse(queryObj["FreeSpace"].ToString());
    WMI_FS = (int)(wmi_fs / 1024 / 1024 / 1024);
   }
   return WMI_FS;
  }
 }

 public class Conf
 {
  public string User = @" ";
  public string Password = @" ";
  public string KeyInstall = @"-d";
  public string FileComp = @"1C.txt";
  public string ProgConf = @"D:\TT";
  [XmlArrayAttribute("Items")]
  public Item[] Items;
 }
 
 public class Item
 {
  public string InstallProgram;
  public string Key;
  public string From;
  public string To;
 }

 public class Program
 {
  public static void WriteFile()
  {
   int i;
   // Читаем из файла списка ИБ 1С все строки
   string[] str = File.ReadAllLines(@"copy\ibases.v8i", Encoding.Default);
   // И для каждой строки, с нулевой до последней...
   for (i = 0; i < str.Length; i++)
   {
    // ...ищем начинающуюся с "ID=" и заменяем её на
    if (str[i].StartsWith("ID="))
    {
     str[i] = ("ID=" + Guid.NewGuid().ToString());
     break;
    }
   }
   // Всё сделали, записываем изменения обратно
   File.WriteAllLines(@"copy\ibases.v8i", str, Encoding.Default);
  }

  public static void Logger(String LogText)
  {
   File.AppendAllText("log.ini", DateTime.Now.ToString() + "\t" + LogText, Encoding.Default);
  }

  public static void Main(string[] args)
  {
   // Определяем переменные
   String S, command;
   // Для файла конфигурации
   String User, Password, KeyInstall, FileComp, ProgConf;
   //Подключаем класс, который получает данные о компьютере
   WMI wmi_info = new WMI();
   // Десериализуем файл конфигурации
   String[] XMLFile = Directory.GetFiles(@".\", "*.xml");
   XmlSerializer serializer = new XmlSerializer(typeof(Conf));
   Stream stream = new FileStream(XMLFile[0], FileMode.Open);
   Conf conf = (Conf)serializer.Deserialize(stream);
   //Чтобы было удобнее работать с десериализованными данными
   User = conf.User;
   Password = conf.Password;
   KeyInstall = conf.KeyInstall;
   FileComp = conf.FileComp;
   ProgConf = conf.ProgConf;
   Item[] items = conf.Items;
   // Создаём поток для файла откуда читать номера компьютеров
   StreamReader sr = new StreamReader(FileComp, Encoding.Default);
   try
   {
    // Пока не достигнем конца файла...
    while (!sr.EndOfStream)
    {
     // ... читаем строки
     S = sr.ReadLine();
     // Парсим список компьютеров и выделяем оттуда имя компьютера
     String[] A = S.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
     // Пингуем
     Ping pingSender = new Ping();
     // Если строка не начинается с "#", пинг успешен и хватает места на установку программы то работаем
     if (A[0][0] != '#')
     {
      PingReply reply = pingSender.Send(A[0], 100);
      if (reply.Status == IPStatus.Success)
      {
       //Получаем имя текущего пользователя с удалённого компьютера
       String wmi_un = wmi_info.WMI_UserName(A[0]);
       Logger("=====================================================\n");
       Logger("Установка Программы" + ProgConf + "\n");
       Logger("=====================================================\n");
       //Ну а если места недостаточно, то пишем в лог, сообщаем в консоль и переходим к следующего компьютеру
       if (wmi_info.WMI_FreeSpace(A[0]) <= 2)
       {
        Logger(A[0] + "\t" + wmi_un + "\tНедостаточной места на диске C: для установки программы" + "\n");
        Console.WriteLine(A[0] + "\t" + wmi_un + "\tНедостаточной места на диске C: для установки программы");
        continue;
       }
       //Редактируем файл списка ИБ 1С
       WriteFile();
       for (int i = 0; i < items.Length; i++)
       {
        String CopyTo = items[i].To.Replace("A[0]", A[0]).Replace("A[1]", A[1]).Replace("B[0]", wmi_un);
        command = "-u " + User + " -p " + Password + @" \\" + A[0] + " " + KeyInstall + " " + items[i].InstallProgram;
        switch (items[i].Key)
        {
         //Ключ "с" обозначает копирование файлов (от Copy)
         case "c":
          try
          {
           File.Copy(items[i].From, CopyTo, true);
          }
          catch (UnauthorizedAccessException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tОтсутствют права на копирование файла " + items[i].From + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tОтсутствют права на копирование файла " + items[i].From);
          }
          catch (IOException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tФайл " + items[i].From + " уже существует или его перезапись невозможна\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tФайл " + items[i].From + " уже существует или его перезапись невозможна");
          }
          catch
          {
           Logger(A[0] + "\t" + wmi_un + "\tВозникла какая-то ошибка при копировании файла " + items[i].From + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tВозникла какая-то ошибка при копировании файла " + items[i].From);
          }
          break;
         //Ключ "md" обозначает создание дирректории (от Make Directory)
         case "md":
          try
          {
           Directory.CreateDirectory(CopyTo);
          }
          catch (UnauthorizedAccessException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tОтсутствют права на создание папки " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tОтсутствют права на создание папки " + items[i].To);
          }
          break;
         //Ключ "rd" обозначает удаление дирректории (от Remove Directory)
         case "rd":
          try
          {
           Directory.Delete(CopyTo, true);
          }
          catch (UnauthorizedAccessException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tОтсутствют права на создание папки " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tОтсутствют права на создание папки " + items[i].To);
          }
          catch (DirectoryNotFoundException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tОтсутствует или не может быть найдена папка " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tОтсутствует или не может быть найдена папка " + items[i].To);
          }
          catch (IOException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tКаталог только для чтения или занят " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tКаталог только для чтения или занят " + items[i].To);
          }
          break;
         //Ключ "rf" обозначает удаление файла (от Remove File)
         case "rf":
          try
          {
           File.Delete(CopyTo);
          }
          catch (UnauthorizedAccessException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tОтсутствют права на удаление файла или файл только для чтения " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tОтсутствют права на удаление файла или файл только для чтения " + items[i].To);
          }
          catch (DirectoryNotFoundException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tУказанный путь недопустим " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tУказанный путь недопустим " + items[i].To);
          }
          catch (IOException)
          {
           Logger(A[0] + "\t" + wmi_un + "\tЗаданный файл кем-то используется " + items[i].To + "\n");
           Console.WriteLine(A[0] + "\t" + wmi_un + "\tЗаданный файл кем-то используется " + items[i].To);
          }
          break;
         //Ключ "i" обозначает установку (от Install)
         case "i":
          Process.Start("psexec.exe", command);
          System.Threading.Thread.Sleep(30000);
          break;
        }
       }
       RegistryKey key = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, A[0])
        .OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", true);
       key.SetValue("SAP_CODEPAGE", "1504");
       //Сообщаем, что что-то было сделано
       Logger(A[0] + "\t" + wmi_un + "\tНа компьютер установлено и скопировано необходимое ПО\n");
       Console.WriteLine(A[0] + "\t" + wmi_un + "\tНа компьютер установлено и скопировано необходимое ПО");
      }
      else
      {
       // Ну а если компьютер выключен или не пингуется, дадим знать об этом в логе
       Logger(A[0] + " Компьютер выключен или не пингуется\n");
       Console.WriteLine(A[0] + " Компьютер выключен или не пингуется");
      }
     }
    }//while
   }//try
   catch (IOException exc)
   {
    Console.WriteLine("Ошибка ввода-вывода:\n" + exc.Message);
   }
   finally
   {
    sr.Close();
   }
  }
 }
}
Файл "comps.txt" имеет такой вид:
ИмяКомпьютера_ИмяПользователя
ИмяКомпьютера_ИмяПользователя
Имя пользователя, правда, теперь берётся с помощью WMI, но переписывать лень, да и вдруг пригодится))

Вот как это работает. Есть файл "comps.txt", в котором содержится имя компьютера, на который необходимо установить 1С (можно и другой продукт, с небольшими изменениями в коде). Парсим строку, берём из неё имя компьютера и пингуем. Если пинг проходит, то запускаем установку приложения и ждём 20 секунд, пока приложение поставится. Через 20 секунд получаем имя текущего пользователя, в профиль которого нам необходимо поместить файл содержащий список Информационных Баз (ИБ), редактируем в нём поле ID, в которое генерируем GUID, необходимый для идентификации в 1С:Предприятие. Далее копируем этот файл в профиль. Ну и копируем файл "nethasp.ini", который содержит адрес сетевого ключа.

После недолгих раздумий прикрутил ещё и конфигурационный файл, чтобы каждый раз не править код под установку разных программ. Файл называется config.xml. Вот как он выглядит:
  

 
   домен\админ-пользователь
   pswd
   -d
   SAP.TXT
   SAP
 
   
        
     i
            \\InstallServer\program
     
            
        
        
     i
            \\InstallServer\program
     
            
        
        
     md
     
     
            \\A[0]\C$\Documents and Settings\B[0]\Application Data\...
        
        
     c
     
            copy\File
            \\A[0]\C$\Documents and Settings\B[0]\Application Data\...\File
        
   
 
Ключи в тэгах "Items" располагать по логической структуре: сначала ставим (ключи "i"), потом создаём директории (ключ "md") и копирование в уже созданные папки (ключ "c").

Из оставшихся задумок:
  1. Указывать в конфигурационном файле список копируемых файлов и создаваемых директорий (придумать как десериализовать массив).
  2. Обработать оставшиеся исключения и правильно их причесать.
  3. Вынести наиболее обширные части кода в модули.
БОльшая часть кода логируется в случае успеха и ошибок.

В комментариях приветствуются замечания и предложения))

Комментариев нет:

Отправить комментарий

Уважаемый комментатор, пишите грамотно.
С благодарностью, автор блога.