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