Переписка с владельцем HWINFO, Мартином Маликом (mm@hwinfo.com), в качестве предисловия:
Я:
Hi Martin, my name is Dmitry.
I am working system administrator in Russia.
I want to do auto hardware reporting from all computers of my company.
Idea is run bach file (or something like that) and get text file with current system configuration (or may be sent the report on email).
I think you’re SDK of HWINFO can do that.
Can you help me? Thanks!
Мартин:
It’s possible to do that with the HWiNFO SDK, but it’s not freeware and the price starts from 1000 EUR per year (including updates&support). So I’m not sure if this would be a choice for you.
В жизни наверно любого IT отдела наступает момент, когда необходимо провести инвентаризацию парка ПК. Одно дело, когда все они включены в домен, находятся в этом же здании и их не много, и совсем другое, когда компьютеров несколько сотен и находятся они по городам и весям…
HWINFO за SDK просит 1000 Евро за год…
Нафиг надо :), напишу сам, на Ruby, тем более что у Windows есть такой замечательный инструмент, как Windows Management Instrumentation (WMI).
Шаг 1. Класс для получения данных из WMI
На самом деле класс для этого создавать не обязательно, но в качестве учебных целей работы с классами — думаю не повредит.
Итак, напишем вот такой код:
require 'win32ole' require 'ruby-wmi' class GetWMIData def initialize(debug=false) @debug=debug end def getWMI(st) wmi = WIN32OLE.connect("winmgmts://") res = wmi.ExecQuery("select * from #{st}") return res end def getCPU res=[] puts "DEBUG: Центральный процессор" if @debug res << "============================" res << "====Центральный процессор===" res << "============================" for get in self.getWMI("Win32_Processor") do res << "CPU Имя: #{get.Caption}" rescue nil res << "CPU Производитель: #{get.Manufacturer}" rescue nil res << "CPU Модель: #{get.Name}" rescue nil res << "CPU Макс. частота: #{get.MaxClockSpeed}" rescue nil res << "CPU Количество ядер: #{get.NumberOfCores}" rescue nil res << "CPU S/n: #{get.ProcessorId}" rescue nil res << "CPU Ревизия: #{get.Revision}" rescue nil res << " " end return res end end
Итак, если этот класс инициализировать как:
w=GetWMIData.new puts w.getCPU
Интерпретатор выдаст нам значения параметров центрального процессора:
ruby>============================ ruby>====Центральный процессор=== ruby>============================ ruby>CPU Имя: x86 Family 15 Model 2 Stepping 7 ruby>CPU Производитель: GenuineIntel ruby>CPU Модель: Intel(R) Celeron(R) CPU 2.00GHz ruby>CPU Макс. частота: 2040 ruby>CPU Количество ядер: 1 ruby>CPU S/n: BFEBFBFF00000F27 ruby>CPU Ревизия: 519
Уже не плохо. Подробно на каждом блоке данных я останавливаться не буду, все они (процессор, ОС, программы, материнка и т.д.) будут описаны в полном коде.
Шаг 2. Отправка отчета на почту
require 'mail' def mailing(data) Mail.defaults do delivery_method :smtp, address: mail_server, port: mail_port, user_name: mail_user, password: mail_psw end mail = Mail.new mail.from = "mail_from@myil.ru" mail.to = "mail_to@admin.ru" #Здесь к теме я добавляю параметры из массива, имя ПК и Серийник операционки #(чтобы письма можно было удобнее сортировать) mail.subject = "Тема письма" mail.body = "Текст письма"+data.join("\n") mail.charset = "UTF-8" #Отправляю if mail.deliver! puts "Почта отправлена" return true else puts "ОШИБКА! Сбой отправки почты!" return false end end
Если данную функцию запустить с нашим предыдущим кодом как:
w=GetWMIData.new mailing(w.getCPU)
то в случае успеха, на адрес администратора mail_to@admin.ru придет письмо с конфигурацией процессора текущего компьютера, а интерпретатор выдаст нам строку «Почта отправлена».
Шаг 3. Шифрование отчета
Согласитесь, что часто в организации есть некоторая часть ПК, не подключенных к общей сети. Тем не менее данные с них тоже нужно получить, и для того чтобы отчет с характеристиками не был «подделан» или «в шутку исправлен» сотрудниками — мы его зашифруем.
Поможет нам в этом 7zip:
require 'seven_zip_ruby' def zipFile(file_to_zip) begin File.open("Отчет.7z", "wb") do |file| SevenZipRuby::Writer.open(file, { password: "superpassowrd" }) do |szr| szr.add_file file_to_zip end end puts "Процедура сжатия завершена успешно" return true rescue => error puts "Ошибка сжатия файла" puts error return false end end
Если в процедуру zipFile передать файл, то на выходе появится «Отчет.7z», зашифрованный паролем «superpassowrd». Дальше мы просим оператора отправить данный файл нам почтой например.
Вот и вся логика. Добавлю к этому маркеры, которыми мы будем помечать письмо, информационные сообщения для пользователей, дебаги, и так по мелочи, и получим:
Полный код
require 'win32ole' require 'ruby-wmi' require 'mail' require 'socket' require 'seven_zip_ruby' class GetWMIData def initialize(debug=false) @prgrs=0 @markers=[] @debug=debug end def marker(mark="") @markers << mark return @markers end def getWMI(st) wmi = WIN32OLE.connect("winmgmts://") res = wmi.ExecQuery("select * from #{st}") return res end def getMB res=[] @prgrs+=10 puts "DEBUG: Материнская плата" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "====Материнская плата=======" res << "============================" for get in self.getWMI("Win32_BaseBoard") do res << "MB Имя: #{get.Description}" rescue nil res << "MB Производитель: #{get.Manufacturer}" rescue nil res << "MB Модель: #{get.Model}" rescue nil res << "MB Модель2: #{get.Product}" rescue nil res << "MB Модель3: #{get.ProductMB}" rescue nil res << "MB Серийый №: #{get.SerialNumber}" rescue nil res << "MB Ревизия: #{get.Version}" rescue nil res << " " end return res end def getOS res=[] @prgrs+=10 puts "DEBUG: Операционная система" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "====Операционная система====" res << "============================" for get in self.getWMI("Win32_OperatingSystem") do #Добавляю нужные елементы в Маркер, затем буду применять это в теме письма self.marker << "#{get.CSName}" rescue nil #self.marker << "#{get.SerialNumber}" rescue nil res << "OS ПК: #{get.CSName}" rescue nil res << "OS Имя: #{get.Caption}" rescue nil res << "OS Сборка: #{get.BuildNumber}" rescue nil res << "OS Обновление: #{get.CSDVersion}" rescue nil res << "OS Пользователей: #{get.NumberOfUsers}" rescue nil res << "OS Организация: #{get.Organization}" rescue nil res << "OS Пользователь: #{get.RegisteredUser}" rescue nil res << "OS S/n: #{get.SerialNumber}" rescue nil mem=get.TotalVisibleMemorySize.to_i/1024/1024 rescue nil res << "OS Всего RAM (Gb): "+mem.to_s rescue nil res << " " end return res end def getCPU res=[] @prgrs+=10 puts "DEBUG: Центральный процессор" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "====Центральный процессор===" res << "============================" for get in self.getWMI("Win32_Processor") do #Добавляю нужные елементы в Маркер, затем буду применять это в теме письма self.marker << "#{get.ProcessorId}" rescue nil res << "CPU Имя: #{get.Caption}" rescue nil res << "CPU Производитель: #{get.Manufacturer}" rescue nil res << "CPU Модель: #{get.Name}" rescue nil res << "CPU Макс. частота: #{get.MaxClockSpeed}" rescue nil res << "CPU Количество ядер: #{get.NumberOfCores}" rescue nil res << "CPU S/n: #{get.ProcessorId}" rescue nil res << "CPU Ревизия: #{get.Revision}" rescue nil res << " " end return res end def getMEM res=[] @prgrs+=10 puts "DEBUG: Оперативная память" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "=====Оперативная память=====" res << "============================" for get in self.getWMI("Win32_PhysicalMemory") do res << "MEM Слот: #{get.BankLabel}" rescue nil res << "MEM ID Производителя: #{get.Manufacturer}" rescue nil res << "MEM Модель: #{get.Model}" rescue nil res << "MEM Частота шины: #{get.Speed}" rescue nil mem=get.Capacity.to_i/1024/1024 rescue nil res << "MEM Объем (Gb): "+mem.to_s rescue nil res << " " end return res end def getVID res=[] @prgrs+=10 puts "DEBUG: Видео система" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "=======Видео система========" res << "============================" for get in self.getWMI("Win32_VideoController") do res << "VIDEO Адаптер: #{get.DeviceID}" rescue nil res << "VIDEO Модель: #{get.Description}" rescue nil res << "VIDEO Производитель: #{get.AdapterCompatibility}" rescue nil res << "VIDEO Чип: #{get.VideoProcessor}" rescue nil mem=get.AdapterRam.to_i/1024/1024 rescue nil res << "VIDEO Объем (Gb): "+mem.to_s rescue nil res << " " end return res end def getHDD res=[] @prgrs+=10 puts "DEBUG: Жесткие диски" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "=======Жесткие диски========" res << "============================" for get in self.getWMI("Win32_DiskDrive") do res << "HDD Описание: #{get.Caption}" rescue nil res << "HDD В системе: #{get.DeviceID}" rescue nil res << "HDD Модель: #{get.model}" rescue nil mem=get.Size.to_i/1024/1024 rescue nil res << "HDD Объем (Gb): "+mem.to_s rescue nil res << " " end return res end def getPRINT res=[] @prgrs+=10 puts "DEBUG: Установленные принтеры" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "===Установленные принтеры===" res << "============================" for get in self.getWMI("Win32_Printer") do res << "PRNTR Описание: #{get.Caption}" rescue nil res << "PRNTR Драйвер: #{get.DriverName}" rescue nil res << "PRNTR Порт: #{get.PortName}" rescue nil res << " " end return res end def getMON res=[] @prgrs+=10 puts "DEBUG: Подключенные мониторы" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "===Подключенные мониторы====" res << "============================" for get in self.getWMI("Win32_DesktopMonitor") do res << "MONITOR Модель: #{get.Description}" rescue nil res << "MONITOR Производитель: #{get.MonitorManufacturer}" rescue nil res << " " end return res end def getSOFT res=[] @prgrs+=10 puts "DEBUG: Установленные программы" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "==Установленные программы===" res << "============================" for get in self.getWMI("Win32_Product") do res << "SOFT Название: #{get.Caption}" rescue nil res << "SOFT Версия: #{get.Version}" rescue nil res << "SOFT Дата установки: #{get.InstallDate}" rescue nil res << " " end return res end def getPROC res=[] @prgrs+=10 puts "DEBUG: Запущенные процессы" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "====Запущенные процессы=====" res << "============================" for get in self.getWMI("Win32_process") do res << "PROCS Имя: #{get.Name}" rescue nil end return res end def getIP res=[] @prgrs+=5 puts "DEBUG: IP Адреса" if @debug puts ("Сбор информации: "+@prgrs.to_s+" %") res << "============================" res << "==========IP Адреса=========" res << "============================" for get in self.getWMI("Win32_NetworkAdapterConfiguration") do #Здесь начинаем записывать, только если адрес не пустой a=get.IPAddress rescue nil m=get.MACAddress rescue nil if a res << "IP Addr: "+a.join(" ") end if m res << "IP MAC: "+m.to_s res << " " end end return res end end def avalable?(debug,server,port) sock = Socket.new(:INET, :STREAM) return true if sock.connect(Socket.sockaddr_in(port, server)) rescue => error puts "DEBUG: Ошибка проверки доступности сервера" if debug puts error if debug return false end def mailing(debug,data,mail_server,mail_port,mail_user,mail_psw,mail_from,mail_to,mail_subj,mail_text,markers) Mail.defaults do delivery_method :smtp, address: mail_server, port: mail_port, user_name: mail_user, password: mail_psw end mail = Mail.new mail.from = mail_from mail.to = mail_to #Здесь к теме я добавляю параметры из массива, имя ПК и Серийник операционки #(чтобы письма можно было удобнее сортировать) mail.subject = mail_subj+markers.join(". ") mail.body = mail_text+data.join("\n") mail.charset = "UTF-8" puts "DEBUG: Попытка отправить почту" if debug if mail.deliver! puts "DEBUG: Почта отправлена" if debug return true else puts "DEBUG: ОШИБКА! Сбой отправки почты!" if debug return false end end def deleteFile(debug,file) begin File.delete(file) puts "DEBUG: Процедура удаления завершена успешно" if debug return true rescue => error puts "DEBUG: Ошибка удаления файла" if debug puts error if debug return false end end def zipFile(debug,file_zip,file_psw,file_temp) begin File.open(file_zip, "wb") do |file| SevenZipRuby::Writer.open(file, { password: file_psw }) do |szr| szr.add_file file_temp end end puts "DEBUG: Процедура сжатия завершена успешно" if debug return true rescue => error puts "DEBUG: Ошибка сжатия файла" if debug puts error if debug return false end end def writeFile(debug,data,file_temp) begin File.open(file_temp, 'w+') do |f| f.puts(data) end puts "DEBUG: Процедура записи завершена успешно" if debug return true rescue => error puts "DEBUG: Ошибка создания файла" if debug puts error if debug return false end end def writeANDzip(debug=false,data,file_temp,file_zip,file_psw) begin if writeFile(debug,data,file_temp) puts "DEBUG: Записано" if debug if zipFile(debug,file_zip,file_psw,file_temp) puts "DEBUG: сжато" if debug if deleteFile(debug,file_temp) puts "DEBUG: Удалено" if debug return true end end end rescue => error puts "DEBUG: Ошибка процедуры записи и сжатия" if debug puts error if debug return false end end def beginning(debug=false,file_name,file_exetemp,file_exezip,file_psw,mail_server,mail_port,mail_user,mail_psw,mail_from,mail_to,mail_subj,mail_text) thread=[] result=[] markers=[] w=GetWMIData.new(debug) wMI=[w.getOS,w.getMB,w.getCPU,w.getMEM,w.getVID,w.getHDD,w.getPRINT,w.getMON,w.getSOFT,w.getPROC] #wMI=[w.getOS,w.getMB,w.getCPU] wMI.each do |wmi| thread << Thread.new {result << wmi} end.each(&:join) markers=w.marker f_name =file_name+markers.join(".") f_zip =f_name+file_exezip f_temp =f_name+file_exetemp #Теперь пробуем отправить то что мы собрали #Сначала проверяем доступен ли почтовый сервер (ПК может оказаться не подключен к интернету) #Если доступен - отправляем почту #Если не доступен, шифруем, записываем файл на рабочий стол и просим оператора отправить его нам puts "\nDEBUG: Все данные собраны.Подготовка данных к отправке" if debug puts "DEBUG: Проверяю доступность почтового сервера" if debug #Проверка доступности почты if avalable?(debug,mail_server,mail_port) puts "DEBUG: Почтовый сервер доступен." if debug #Если доступна, отправляем почту if mailing(debug,result,mail_server,mail_port,mail_user,mail_psw,mail_from,mail_to,mail_subj,mail_text,markers) puts "DEBUG: Письмо отправлено" if debug puts "\nДанные успешно отправлены!\n\nДля выхода из программы нажмите < Enter >" choice = gets.chomp.downcase if choice end else #Если во время отправки возникли ошибки, puts "\nОШИБКА! Сбой отправки почты!" puts "DEBUG: Пробую создать файл: "+f_zip if debug #Пробуем записать файл на рабочий стол if writeANDzip(debug,result,f_temp,f_zip,file_psw) puts "DEBUG: Файл записан в "+f_zip if debug puts "В папке с программой создан файл: "+f_zip+"\nПожалуйста, самостоятельно отправьте его на "+mail_to+"\n\nДля выхода из программы нажмите < Enter >" choice = gets.chomp.downcase if choice end #Если не можем записать - выходим else puts "DEBUG: Не могу создать файл отчета!"+f_zip if debug puts "Ошибка создания файла отчета!\nПожалуйста обратитесь к системному администратору "+mail_to+"\n\nДля выхода из программы нажмите < Enter >" choice = gets.chomp.downcase if choice end end #if writeFile(result,file_path,file_psw) end #if mailing #Если почта не доступна else puts "\nОШИБКА! Почтовый сервер не доступен!" #Пробуем создать файл puts "DEBUG: Пробую создать файл: "+f_zip if debug #Если можно создавать файлы, записываем результат в темп if writeANDzip(debug,result,f_temp,f_zip,file_psw) puts "DEBUG: Файл записан в "+f_zip if debug puts "\nВ папке с программой создан файл: "+f_zip+"\nПожалуйста, самостоятельно отправьте его на "+mail_to+"\n\nДля выхода из программы нажмите < Enter >" choice = gets.chomp.downcase if choice end else puts "DEBUG: Не могу создать файл отчета! "+f_zip if debug puts "Ошибка создания файла отчета!\nПожалуйста обратитесь к системному администратору "+mail_to+"\n\nДля выхода из программы нажмите < Enter >" choice = gets.chomp.downcase if choice end end #if writeFile(result,file_path) end #if avalable?(mail_server,mail_port) end puts "\nНажмите < Enter > для начала сбора информации о компьютере.\nВсе данные будут отправлены на почту администратору.\nЧтобы выйти нажмите < N > а затем < Enter >" choice = gets.chomp.downcase if choice != 'n' debug=false debug=true if choice == 'd' #Если написать D и нажать энтер, запустится режим отладки file_name ="Config" file_exetemp=".cfg" file_exezip =".7z" file_psw ="123" mail_server ='mayl.ru' mail_port =25 mail_user ='mailfrom' mail_psw ='mail_password' mail_from ='mailfrom@mayl.ru' mail_to ='admin@mayl.ru' mail_subj ="ИНВЕНТАРИЗАЦИЯ: " mail_text ="Здравствуйте!\n\n\n" beginning(debug,file_name,file_exetemp,file_exezip,file_psw,mail_server,mail_port,mail_user,mail_psw,mail_from,mail_to,mail_subj,mail_text) end
ссылка на оригинал статьи http://habrahabr.ru/post/267299/