# machine.py # ========== import wmi import win32net import win32com.client from BeautifulSoup import BeautifulSoup from string import replace, digits, ascii_letters import urllib, urllib2 DELL_SVCTAG_URI = 'http://support.dell.com/support/topics/global.aspx/support/my_systems_info/en/details?c=us' DELL_SVCTAG_PARTS_URI = DELL_SVCTAG_URI + '&l=en&s=gen&~tab=2&~wsf=tabs' DELL_UA = [('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')] class InvalidServiceTagException(Exception): pass def machines_in_default_domain(): domain_controller = win32net.NetGetDCName(None, None) domain_name = win32net.NetUserModalsGet(domain_controller, 2)['domain_name'] return machines_in_domain(domain_name) def machines_in_domain(domain_name): adsi = win32com.client.Dispatch("ADsNameSpaces") nt = adsi.GetObject('', 'WinNT:') result = nt.OpenDSObject('WinNT://%s' % domain_name, '', '', 0) result.Filter = ['computer'] for machine in result: yield machine.Name class Machine(object): def __init__(self, machine_name): self.machine = machine_name try: self.wmi_conn = wmi.WMI(self.machine) self.active = True except: self.active = False def __safe_encode(self, val): return ''.join([c for c in val if (c in digits or c in ascii_letters)]) @property def _enclosure(self): if not hasattr(self, '__enclosure_query'): self.__enclosure_query = self.wmi_conn.query('Select * from Win32_SystemEnclosure') return self.__enclosure_query @property def _system(self): if not hasattr(self, '__system_query'): self.__system_query = self.wmi_conn.query('Select * from Win32_ComputerSystem') return self.__system_query def query_results(self, query): return self.wmi_conn.query(query) @property def service_tags(self): tags = [self.__safe_encode(e.SerialNumber.strip()) for e in self._enclosure] if not tags: return None return tags @property def model(self): m = [self.__safe_encode(c.Model.strip()) for c in self._system] if not m: return None return m[0] @property def manufacturer(self): mfs = [self.__safe_encode(e.Manufacturer.strip()) for e in self._enclosure] if not mfs: return None return mfs[0] @property def name(self): return self.machine @property def is_dell(self): return 'dell' in self.manufacturer.lower() @property def has_valid_dell_service_tags(self): tag = self.service_tags[0] if len(tag) == 7: return True def __normalize_dell_charset(self, page_content): page_content = replace(page_content, "<sc'+'ript", '') page_content = replace(page_content, "</sc'+'ript", '') t = ''.join(map(chr, range(256))) d = ''.join(map(chr, range(128, 256))) page_content = page_content.translate(t, d) return page_content def __retrieve_tag_information(self): if not self.has_valid_dell_service_tags: raise InvalidServiceTagException if not hasattr(self, '_dell_connection'): self._dell_cookie_jar = urllib2.HTTPCookieProcessor() self._dell_connection = urllib2.build_opener(self._dell_cookie_jar) self._dell_connection.addheaders = DELL_UA urllib2.install_opener(self._dell_connection) params = urllib.urlencode({'ServiceTag': self.service_tags[0]}) f = self._dell_connection.open(DELL_SVCTAG_URI, params) page_content = f.read() # clean up the page soup = BeautifulSoup(self.__normalize_dell_charset(page_content)) # Determine if we're still under warranty self._service_contracts = [] for _contract in soup.find('table', {'class': 'contract_table'}).findAll('tr'): try: parts = _contract.findAll('td') if any([('contract_header' in m['class']) for m in parts]): continue self._service_contracts.append({ 'name': parts[0].first().string, 'provider': parts[1].string, 'start_date': parts[2].string, 'end_date': parts[3].string, 'days_remaining': int(parts[4].find(text=True)), }) except: pass # Determine original parts f = self._dell_connection.open(DELL_SVCTAG_PARTS_URI) page_content = self.__normalize_dell_charset(f.read()) soup = BeautifulSoup(page_content) self._parts = [] for _part in soup.find('a', {'name': 'grid_1'}).nextSibling: try: fields = _part.findAll('td') if any([('gridTitle' in m['class']) for m in fields]): continue self._parts.append({ 'quantity': int(fields[0].string), 'part_number': fields[1].string, 'description': fields[2].string, }) except: pass @property def service_contracts(self): if self.is_dell and not hasattr(self, '_service_contracts'): self.__retrieve_tag_information() return self._service_contracts @property def parts(self): if self.is_dell and not hasattr(self, '_parts'): self.__retrieve_tag_information() return self._parts @property def is_under_warranty(self): if self.is_dell: return any([(m['days_remaining'] > 0) for m in self.service_contracts]) return None # warranty_report.py import csv from machine import Machine, InvalidServiceTagException, machines_in_default_domain class MachineNotDell(Exception):pass class MachineNotActive(Exception):pass BASE_INDENT = 4 def validate_machine(m): """ We're only searching for Dell machines, so filter everything else out. """ if not m.active: raise MachineNotActive elif not m.is_dell: raise MachineNotDell else: return m def indent(spaces, text): return ' ' * BASE_INDENT + text def main(): output_writer = csv.writer(open('MachinesList.csv', 'w')) # write header output_writer.writerow([ 'Machine Name', 'Model', 'Service Tag(s)', 'Under Warranty?', 'Contract days remaining', ]) for hostname in machines_in_default_domain(): try: # Print Identification print 'Retrieving machine information for host: %s' % hostname m = validate_machine(Machine(hostname)) print indent(1, '%s (%s) discovered.' % (m.model, ', '.join(m.service_tags))) # Print Service Contracts print indent(1, 'Found %d service contracts:' % len(m.service_contracts)) for contract in m.service_contracts: print indent(2, '> %s (%d days remain)' % (contract['name'], contract['days_remaining'])) # Prettify (put newline after each record) print '' # Write Output to CSV output_writer.writerow([ hostname, m.model, ', '.join(m.service_tags), ['N','Y'][int(m.is_under_warranty)], max([c['days_remaining'] for c in m.service_contracts]) ]) # handle failures except MachineNotActive: print indent(1, 'No WMI response from %s. Continuing.\n' % hostname) continue except MachineNotDell: print indent(1, 'Machine was not manufactured by Dell. Continuing.\n') continue except InvalidServiceTagException: print indent(1, 'Machine provided an invalid service tag.') print indent(1, 'Unable to retrieve details. Continuing.\n') continue print 'Network scan complete.' if __name__ == '__main__': main()