├── README.md └── cuw.py /README.md: -------------------------------------------------------------------------------- 1 | # cuw 2 | CUW or (C)heck for (U)pdates for (W)indows 3 | 4 | CUW is a local windows update database maintainer/ updater tool which also solves patch superseding logic for a given machine in offline without dependency on WSUS or WUA. 5 | 6 | Background about CUW 7 | ------------------------- 8 | 9 | Major inspiration for creation of this tool was to check out the vulnerabilities (in case of a pentest, to find applicable exploits for post exploitation phase) in the existing system and to determine the patch life cycle maturity ( i.e missing patches, same logic as pentest but different view :P ) in case of a compliance audit; in offline mode. 10 | 11 | Since determining if all required patches have been applied in the system is a trival task as there is no online one-stop efficient searchable database (post April 2017 because of discontinuation of periodic release of MS Bulletin Excel file) where patches, its's subsequent superseded patches, patch description etc for a specific product can be searched out and compared with the list of patches installed in the system (if any); and further more to add to the problem is solving manually the logic of superseded patches. Hence the motivation to build this tool. 12 | 13 | Working of/with CUW 14 | ------------------- 15 | 16 | CUW maintains a local sqlite database consisting of Windows patches and it's details.During the update process the tool downloads the official WSUS offline cab file from the link "http://go.microsoft.com/fwlink/?LinkID=74689" and parses the required information and feeds onto the local database. You can consider it as a localised repository of https://www.catalog.update.microsoft.com/Home.aspx but with more filter search capability as the entire data is in your control. 17 | 18 | CUW exports a csv format file "MSPatches.csv" containing only important columns from the database table as soon the database is updated. 19 | Note : Raw database is in sqlite format. If you want to view the table in the database, you can use any sqlite viewer tool. 20 | 21 | Now the user can use this csv to filter out the the updates containing keyword pertaining to the target operating system or the product installed using his/her super excel powers. The problem doesnt end there as the updates which have been filtered may have superseded updates. So to solve this , the user copies the updateids from the csv file after filtering out the keywords from the file into a text file. 22 | Now this text file is fed into the cuw tool using the "scan" option. CUW takes in the updateids, check for superseded patches within the database and after applying internal logic, gives out the final applicable patch ids which is supposed to be present in the system. Now this output can be used to compare with the existing patches (results obained from wmic qfe list command, refer "Some of the Key Information required for the user to filter the required updateids before using the tool" section below) and determine the missing patches / vulnerabilities in the target system. 23 | 24 | Q) Cant this entire thing of user selecting the required patches and feeding to CUW be automated ? 25 | 26 | It can be, provided Microsoft follows a standard and accurate convention of naming its products and the products appicable to each of the updates in the wsusscn2.cab file (The official offline patch database released by microsoft every month to be used with WSUS and WUA tools), since CUW totally depends on the textual search. WSUS , WUA uses a set of detection mechanisms mentioned in the same cab file to determine all the products installed in a workstation and accordingly picks the required updates and patches the system and that is the reason why your regular system takes so much time to update and this entire process is active wheras CUW tries to attempt this very same process passively taking out the installation of patches. 27 | 28 | Challenge Explanation :- 29 | 30 | To understand the challenge in entirety, the entire cab file structure and its working needs to be explained . Btw there is no official detailed manual explaining the structure of wssuscn2.cab file which I could find apart from this link https://support.microsoft.com/en-us/help/927745/detailed-information-for-developers-who-use-the-windows-update-offline , which at least I couldnt find of any help since I was not using WUA API and I had to understand the entire cab file structure and the internal xmls and its tags to write a tool to parse its contents. I will save the explanation on how I understood the structure of wssuscn2.cab file for another post in my blog at https://rams3sh.blogspot.com/. 31 | 32 | Now to keep it simple and to understand the challenge, lets understand that wssuscn2.cab file stores lot of informations such as Windows Products, Windows Updates, Different ways to check for an existence of a product (Detectoids), types of updates, EULA files, download links for various updates etc in different xml files (not one). Each of these information is mapped to unique ID called updateid (even though many of them are not updates, Microsoft chooses to call it that way). 33 | 34 | So if one wants to look for patches applicable for MS Office 2016, one needs to first search for updateid of the product , search across all the updates where prerequistes column of that update has MS Office 2016's updateid part of it. This can be automated provided the input file,say which consists of list of installed applications' name (received as output from the commmand "wmic product get name") follows the same naming convention as the one in the wsusscn2.cab. But it isnt that way. Look at the example 35 | 36 | Example:- 37 | 38 | Installed Application: Microsoft Office Professional Plus 2016 39 | 40 | 41 | WSUSSCN2.CAB Equivalent Product Name: Office 2016 42 | 43 | Simple search for existence for 2016 and Office is not enough as there are other examples such as MSSQL 2008 R2 Server as product and installed application is just MSSQL 2008 Client. Hence word matching logic does not work. 44 | 45 | Tried for fuzzy string match logic using fuzzywuzzy library (https://github.com/seatgeek/fuzzywuzzy) but still identifying and fixing a threshold ratio for string match was difficult and was not consistent throughout.Hence left the challenge of selecting the updates to the user. 46 | 47 | If this challenge is overcome, the process of update selection by the user can be automated. 48 | 49 | 50 | 51 | Q) Is there any possibility of inaccuracies on the output of results ? 52 | 53 | Yes, if the update ids of the applicable updates (after filtration process in csv) provided by the user to the tool is inaccurate as the tool is merely solving the superseded update logic. 54 | 55 | 56 | Some of the Key Information required for the user to filter the required updateids before using the tool 57 | -------------------------------------------------------------------------------------------------------- 58 | 59 | 1. System Information 60 | To get the operating system details, architecture and other informations. 61 | 62 | Command : systeminfo.exe 63 | 64 | 2. Build Version of the Operating System 65 | This helps in filtering out the updates further from a set of updates applicable for an operating system. 66 | 67 | Example :- 68 | Both of the following updates apply for Windows 10 but each one for different Version of Windows 10 69 | 70 | i. 2018-06 Cumulative Update for Windows 10 Version 1709 for x64-based Systems 71 | 72 | ii. 2018-06 Cumulative Update for Windows 10 Version 1803 for x64-based Systems 73 | 74 | So differentiating factor is the Version Number here. 75 | 76 | Command :reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v ReleaseId 77 | 78 | 3. Get List of Installed Programs 79 | This will help us in finding the list of programs installed in the machine. The program names can be used to filter out the applicable updates from the csv file. 80 | 81 | Command : wmic product get name,version,vendor,installdate 82 | 83 | 4. Get list of updates installed in the system 84 | This will help us to compare the output from the tool and the installed updates to decide on which updates are missing in the system (if any). 85 | 86 | Command : wmic qfe list 87 | 88 | Note: None of the commands or outputs given by the commands mentioned above are used by the tool. These details are just for the user to finally filter out the required updates from the MSPatch csv file. 89 | 90 | 91 | How To use 92 | ----------- 93 | 94 | Usage:- 95 | 96 | 97 | cuw.exe scan - Checks for Update ids in the input file and gives the final list of applicable updates with details 98 | cuw.exe scan output - Same as the previous option with the output (with extra details) being exported as csv 99 | cuw.exe update - Updates the local patch database (Requires Internet Connection and some patience !! :P) 100 | cuw.exe exportdb " - Exports the local patch database as csv file 101 | cuw.exe help - Displays this help 102 | 103 | 104 | External Dependency 105 | ------------------- 106 | cuw currently works only on windows due to hard coded windows based commands in the script. 107 | cuw depends on 7zip binary. 108 | The required binaries has been packed along with the release zip file. The author claims no ownership over the same. 109 | 110 | Note 111 | ---- 112 | If you are using the source cuw.py, then you would have to dowload 7z binaries (7z.exe and 7z.dll) separately from the official site and place it in class path and update the entire database. The initial database updation on my machine which is i7 7th gen with 8GB Ram and Win10 OS took 3 hours of time. 113 | 114 | The release zip has been packed with latest updated database and csv file as on 17-6-2018 to avoid long update process in the first subsequent update event. 115 | 116 | 117 | Future Plans 118 | ------------- 119 | 120 | To add exploitdb informations to the database so that a mapping exploit (if any) also could be given out along with the report. More or less like windows_privesc_check (https://github.com/pentestmonkey/windows-privesc-check) just that I wouldnt want to limit it to priv escalation related exploit alone. 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /cuw.py: -------------------------------------------------------------------------------- 1 | 2 | from scrapy import Selector 3 | from tqdm import tqdm 4 | import requests 5 | import math 6 | import re 7 | import sqlite3 8 | import subprocess 9 | import sys,os 10 | import tempfile 11 | import xml.etree.ElementTree as ET 12 | import time 13 | import signal 14 | signal.signal(signal.SIGINT, lambda x,y: sys.exit(0)) 15 | #Global Variables 16 | 17 | conn = sqlite3.connect('MSPatch.db') 18 | c=conn.cursor() 19 | cabname=[] 20 | extractedcabs=[] 21 | rangestart=[] 22 | updatetempfiles_path= tempfile.gettempdir()+"\\winupdate" 23 | patches=[] 24 | programs=[] 25 | final_updates_list=[] 26 | solved_revisionids=[] 27 | 28 | #Functions 29 | 30 | def concatenate_list_data(list): 31 | result= '' 32 | for element in list: 33 | result += str(element) 34 | return result 35 | 36 | def parse_wssuscn2cab(buff): 37 | global cabname,rangestart,extractedcabs 38 | scrape=Selector(text=buff) 39 | updateid=scrape.xpath("//update/@updateid").extract_first() 40 | 41 | if c.execute("select updateid from MSPatchTable where updateid=\""+updateid+"\"").fetchall().__len__()>0: 42 | return 43 | creationdate=scrape.xpath("//update/@creationdate").extract_first() 44 | revisionid=scrape.xpath("//update/@revisionid").extract_first() 45 | cabtoextract="" 46 | for i in range(cabname.__len__()-1): 47 | 48 | if int(revisionid)>int(rangestart[i]): 49 | pass 50 | else: 51 | cabtoextract=cabname[i] 52 | break 53 | if cabtoextract=="": 54 | cabtoextract=cabname[cabname.__len__()-1] 55 | 56 | if cabtoextract in extractedcabs: 57 | pass 58 | else: 59 | for f in os.listdir(updatetempfiles_path): 60 | if os.path.isdir(updatetempfiles_path+"\\"+f): 61 | os.system("rmdir "+updatetempfiles_path+"\\"+f+" /S /Q") 62 | mute=subprocess.check_output(".\\7z.exe x "+updatetempfiles_path+"\\wsusscn2.cab "+cabtoextract+" -o"+updatetempfiles_path+" -y") 63 | mute=subprocess.check_output(".\\7z.exe x "+updatetempfiles_path+"\\"+cabtoextract+" -o"+updatetempfiles_path+" -y") 64 | os.system("del "+updatetempfiles_path+"\\"+cabtoextract) 65 | extractedcabs.append(cabtoextract) 66 | 67 | prerequisite=list_to_string(scrape.xpath("//prerequisites/*/@id").extract()) 68 | supersededby=list_to_string(scrape.xpath("//supersededby/*/@id").extract()) 69 | if scrape.xpath("//category[@type = 'Company']/@id").extract_first(): 70 | Category_Company = list_to_string(scrape.xpath("//category[@type = 'Company']/@id").extract_first()) 71 | Category_Product= list_to_string(scrape.xpath("//category[@type = 'Product']/@id").extract()) 72 | Category_ProductFamily=list_to_string(scrape.xpath("//category[@type = 'ProductFamily']/@id").extract()) 73 | Category_UpdateClassification=list_to_string(scrape.xpath("//category[@type = 'UpdateClassification']/@id").extract()) 74 | 75 | 76 | else: 77 | Category_Company=Category_Product=Category_ProductFamily=Category_UpdateClassification=prerequisites="" 78 | 79 | try: 80 | scrape=Selector(text=(re.sub(r'[^\x00-\x7f]','',concatenate_list_data(open(updatetempfiles_path+"\\c\\"+revisionid,"r").readlines()).replace("\n","").replace("\r","").replace("\t","").strip(" ")))) 81 | supersedes=list_to_string(scrape.xpath("//supersededupdates/updateidentity/@updateid").extract()) 82 | except Exception as e: 83 | supersedes="" 84 | 85 | try: 86 | scrape=Selector(text=(re.sub(r'[^\x00-\x7f]','',concatenate_list_data(open(updatetempfiles_path+"\\l\\en\\"+revisionid,"r").readlines()).replace("\n","").replace("\r","").replace("\t","").strip(" ")))) 87 | title=re.sub(r'[^\x00-\x7f]','',str(scrape.xpath("//title/text()").extract_first())) 88 | description=re.sub(r'[^\x00-\x7f]','',str(scrape.xpath("//description/text()").extract_first())) 89 | except Exception as e: 90 | title="" 91 | description="" 92 | try: 93 | scrape=Selector(text=(re.sub(r'[^\x00-\x7f]','',concatenate_list_data(open(updatetempfiles_path+"\\s\\"+revisionid,"r").readlines()).replace("\n","").replace("\r","").replace("\t","").strip(" ")))) 94 | msrcseverity=str(scrape.xpath("//properties/@msrcseverity").extract_first()) 95 | msrcnumber=str(scrape.xpath("//securitybulletinid/text()").extract_first()) 96 | kb="KB"+str(scrape.xpath("//properties/kbarticleid/text()").extract_first()) 97 | languages=list_to_string(scrape.xpath("//properties/language/text()").extract()) 98 | 99 | except Exception as e: 100 | msrcnumber="" 101 | msrcseverity="" 102 | kb="" 103 | languages="" 104 | 105 | try: 106 | scrape=Selector(text=(re.sub(r'[^\x00-\x7f]','',concatenate_list_data(open(updatetempfiles_path+"\\x\\"+revisionid,"r").readlines()).replace("\n","").replace("\r","").replace("\t","").strip(" ")))) 107 | category=str(scrape.xpath("//categoryinformation/@categorytype").get()) 108 | except Exception as e: 109 | category="" 110 | 111 | try: 112 | params=(updateid,revisionid,creationdate,Category_Company,Category_Product,Category_ProductFamily,Category_UpdateClassification,prerequisite,title,description,msrcseverity,msrcnumber,kb,languages,category,supersededby,supersedes) 113 | c.execute('Insert into MSPatchTable values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',params) 114 | except Exception as e: 115 | pass 116 | 117 | def download_cab(): 118 | print "\n\nThis is going to take some time. Please be patient .... \n\nGrabbing a cup of coffee might be a good idea right now !! :P \n\nStep 1 out of 4 :- Downloading Official WSUS Update Cab File\n\n" 119 | url="http://go.microsoft.com/fwlink/?linkid=74689" 120 | r=requests.get(url, stream=True) 121 | total_size = int(r.headers.get('content-length', 0)); 122 | block_size = 1024 123 | wrote = 0 124 | if os.path.isdir(updatetempfiles_path): 125 | pass 126 | else: 127 | mute=subprocess.check_output("mkdir "+updatetempfiles_path) 128 | 129 | with open(updatetempfiles_path+'\\wsusscn2.cab', 'wb') as f: 130 | for data in tqdm(r.iter_content(block_size), total=math.ceil(total_size//block_size) , unit='KB', unit_scale=True): 131 | wrote = wrote + len(data) 132 | f.write(data) 133 | if total_size != 0 and wrote != total_size: 134 | print("ERROR, something went wrong") 135 | sys.exit() 136 | 137 | def list_to_string(buff): 138 | return str(buff).replace("u'","").replace("[","").replace("]","").replace("'","").replace("\r","").replace("\n","") 139 | 140 | def solve_supersede_updateids(applicable_updates): 141 | global final_updates_list 142 | superseded_updates=[] 143 | applicable_updates_query=str(applicable_updates).replace("u'","'").replace("[","(").replace("]",")") 144 | record=c.execute("select supersededby from MSPatchTable where updateid in "+applicable_updates_query+";") 145 | for i,j in zip(record.fetchall(),range(0,applicable_updates.__len__())): 146 | if i[0].__len__()==0: 147 | final_updates_list.append(applicable_updates[j]) 148 | record=c.execute("select revisionid from MSPatchTable where updateid ='"+applicable_updates[j]+"';") 149 | solved_revisionids.append(record.fetchall()[0][0]) 150 | else: 151 | solve_supersede_revisionids(i[0]) 152 | 153 | def solve_supersede_revisionids(revisionids): 154 | for i in revisionids.split(","): 155 | if i.strip(" ") in solved_revisionids: 156 | continue 157 | record=c.execute("select updateid,supersededby from MSPatchTable where revisionid='"+i.strip(" ")+"' ;") 158 | for j in record.fetchall(): 159 | if j[1].__len__()==0: 160 | if j[0] in final_updates_list: 161 | continue 162 | else: 163 | final_updates_list.append(j[0]) 164 | solved_revisionids.append(i.strip(" ")) 165 | else: 166 | solve_supersede_revisionids(j[1]) 167 | solved_revisionids.append(i.strip(" ")) 168 | 169 | def update(): 170 | global cabname,rangestart,i 171 | print_ascii_art() 172 | record=c.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='MSPatchTable';") 173 | if record.fetchone()[0] == 0: 174 | c.execute("""CREATE TABLE `MSPatchTable` ( 175 | `updateid` TEXT NOT NULL UNIQUE, 176 | `revisionid` TEXT UNIQUE, 177 | `creationdate` TEXT NOT NULL, 178 | `company` TEXT, 179 | `product` TEXT, 180 | `productfamily` TEXT, 181 | `updateclassification` TEXT, 182 | `prerequisite` TEXT, 183 | `title` TEXT, 184 | `description` TEXT, 185 | `msrcseverity` TEXT, 186 | `msrcnumber` TEXT, 187 | `kb` TEXT, 188 | `languages` TEXT, 189 | `category` TEXT, 190 | `supersededby` TEXT, 191 | `supersedes` TEXT, 192 | PRIMARY KEY(`updateid`) 193 | );""") 194 | 195 | 196 | 197 | download_cab() 198 | print "\n\nStep 2 out of 4 :- Reading data from cab file and feeding the database\n\n" 199 | mute=subprocess.check_output(".\\7z.exe x "+updatetempfiles_path+"\\wsusscn2.cab index.xml -o"+updatetempfiles_path+" -y") 200 | mute=subprocess.check_output(".\\7z.exe x "+updatetempfiles_path+"\\wsusscn2.cab package.cab -o"+updatetempfiles_path+" -y") 201 | mute=subprocess.check_output(".\\7z.exe x "+updatetempfiles_path+"\\package.cab package.xml -o"+updatetempfiles_path+" -y") 202 | os.system("del \""+updatetempfiles_path+"\\package.cab\"") 203 | indexfile=open(updatetempfiles_path+"\\index.xml","r") 204 | indexf=indexfile.readlines()[0] 205 | scrape=Selector(text=indexf) 206 | cabname=scrape.xpath("//cab/@name").extract() 207 | rangestart=scrape.xpath("//cab/@rangestart").extract() 208 | indexfile.close() 209 | 210 | context = ET.iterparse(updatetempfiles_path+'\\package.xml', events=('end',)) 211 | 212 | #Counting the number of tags to process for our status bar for Step 2 213 | i=0 214 | for event,elem in context: 215 | i+=1 216 | 217 | context = ET.iterparse(updatetempfiles_path+'\\package.xml', events=('end',)) 218 | for event, elem in tqdm(context,total=i): 219 | if elem.tag == '{http://schemas.microsoft.com/msus/2004/02/OfflineSync}Update': 220 | parse_wssuscn2cab(ET.tostring(elem).replace("ns0:","")) 221 | print "\n\nStep 3 out of 4 :- Clearing temp cache \n\n" 222 | os.system("rmdir "+updatetempfiles_path+" /s /Q") 223 | print "Update Complete .....!! :) " 224 | conn.commit() 225 | print "\n\nStep 4 out of 4 :- Exporting the database as csv \n\n" 226 | exportdb("MSPatches.csv") 227 | 228 | def scan(input_file): 229 | print_ascii_art() 230 | files=open(input_file,"r") 231 | a=str(files.readlines()).replace("\\n","") 232 | print str(a.replace("]","").replace("[","").replace("'","").replace(" ","").split(",").__len__())+" selected updates" 233 | solve_supersede_updateids(a.replace("]","").replace("[","").replace("'","").replace(" ","").split(",")) 234 | print str(final_updates_list.__len__())+" applicable updates after solving for supersedes !!" 235 | 236 | def exportdb(output_file): 237 | print_ascii_art() 238 | fileout=open(output_file,"w") 239 | fileout.write('"Update ID","Creation Date","Update Classification","Title","Description","KB ID","MSRC Severity","MSRC Number"\n') 240 | record=c.execute("select updateid,creationdate,updateclassification,title,description,kb,msrcseverity,msrcnumber from MSPatchTable where updateclassification not in ('','None') order by creationdate desc;") 241 | for i in tqdm(record.fetchall(),total=c.execute("select count(updateid) from MSPatchTable where updateclassification not in ('','None');").fetchone()[0]): 242 | updateclassification=c.execute("select title from MSPatchTable where updateid='"+i[2]+"';").fetchone()[0] 243 | fileout.write("\""+i[0]+"\",\""+i[1]+"\",\""+updateclassification+"\",\""+i[3]+"\",\""+i[4]+"\",\""+i[5]+"\",\""+i[6]+"\",\""+i[7]+"\""+"\n") 244 | fileout.close() 245 | print "\n \n DB Exported as CSV sucessfully !!" 246 | 247 | def print_ascii_art(): 248 | 249 | print r""" 250 | 251 | db db 252 | d88 88 253 | 888 888 254 | d88 888b The HOLY *** 255 | 888 d88P 256 | Y888b /``````\8888 257 | ,----Y888 Y88P`````\ 258 | | ,'`\_/``\ |,, | 259 | \,,,,-| | o | o / | ```' 260 | | ''' ''' | 261 | / \ 262 | | \ 263 | | ,,,,----'''```| 264 | |`` @ @ | 265 | \,, ___ ,,/ 266 | \__| |__/ 267 | | | | 268 | \_|_/ 269 | 270 | 271 | CUW Tool - (C)heck for (U)pdates for (W)indows 272 | By R4m 273 | """ 274 | 275 | def help(): 276 | print_ascii_art() 277 | 278 | print r"""Usage:- 279 | 280 | cuw.exe scan - Checks for Update ids in the input file and gives the final list of applicable updates with details 281 | cuw.exe scan output - Same as the previous option with the output (with extra details) being exported as csv 282 | cuw.exe update - Updates the local patch database (Requires Internet Connection and some patience !! :P) 283 | cuw.exe exportdb " - Exports the local patch database as csv file 284 | cuw.exe help - Displays this help""" 285 | 286 | def export(output_file): 287 | fileout=open(output_file,"w") 288 | finalupdates_query=str(final_updates_list).replace("u'","'").replace("[","(").replace("]",")") 289 | fileout.write('"Update ID","Creation Date","Update Classification","Title","Description","KB ID","MSRC Severity","MSRC Number"\n') 290 | record=c.execute("select updateid,creationdate,updateclassification,title,description,kb,msrcseverity,msrcnumber from MSPatchTable where updateid in "+finalupdates_query+" order by creationdate desc;") 291 | for i in tqdm(record.fetchall(),total=final_updates_list.__len__()): 292 | updateclassification=c.execute("select title from MSPatchTable where updateid='"+i[2]+"';").fetchone()[0] 293 | fileout.write("\""+i[0]+"\",\""+i[1]+"\",\""+updateclassification+"\",\""+i[3]+"\",\""+i[4]+"\",\""+i[5]+"\",\""+i[6]+"\",\""+i[7]+"\""+"\n") 294 | fileout.close() 295 | print "\n\n Report exported Successfully !!" 296 | 297 | if os.path.isfile("7z.exe") and os.path.isfile("7z.dll"): 298 | pass 299 | else: 300 | print_ascii_art() 301 | print "7z.exe or/and 7z.dll file not found in the current directory.\n\nCUW cannot function without these dependencies.\n\nPlease download the 7z binaries for your architecture (x86 / x64) and extract the mentioned files in the current directory and run the tool. Link :https://www.7-zip.org/download.html" 302 | sys.exit(1) 303 | if sys.argv.__len__()==1: 304 | help() 305 | elif sys.argv.__len__()==2: 306 | if sys.argv[1] == "update": 307 | update() 308 | else: 309 | help() 310 | elif sys.argv.__len__() ==3: 311 | if sys.argv[1]== "scan": 312 | if os.path.isfile(sys.argv[2]): 313 | scan(sys.argv[2]) 314 | records=c.execute("select title,kb from MSPatchTable where updateid in "+str(final_updates_list).replace("u'","'").replace("[","(").replace("]",")")+";") 315 | for i in records: 316 | print i[0]+" : "+i[1] 317 | else: 318 | print "\n\n"+sys.argv[2]+" file does not exist !!" 319 | help() 320 | elif sys.argv[1] == "exportdb": 321 | exportdb(sys.argv[2]) 322 | else: 323 | help() 324 | elif sys.argv.__len__()==5: 325 | if sys.argv[1]== "scan" and os.path.isfile(sys.argv[2]) and sys.argv[3] == "output": 326 | scan(sys.argv[2]) 327 | export(sys.argv[4]) 328 | else: 329 | help() 330 | else: 331 | help() 332 | 333 | --------------------------------------------------------------------------------