Пишем программу сбора конфигурации о ПК и отправки отчета на почту


Переписка с владельцем 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/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *