OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # Copyright 2017 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 # A tool for manipulating the user retention experiment state for a Chrome | |
7 # install. | |
8 # | |
9 # usage: experiment_tool_win.py [-h] --operation {clean,prep} | |
10 # [--channel {stable,beta,dev,canary}] | |
11 # [--system-level] | |
12 # | |
13 # A helper for working with state associated with the M60 Chrome on Windows 10 | |
14 # retention experiment. | |
15 # | |
16 # optional arguments: | |
17 # -h, --help show this help message and exit | |
18 # --operation {clean,prep} | |
19 # The operation to be performed. | |
20 # --channel {stable,beta,dev,canary} | |
21 # The install on which to operate (stable by default). | |
22 # --system-level Specify to operate on a system-level install (user- | |
23 # level by default). | |
24 | |
25 import argparse | |
26 import math | |
27 import sys | |
28 from datetime import datetime, timedelta | |
29 import win32api | |
30 import win32security | |
31 import _winreg | |
32 | |
33 | |
34 def GetUserSidString(): | |
35 """Returns the current user's SID string.""" | |
36 token_handle = win32security.OpenProcessToken(win32api.GetCurrentProcess(), | |
37 win32security.TOKEN_QUERY) | |
38 user_sid, _ = win32security.GetTokenInformation(token_handle, | |
39 win32security.TokenUser) | |
40 return win32security.ConvertSidToStringSid(user_sid) | |
41 | |
42 | |
43 def InternalTimeFromPyTime(pytime): | |
44 """Returns a Chromium internal time value representing a Python datetime.""" | |
45 # Microseconds since 1601-01-01 00:00:00 UTC | |
46 delta = pytime - datetime(1601, 1, 1) | |
47 return math.trunc(delta.total_seconds()) * 1000000 + delta.microseconds | |
48 | |
49 | |
50 class ChromeState: | |
51 """An object that provides mutations on Chrome's state relating to the | |
52 user experiment. | |
53 """ | |
54 _CHANNEL_CONFIGS = { | |
55 'stable': { | |
56 'guid': '{8A69D345-D564-463c-AFF1-A69D9E530F96}' | |
57 }, | |
58 'beta': { | |
59 'guid': '{8237E44A-0054-442C-B6B6-EA0509993955}' | |
60 }, | |
61 'dev': { | |
62 'guid': '{401C381F-E0DE-4B85-8BD8-3F3F14FBDA57}' | |
63 }, | |
64 'canary': { | |
65 'guid': '{4ea16ac7-fd5a-47c3-875b-dbf4a2008c20}' | |
66 }, | |
67 } | |
68 _GOOGLE_UPDATE_PATH = 'Software\\Google\\Update' | |
69 | |
70 def __init__(self, channel_name, system_level): | |
71 self._config = ChromeState._CHANNEL_CONFIGS[channel_name] | |
72 self._system_level = system_level | |
73 self._registry_root = _winreg.HKEY_LOCAL_MACHINE if self._system_level \ | |
74 else _winreg.HKEY_CURRENT_USER | |
75 | |
76 def DeleteRetentionStudyValue(self): | |
77 """Deletes the RetntionStudy value for the install.""" | |
Patrick Monette
2017/06/13 19:13:40
Typo
grt (UTC plus 2)
2017/06/14 20:43:44
Done.
| |
78 path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \ | |
79 self._config['guid'] | |
80 try: | |
81 with _winreg.OpenKey(self._registry_root, path, 0, | |
82 _winreg.KEY_WOW64_32KEY | | |
83 _winreg.KEY_SET_VALUE) as key: | |
84 _winreg.DeleteValue(key, 'RetentionStudy') | |
85 except WindowsError as error: | |
86 if error.winerror != 2: | |
87 raise | |
88 | |
89 def DeleteExperimentLabelsValue(self): | |
90 """Deletes the experiment_labels for the install.""" | |
91 medium = 'Medium' if self._system_level else '' | |
92 path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState' + medium + '\\' + \ | |
93 self._config['guid'] | |
94 try: | |
95 with _winreg.OpenKey(self._registry_root, path, 0, | |
96 _winreg.KEY_WOW64_32KEY | | |
97 _winreg.KEY_SET_VALUE) as key: | |
98 _winreg.DeleteValue(key, 'experiment_labels') | |
99 except WindowsError as error: | |
100 if error.winerror != 2: | |
101 raise | |
102 | |
103 def DeleteRentionKey(self): | |
104 """Deletes the Retention key for the current user.""" | |
105 medium = 'Medium' if self._system_level else '' | |
106 path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState' + medium + '\\' + \ | |
107 self._config['guid'] + '\\Retention' | |
108 try: | |
109 if self._system_level: | |
110 _winreg.DeleteKeyEx(self._registry_root, | |
111 path + '\\' + GetUserSidString(), | |
112 _winreg.KEY_WOW64_32KEY) | |
113 _winreg.DeleteKeyEx(self._registry_root, path, _winreg.KEY_WOW64_32KEY) | |
114 except WindowsError as error: | |
115 if error.winerror != 2: | |
116 raise | |
117 | |
118 def SetLastRunTime(self, delta): | |
119 """Sets Chrome's lastrun time for the current user.""" | |
120 path = ChromeState._GOOGLE_UPDATE_PATH + '\\ClientState\\' + \ | |
121 self._config['guid'] | |
122 lastrun = InternalTimeFromPyTime(datetime.utcnow() - delta) | |
123 with _winreg.CreateKeyEx(_winreg.HKEY_CURRENT_USER, path, 0, | |
124 _winreg.KEY_WOW64_32KEY | | |
125 _winreg.KEY_SET_VALUE) as key: | |
126 _winreg.SetValueEx(key, 'lastrun', 0, _winreg.REG_SZ, str(lastrun)) | |
127 | |
128 | |
129 def DoClean(chrome_state): | |
130 """Deletes all state associated with the user experiment.""" | |
131 chrome_state.DeleteRetentionStudyValue() | |
132 chrome_state.DeleteExperimentLabelsValue() | |
133 chrome_state.DeleteRentionKey() | |
134 return 0 | |
135 | |
136 | |
137 def DoPrep(chrome_state): | |
138 """Prepares an install for participation in the experiment.""" | |
139 # Clear old state. | |
140 DoClean(chrome_state) | |
141 # Make Chrome appear to have been last run 30 days ago. | |
142 chrome_state.SetLastRunTime(timedelta(30)) | |
143 return 0 | |
144 | |
145 | |
146 def main(options): | |
147 chrome_state = ChromeState(options.channel, options.system_level) | |
148 if options.operation == 'clean': | |
149 return DoClean(chrome_state) | |
150 if options.operation == 'prep': | |
151 return DoPrep(chrome_state) | |
152 return 1 | |
153 | |
154 | |
155 if __name__ == '__main__': | |
156 parser = argparse.ArgumentParser( | |
157 description='A helper for working with state associated with the M60 ' | |
158 'Chrome on Windows 10 retention experiment.') | |
159 parser.add_argument('--operation', required=True, choices=['clean', 'prep'], | |
160 help='The operation to be performed.') | |
161 parser.add_argument('--channel', default='stable', | |
162 choices=['stable', 'beta', 'dev', 'canary'], | |
163 help='The install on which to operate (stable by ' \ | |
164 'default).') | |
165 parser.add_argument('--system-level', action='store_true', | |
166 help='Specify to operate on a system-level install ' \ | |
167 '(user-level by default).') | |
168 sys.exit(main(parser.parse_args())) | |
OLD | NEW |