1 | # -*- coding: utf-8 -*-
|
---|
2 | # $Id: fix_stale_refs.py 93115 2022-01-01 11:31:46Z vboxsync $
|
---|
3 |
|
---|
4 | """
|
---|
5 | This module must be used interactively!
|
---|
6 | Use with caution as it will delete some values from the regisry!
|
---|
7 |
|
---|
8 | It tries to locate client references to products that no longer exist.
|
---|
9 | """
|
---|
10 |
|
---|
11 | __copyright__ = \
|
---|
12 | """
|
---|
13 | Copyright (C) 2012-2022 Oracle Corporation
|
---|
14 |
|
---|
15 | This file is part of VirtualBox Open Source Edition (OSE), as
|
---|
16 | available from http://www.virtualbox.org. This file is free software;
|
---|
17 | you can redistribute it and/or modify it under the terms of the GNU
|
---|
18 | General Public License (GPL) as published by the Free Software
|
---|
19 | Foundation, in version 2 as it comes in the "COPYING" file of the
|
---|
20 | VirtualBox OSE distribution. VirtualBox OSE is distributed in the
|
---|
21 | hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
|
---|
22 |
|
---|
23 | The contents of this file may alternatively be used under the terms
|
---|
24 | of the Common Development and Distribution License Version 1.0
|
---|
25 | (CDDL) only, as it comes in the "COPYING.CDDL" file of the
|
---|
26 | VirtualBox OSE distribution, in which case the provisions of the
|
---|
27 | CDDL are applicable instead of those of the GPL.
|
---|
28 |
|
---|
29 | You may elect to license modified versions of this file under the
|
---|
30 | terms and conditions of either the GPL or the CDDL or both.
|
---|
31 | """
|
---|
32 | __version__ = "$Revision: 93115 $"
|
---|
33 |
|
---|
34 |
|
---|
35 | from _winreg import HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS
|
---|
36 | from _winreg import OpenKey, CloseKey, EnumKey, QueryInfoKey, EnumValue, DeleteValue, QueryValueEx
|
---|
37 | from distutils.util import strtobool
|
---|
38 |
|
---|
39 | def reverse_bytes(hex_string):
|
---|
40 | """
|
---|
41 | This function reverses the order of bytes in the provided string.
|
---|
42 | Each byte is represented by two characters which are reversed as well.
|
---|
43 | """
|
---|
44 | #print 'reverse_bytes(' + hex_string + ')'
|
---|
45 | chars = len(hex_string)
|
---|
46 | if chars > 2:
|
---|
47 | return reverse_bytes(hex_string[chars/2:]) + reverse_bytes(hex_string[:chars/2])
|
---|
48 | else:
|
---|
49 | return hex_string[1] + hex_string[0]
|
---|
50 |
|
---|
51 | def transpose_guid(guid):
|
---|
52 | """
|
---|
53 | Windows Installer uses different way to present GUID string. This function converts GUID
|
---|
54 | from installer's presentation to more conventional form.
|
---|
55 | """
|
---|
56 | return '{' + reverse_bytes(guid[0:8]) + '-' + reverse_bytes(guid[8:12]) + \
|
---|
57 | '-' + reverse_bytes(guid[12:16]) + \
|
---|
58 | '-' + reverse_bytes(guid[16:18]) + reverse_bytes(guid[18:20]) + \
|
---|
59 | '-' + ''.join([reverse_bytes(guid[i:i+2]) for i in range(20, 32, 2)]) + '}'
|
---|
60 |
|
---|
61 | PRODUCTS_KEY = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products'
|
---|
62 | COMPONENTS_KEY = r'SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components'
|
---|
63 |
|
---|
64 | def get_installed_products():
|
---|
65 | """
|
---|
66 | Enumerate all installed products.
|
---|
67 | """
|
---|
68 | products = {}
|
---|
69 | hkey_products = OpenKey(HKEY_LOCAL_MACHINE, PRODUCTS_KEY, 0, KEY_ALL_ACCESS)
|
---|
70 |
|
---|
71 | try:
|
---|
72 | product_index = 0
|
---|
73 | while True:
|
---|
74 | product_guid = EnumKey(hkey_products, product_index)
|
---|
75 | hkey_product_properties = OpenKey(hkey_products, product_guid + r'\InstallProperties', 0, KEY_ALL_ACCESS)
|
---|
76 | try:
|
---|
77 | value = QueryValueEx(hkey_product_properties, 'DisplayName')[0]
|
---|
78 | except WindowsError as oXcpt:
|
---|
79 | if oXcpt.winerror != 2:
|
---|
80 | raise
|
---|
81 | value = '<unknown>'
|
---|
82 | CloseKey(hkey_product_properties)
|
---|
83 | products[product_guid] = value
|
---|
84 | product_index += 1
|
---|
85 | except WindowsError as oXcpt:
|
---|
86 | if oXcpt.winerror != 259:
|
---|
87 | print(oXcpt.strerror + '.', 'error', oXcpt.winerror)
|
---|
88 | CloseKey(hkey_products)
|
---|
89 |
|
---|
90 | print('Installed products:')
|
---|
91 | for product_key in sorted(products.keys()):
|
---|
92 | print(transpose_guid(product_key), '=', products[product_key])
|
---|
93 |
|
---|
94 | print()
|
---|
95 | return products
|
---|
96 |
|
---|
97 | def get_missing_products(hkey_components):
|
---|
98 | """
|
---|
99 | Detect references to missing products.
|
---|
100 | """
|
---|
101 | products = get_installed_products()
|
---|
102 |
|
---|
103 | missing_products = {}
|
---|
104 |
|
---|
105 | for component_index in xrange(0, QueryInfoKey(hkey_components)[0]):
|
---|
106 | component_guid = EnumKey(hkey_components, component_index)
|
---|
107 | hkey_component = OpenKey(hkey_components, component_guid, 0, KEY_ALL_ACCESS)
|
---|
108 | clients = []
|
---|
109 | for value_index in xrange(0, QueryInfoKey(hkey_component)[1]):
|
---|
110 | client_guid, client_path = EnumValue(hkey_component, value_index)[:2]
|
---|
111 | clients.append((client_guid, client_path))
|
---|
112 | if not client_guid in products:
|
---|
113 | if client_guid in missing_products:
|
---|
114 | missing_products[client_guid].append((component_guid, client_path))
|
---|
115 | else:
|
---|
116 | missing_products[client_guid] = [(component_guid, client_path)]
|
---|
117 | CloseKey(hkey_component)
|
---|
118 | return missing_products
|
---|
119 |
|
---|
120 | def main():
|
---|
121 | """
|
---|
122 | Enumerate all installed products, go through all components and check if client refences
|
---|
123 | point to valid products. Remove references to non-existing products if the user allowed it.
|
---|
124 | """
|
---|
125 | hkey_components = OpenKey(HKEY_LOCAL_MACHINE, COMPONENTS_KEY, 0, KEY_ALL_ACCESS)
|
---|
126 |
|
---|
127 | missing_products = get_missing_products(hkey_components)
|
---|
128 |
|
---|
129 | print('Missing products refer the following components:')
|
---|
130 | for product_guid in sorted(missing_products.keys()):
|
---|
131 | if product_guid[1:] == '0'*31:
|
---|
132 | continue
|
---|
133 | print('Product', transpose_guid(product_guid) + ':')
|
---|
134 | for component_guid, component_file in missing_products[product_guid]:
|
---|
135 | print(' ' + transpose_guid(component_guid), '=', component_file)
|
---|
136 |
|
---|
137 | print('Remove all references to product', transpose_guid(product_guid) + '? [y/n]')
|
---|
138 | if strtobool(raw_input().lower()):
|
---|
139 | for component_guid, component_file in missing_products[product_guid]:
|
---|
140 | hkey_component = OpenKey(hkey_components, component_guid, 0, KEY_ALL_ACCESS)
|
---|
141 | print('Removing reference in ' + transpose_guid(component_guid), '=', component_file)
|
---|
142 | DeleteValue(hkey_component, product_guid)
|
---|
143 | CloseKey(hkey_component)
|
---|
144 | else:
|
---|
145 | print('Cancelled removal of product', transpose_guid(product_guid))
|
---|
146 |
|
---|
147 | CloseKey(hkey_components)
|
---|
148 |
|
---|
149 | if __name__ == "__main__":
|
---|
150 | main()
|
---|