Я:
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
Комментарии (12)
galk_in
21.09.2015 11:25+12Вы забыли про кат
meft
21.09.2015 11:39-9напишешь так в комментариях и всю карму сольют.
meft
21.09.2015 12:36+1Вы знаете, а я не против, сливайте.
Одно дело, когда ставят минус, тем самым выражая свое мнение к сообщению. Другое дело, когда сливают карму, как и в этот раз, выражая отношение к автору. При этом если про минус все понятно (написал что-то не правильно, сам разберешься), то с кармой веселее, объяснений то нет.
Lure_of_Chaos
21.09.2015 11:39Кошмарное изложение: длинное, скомканное и в основном код. Ссылка на Git репозиторий была бы ровно столько же информативна, как и сама статья. Где изложение, где сюжет «квеста»?
batyrmastyr
21.09.2015 12:121. «не плохо» и «неплохо» — разные вещи.
2. «не подключенных к общей сети» — не проблема. Не подключен к сети, включая интернет, — да, это уже похоже на проблему.
3. Пароль в исходниках лежит — кто мешает распаковать тем же 7zip, подправить и запаковать обратно?dimult
21.09.2015 12:19Пароль в исходниках лежит — кто мешает распаковать тем же 7zip, подправить и запаковать обратно?
Скажите, а зачем вы будете отправлять программу вместе с исходником?batyrmastyr
21.09.2015 16:01Руби по-умолчанию как бы скриптовый язык, так что, раз не сказано обратное, автор вероятнее всего будет запускать чудо .bat с «ruby GetWMIData.rb». Ну. наверное ещё установку интерпретатора автоматизирует.
dimult
22.09.2015 08:19нет, все «это» компилируется в обычный exe, зачем еще интерпритатор ставить, что вы )
batyrmastyr
22.09.2015 10:23За 10 минут поисков были найдены только компиляторы в .Net (пару лет как заброшен) и Java (его выхлоп за exe не считается) и ещё rubyscript2exe который не обновлялся 4 года и с новыми версиями наверняка не работающий. Был ещё какой-то компилятор, но он делает бинарники для Линукса.
Сами понимете, если генераторов .exe полштуки (подошёл бы и тупо упаковщик исходника + интерпретатора в один бинарник) неизбежен вывод о пересылке исходника.dimult
24.09.2015 09:07ок, не знаю как вы искали, но вот вам мануал как сделать из .rb .exe:
1. gem install ocra
2. ocra youre_script.rb
на выходе у вас будет .exe файл
Если есть интерес, могу скомпилировать для вас этот скрипт, скажите только на какую почту слать отчет.batyrmastyr
24.09.2015 10:03Искал простейшим способом: www.ruby-lang.org/ru/downloads + нечто вроде «ruby exe» в поисковиках.
Собственно, мой комментарий должен был навести на мысль что среди очевидностей про отправку почты и архивацию (первое совсем уж коробочное, даже новички сообразят что для этой задачи должно быть готовое решение) странным образом нет упаковки скрипта в exe, которым заведует отдельное расширение с ничего не говорящим названием.
ragequit
dxdiag вышел из моды уже?