Skip to content

Instantly share code, notes, and snippets.

@native-api
Created January 5, 2017 09:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save native-api/77b7399955195a8ef489e2d3120e5543 to your computer and use it in GitHub Desktop.
Save native-api/77b7399955195a8ef489e2d3120e5543 to your computer and use it in GitHub Desktop.
utility functions to work with Windows Installer registry database
# coding: utf-8
"""
Some utility functions to work with Windows Installer database in registry.
(Worked it out while rebuilding deleted %windir%\Installer)
Terminology used in fn names:
* patchid,productid - corresponding GUIDs as they are used in key names, e.g. CC458296FE7970347B78C876789B0194
* msx/fname - original package file name as it is in HKCR\Installer\SourceList:PackageName
* local - full path to cached file in %windir%\installer
"""
import os,os.path
import shutil
import re
import winsys.registry as reg
ksources=reg.registry(r"HKEY_CLASSES_ROOT\Installer\Patches")
klocals=reg.registry(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Patches")
kproducts=reg.registry(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products")
kallsources=reg.registry(r'HKEY_CLASSES_ROOT\Installer')
def patchid2productid(patchid):
"""product a patch belongs to"""
for key,keys,values in kproducts.walk():
if key.name!='Patches': continue
if patchid in list(k.name for k in keys): return key.parent().name
return None
def patchid_is_orphaned(id_):
"""a patch is orphaned if it doesn't belong to any installed product"""
return patchid2productid(id_) is None
def patchid2local(id_):
return klocals[id_].LocalPackage
def msx2patchid(msx):
"""find patches that match msx, case-insensitively.
If only one is found, it doesn't care if it's orphaned (for the task at hand, it was easier that way)"""
msx=msx.lower()
result=[]
for key,keys,values in ksources.walk():
if os.path.basename(key.moniker)=='SourceList' and hasattr(key,'PackageName') and key.PackageName.lower()==msx:
id_ = key.parent().name
result.append(id_)
if not result: raise KeyError(msx)
if len(result)==1: return result
no_result=[id_ for id_ in result if not patchid_is_orphaned(id_)]
if len(no_result)>1:
productids=set(patchid2productid(id_) for id_ in no_result)
if len(productids)>1:
raise ValueError("`%s' matches multiple non-orphaned patches %r for different products %r"%(msx,no_result,productids))
return no_result
def productid2local(id_):
return kproducts[id_]['InstallProperties'].LocalPackage
def local2patchid(local):
for key in klocals:
if key.LocalPackage==local: return key.name
raise KeyError(local)
def missingpatches(productid):
"""patches for a product with missing locals"""
allpatches=[key.name for key in kproducts+productid+'Patches']
result=[patchid for patchid in allpatches if not os.path.exists(patchid2local(patchid))]
return result
def patchid2fname(id_):
return (kallsources+'Patches'+id_+'SourceList').PackageName
def fname2kburl(fname):
"""MS KB URL that an fname refers to"""
m=re.search('-kb(\d{6,})\.ms[a-z]$',fname,re.I)
if not m: raise ValueError(fname)
return 'http://support.microsoft.com/en-us/kb/%s'%(m.group(1),)
def searchbyinstdir(dirs):
"""ids by original installation source path component(s)"""
if isinstance(dirs,basestring):dirs=(dirs,)
result=[]
for key,keys,values in kallsources.walk():
if key.name!='Net':continue
if not any(n for n,v in values if all(dir_ in v.split(os.path.sep) for dir_ in dirs)):continue
fname=key.parent().PackageName
id_=key.parent().parent().name
type_=key.parent().parent().parent().name
result.append((type_,id_,fname))
return result
def sample1():
productid2local('00002109810091400000000000F01FEC')
patchid2fname('624B02B22C0AE503691A62B10223C5FE')
local2patchid(r'D:\WINDOWS\Installer\21ecedb7.msp')
os.path.exists(r'D:\WINDOWS\Installer\21ecedb7.msp')
patchid_is_orphaned('9C0483E293578493A8D1B33F21DBEA92')
patchid2productid('63A0A088B442A7C3D8B6656E49EC8738')
missingpatches('0DC1503A46F231838AD88BCDDC8E8F7C')
def sample2():
"""copy all .msp's from unpacked .NET 3.5 distribution to locals."""
""".NET .msi's don't have files inside them so they can be copied as-is,
no need to prepare a stripped one with msiexec /a"""
#get_ipython().magic(u'cd C:\\Documents and Settings\\Administrator\\My Documents\\Desktop\\dotnetfx35langpack_x64ru\\netfx20lp')
for type_,id_,fname in searchbyinstdir(("dotnetfx35","x64")):
if type_=='Patches': local=patchid2local(id_)
elif type_=='Products': local=productid2local(id_)
else: raise KeyError(type_)
print fname,local
shutil.copyfile(fname,local)
def sample3():
"""open KB web pages for all the locally missing patches for a product"""
import win32api
for url in [fname2kburl(patchid2fname(id_)) for id_ in missingpatches('26DDC2EC4210AC63483DF9D4FCC5B59D')]:
win32api.ShellExecute(None,None,url,None,None,0)
def sample4():
"""copy .msp's from updates unpacked to current dir to locals;
if a local is already present, call `ls' to compare them"""
#get_ipython().magic(u'cd C:\\Documents and Settings\\Administrator\\My Documents\\Desktop\\U')
import subprocess
for fname in (fname for fname in os.walk('.').next()[2] if re.match(r'.*\.msp$',fname)):
ids=msx2patchid(fname)
locals_=[patchid2local(id_) for id_ in ids]
print fname,locals_
for local in locals_:
exists=os.path.exists(local)
print exists
if exists: subprocess.call(('ls','-l',fname,local))
if not exists: shutil.copyfile(fname,local)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment