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.""" |
| 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 |