WizardKit/scripts/wk/repairs/win.py
2Shirt 0e124dc1f2
Skip Autologon cleanup if it wasn't used
We found in a few cases systems starting requiring logon passwords after
Auto Repairs were run.  Autologon might be the cause so we should avoid
running it, and the cleanup, if settings are already present.

Addresses issue #175
2021-08-31 17:06:38 -06:00

1388 lines
39 KiB
Python

"""WizardKit: Repairs - Windows"""
# pylint: disable=too-many-lines
# vim: sts=2 sw=2 ts=2
import atexit
import logging
import os
import platform
import re
import sys
import time
from subprocess import CalledProcessError, DEVNULL
from wk.cfg.main import KIT_NAME_FULL, KIT_NAME_SHORT, WINDOWS_TIME_ZONE
from wk.exe import (
get_procs,
run_program,
popen_program,
wait_for_procs,
)
from wk.io import (
delete_folder,
get_path_obj,
non_clobber_path,
rename_item,
)
from wk.kit.tools import download_tool, get_tool_path, run_tool
from wk.log import format_log_path, update_log_path
from wk.os.win import (
get_timezone,
set_timezone,
reg_delete_value,
reg_read_value,
reg_set_value,
reg_write_settings,
disable_service,
enable_service,
stop_service,
)
from wk.std import (
GenericError,
GenericWarning,
Menu,
TryAndPrint,
abort,
ask,
clear_screen,
color_string,
pause,
print_info,
print_standard,
print_warning,
set_title,
show_data,
sleep,
strip_colors,
)
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
AUTO_REPAIR_DELAY_IN_SECONDS = 30
AUTO_REPAIR_KEY = fr'Software\{KIT_NAME_FULL}\Auto Repairs'
BACKUP_BROWSER_BASE_CMD = (
get_tool_path('7-Zip', '7za'),
'a', '-t7z', '-mx=1', '-bso0', '-bse0', '-bsp0',
)
BLEACH_BIT_CLEANERS = (
# Applications
'adobe_reader.cache',
'adobe_reader.tmp',
'flash.cache',
'gimp.tmp',
'hippo_opensim_viewer.cache',
'java.cache',
'miro.cache',
'openofficeorg.cache',
'pidgin.cache',
'secondlife_viewer.Cache',
'thunderbird.cache',
'vuze.cache',
'yahoo_messenger.cache',
# Browsers
'chromium.cache',
'chromium.session',
'firefox.cache',
'firefox.session_restore',
'google_chrome.cache',
'google_chrome.session',
'google_earth.temporary_files',
'opera.cache',
'opera.session',
'safari.cache',
'seamonkey.cache',
# System
'system.clipboard',
'system.tmp',
'winapp2_windows.jump_lists',
'winapp2_windows.ms_search',
'windows_explorer.run',
'windows_explorer.search_history',
'windows_explorer.thumbnails',
)
CONEMU_EXE = get_tool_path('ConEmu', 'ConEmu', check=False)
GPUPDATE_SUCCESS_STRINGS = (
'Computer Policy update has completed successfully.',
'User Policy update has completed successfully.',
)
IN_CONEMU = 'ConEmuPID' in os.environ
PROGRAMFILES_32 = os.environ.get(
'PROGRAMFILES(X86)', os.environ.get(
'PROGRAMFILES', r'C:\Program Files (x86)',
),
)
OS_VERSION = float(platform.win32_ver()[0])
POWER_PLANS = {
'Balanced': '381b4222-f694-41f0-9685-ff5bb260df2e',
'Custom': '01189998-8199-9119-725c-ccccccccccc3',
'High Performance': '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c',
}
REG_UAC_DEFAULT_SETTINGS = {
'HKLM': {
r'Software\Microsoft\Windows\CurrentVersion\Policies\System': (
('ConsentPromptBehaviorAdmin', 5, 'DWORD'),
('ConsentPromptBehaviorUser', 3, 'DWORD'),
('EnableLUA', 1, 'DWORD'),
('PromptOnSecureDesktop', 1, 'DWORD'),
),
},
}
RKILL_WHITELIST = (
CONEMU_EXE,
fr'{PROGRAMFILES_32}\TeamViewer\TeamViewer.exe',
fr'{PROGRAMFILES_32}\TeamViewer\TeamViewer_Desktop.exe',
fr'{PROGRAMFILES_32}\TeamViewer\TeamViewer_Note.exe',
fr'{PROGRAMFILES_32}\TeamViewer\TeamViewer_Service.exe',
fr'{PROGRAMFILES_32}\TeamViewer\tv_w32.exe',
fr'{PROGRAMFILES_32}\TeamViewer\tv_x64.exe',
sys.executable,
)
SYSTEMDRIVE = os.environ.get('SYSTEMDRIVE', 'C:')
WIDTH = 50
TRY_PRINT = TryAndPrint()
TRY_PRINT.width = WIDTH
TRY_PRINT.verbose = True
for error in ('CalledProcessError', 'FileNotFoundError'):
TRY_PRINT.add_error(error)
# Auto Repairs
def build_menus(base_menus, title):
"""Build menus, returns dict."""
menus = {}
menus['Main'] = Menu(title=f'{title}\n{color_string("Main Menu", "GREEN")}')
# Main Menu
for entry in base_menus['Actions']:
menus['Main'].add_action(entry.name, entry.details)
for group in base_menus['Groups']:
menus['Main'].add_option(group, {'Selected': True})
# Options
menus['Options'] = Menu(title=f'{title}\n{color_string("Options", "GREEN")}')
for entry in base_menus['Options']:
menus['Options'].add_option(entry.name, entry.details)
menus['Options'].add_action('All')
menus['Options'].add_action('None')
menus['Options'].add_action('Main Menu', {'Separator': True})
menus['Options'].add_action('Quit')
# Run groups
for group, entries in base_menus['Groups'].items():
menus[group] = Menu(title=f'{title}\n{color_string(group, "GREEN")}')
menus[group].disabled_str = 'Locked'
for entry in entries:
menus[group].add_option(entry.name, entry.details)
menus[group].add_action('All')
menus[group].add_action('None')
menus[group].add_action('Select Skipped Entries', {'Separator': True})
menus[group].add_action('Unlock All Entries')
menus[group].add_action('Main Menu', {'Separator': True})
menus[group].add_action('Quit')
# Initialize main menu display names
menus['Main'].update()
# Fix Function references
for group, menu in menus.items():
if group not in base_menus['Groups']:
continue
for name in menu.options:
_function = menu.options[name]['Function']
if isinstance(_function, str):
menu.options[name]['Function'] = getattr(
sys.modules[__name__], _function,
)
# Done
return menus
def end_session():
"""End Auto Repairs session."""
# Remove logon task
cmd = [
'schtasks', '/delete', '/f',
'/tn', f'{KIT_NAME_FULL}-AutoRepairs',
]
try:
run_program(cmd)
except CalledProcessError:
LOG.error("Failed to remove scheduled task or it doesn't exist.")
# Disable Autologon
autologon_selected = reg_read_value(
'HKCU', AUTO_REPAIR_KEY, 'Use Autologon',
)
if autologon_selected and is_autologon_enabled():
run_tool('Sysinternals', 'Autologon', download=True)
reg_set_value(
'HKLM', r'Software\Microsoft\Windows NT\CurrentVersion\Winlogon',
'AutoAdminLogon', '0', 'SZ',
)
# Delete Auto Repairs keys
try:
reg_delete_value('HKCU', AUTO_REPAIR_KEY, 'SessionStarted')
except FileNotFoundError:
LOG.error('Ending repair session but session not started.')
try:
cmd = ['reg', 'delete', fr'HKCU\{AUTO_REPAIR_KEY}', '/f']
run_program(cmd)
except CalledProcessError:
LOG.error('Failed to remote Auto Repairs session settings')
def get_entry_settings(group, name):
"""Get menu entry settings from the registry, returns dict."""
key_path = fr'{AUTO_REPAIR_KEY}\{group}\{strip_colors(name)}'
settings = {}
for value in ('done', 'failed', 'message', 'selected', 'skipped', 'warning'):
try:
settings[value.title()] = reg_read_value('HKCU', key_path, value)
except FileNotFoundError:
# Ignore and use current settings
pass
# Disable previously run or skipped entries
if settings.get('Done', False) or settings.get('Skipped', False):
settings['Disabled'] = True
# Done
return settings
def init(menus):
"""Initialize Auto Repairs."""
session_started = is_session_started()
# Run AVRemover
if not session_started:
TRY_PRINT.run(
'Run AVRemover...', run_tool, 'AVRemover', 'AVRemover',
download=True, msg_good='DONE',
)
# Check if autologon is needed
if not session_started and is_autologon_enabled():
# Avoid running Autologon and keep current settings
menus['Options'].options['Use Autologon']['Selected'] = False
save_selection_settings(menus)
# Start or resume a repair session
if session_started:
load_settings(menus)
print_info('Resuming session, press CTRL+c to cancel')
for _x in range(AUTO_REPAIR_DELAY_IN_SECONDS, 0, -1):
print(f' {_x} second{"" if _x==1 else "s"} remaining... \r', end='')
sleep(1)
print('')
# Done
return session_started
def init_run(options):
"""Initialize Auto Repairs Run."""
if options['Kill Explorer']['Selected']:
atexit.register(start_explorer)
TRY_PRINT.run('Killing Explorer...', kill_explorer, msg_good='DONE')
if options['Use Autologon']['Selected'] and not is_autologon_enabled():
TRY_PRINT.run(
'Running Autologon...', run_tool,
'Sysinternals', 'Autologon',
download=True, msg_good='DONE',
)
if options['Sync Clock']['Selected']:
TRY_PRINT.run(
'Syncing Clock...', run_tool, 'Neutron', 'Neutron',
cbin=True, msg_good='DONE',
)
if options['Run RKill']['Selected']:
TRY_PRINT.run('Running RKill...', run_rkill, msg_good='DONE')
def init_session(options):
"""Initialize Auto Repairs session."""
reg_set_value('HKCU', AUTO_REPAIR_KEY, 'SessionStarted', 1, 'DWORD')
# Check timezone
zone = get_timezone()
msg = (
'The timezone is currently set to '
f'{zone}, switch it to {WINDOWS_TIME_ZONE}?'
)
if zone != WINDOWS_TIME_ZONE and ask(msg):
set_timezone(WINDOWS_TIME_ZONE)
# Create logon task for Auto Repairs
cmd = [
'schtasks', '/create', '/f',
'/sc', 'ONLOGON',
'/tn', f'{KIT_NAME_FULL}-AutoRepairs',
'/rl', 'HIGHEST',
'/tr', fr'C:\Windows\System32\cmd.exe "/C {sys.executable} {sys.argv[0]}"',
]
if IN_CONEMU:
cmd[-1] = f'{CONEMU_EXE} -run {sys.executable} {sys.argv[0]}'
run_program(cmd)
# One-time tasks
if options['Run TDSSKiller (once)']['Selected']:
TRY_PRINT.run('Running TDSSKiller...', run_tdsskiller, msg_good='DONE')
print('')
reboot(30)
def is_autologon_enabled():
"""Check if Autologon is enabled, returns bool."""
auto_admin_logon = False
try:
auto_admin_logon = reg_read_value(
'HKLM', r'Software\Microsoft\Windows NT\CurrentVersion\Winlogon',
'AutoAdminLogon',
)
except FileNotFoundError:
# Ignore and assume it's disabled
pass
else:
auto_admin_logon = auto_admin_logon != '0'
# Done
return auto_admin_logon
def is_session_started():
"""Check if session was started, returns bool."""
session_started = False
try:
session_started = reg_read_value('HKCU', AUTO_REPAIR_KEY, 'SessionStarted')
except FileNotFoundError:
pass
# Done
return session_started
def load_settings(menus):
"""Load session settings from the registry."""
for group, menu in menus.items():
if group == 'Main':
continue
for name in menu.options:
menu.options[name].update(get_entry_settings(group, name))
def run_auto_repairs(base_menus):
"""Run Auto Repairs."""
update_log_path(dest_name='Auto Repairs', timestamp=True)
title = f'{KIT_NAME_FULL}: Auto Repairs'
clear_screen()
set_title(title)
print_info(title)
print('')
# Generate menus
print_standard('Initializing...')
menus = build_menus(base_menus, title)
# Init
try:
session_started = init(menus)
except KeyboardInterrupt:
# Assuming session was started and resume countdown was interrupted
session_started = None
# Show Menu
if session_started is None or not session_started:
try:
show_main_menu(base_menus, menus)
except SystemExit:
if ask('End session?'):
end_session()
raise
# Re-check if a repair session was started
if session_started is None:
session_started = is_session_started()
# Start or resume repairs
clear_screen()
print_standard(title)
print('')
save_selection_settings(menus)
print_info('Initializing...')
init_run(menus['Options'].options)
reg_set_value(
'HKCU', AUTO_REPAIR_KEY, 'Use Autologon',
int(is_autologon_enabled()), 'DWORD',
)
if not is_autologon_enabled():
# Either it wasn't selected or a password wasn't entered
menus['Options'].options['Use Autologon']['Selected'] = False
save_selection_settings(menus)
if not session_started:
init_session(menus['Options'].options)
print_info('Running repairs')
# Run repairs
for group, menu in menus.items():
if group in ('Main', 'Options'):
continue
try:
run_group(group, menu)
except KeyboardInterrupt:
abort()
# Done
end_session()
print_info('Done')
pause('Press Enter to exit...')
def run_group(group, menu):
"""Run entries in group if appropriate."""
print_info(f' {group}')
for name, details in menu.options.items():
name_str = strip_colors(name)
skipped = details.get('Skipped', False)
done = details.get('Done', False)
disabled = details.get('Disabled', False)
selected = details.get('Selected', False)
# Selection changed
if (skipped or done) and not disabled and selected:
save_settings(group, name, done=False, skipped=False)
details['Function'](group, name)
continue
# Previously skipped
if skipped:
show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
continue
# Previously ran
if done:
color = 'GREEN'
if details.get('Warning', False):
color = 'YELLOW'
elif details.get('Failed', False):
color = 'RED'
show_data(
f'{name_str}...',
details.get('Message', 'Unknown'), color, width=WIDTH,
)
continue
# Not selected
if not selected:
show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
save_settings(group, name, skipped=True)
continue
# Selected
details['Function'](group, name)
def save_selection_settings(menus):
"""Save selections in the registry."""
for group, menu in menus.items():
if group == 'Main':
continue
for name, details in menu.options.items():
save_settings(
group, name,
disabled=details.get('Disabled', False),
selected=details.get('Selected', False),
)
def save_settings(group, name, result=None, **kwargs):
"""Save entry settings in the registry."""
key_path = fr'{AUTO_REPAIR_KEY}\{group}\{strip_colors(name)}'
# Get values from TryAndPrint result
if result:
kwargs.update({
'done': True,
'failed': result['Failed'],
'message': result['Message'],
})
if isinstance(result['Exception'], GenericWarning):
kwargs['warning'] = True
# Write values to registry
for value_name, data in kwargs.items():
if isinstance(data, bool):
data = 1 if data else 0
if isinstance(data, int):
data_type = 'DWORD'
elif isinstance(data, str):
data_type = 'SZ'
else:
raise TypeError(f'Invalid data: "{data}" ({type(data)})')
reg_set_value('HKCU', key_path, value_name, data, data_type)
def show_main_menu(base_menus, menus):
"""Show main menu and handle actions."""
while True:
update_main_menu(menus)
selection = menus['Main'].simple_select(update=False)
if selection[0] in base_menus['Groups'] or selection[0] == 'Options':
show_sub_menu(menus[selection[0]])
elif 'Start' in selection:
break
elif 'Quit' in selection:
raise SystemExit
def show_sub_menu(menu):
"""Show sub-menu and handle sub-menu actions."""
while True:
selection = menu.advanced_select()
if 'Main Menu' in selection:
break
if 'Quit' in selection:
raise SystemExit
# Modify entries
key = 'Selected'
unlock_all = False
unlock_skipped = False
if 'Select Skipped Entries' in selection:
key = 'Disabled'
unlock_skipped = True
value = False
if 'Unlock All Entries' in selection:
key = 'Disabled'
unlock_all = True
value = False
else:
value = 'All' in selection
for name in menu.options:
if (unlock_all
or (unlock_skipped and not menu.options[name].get('Selected', False))
or not menu.options[name].get('Disabled', False)
):
menu.options[name][key] = value
def update_main_menu(menus):
"""Update main menu based on current selections."""
index = 1
skip = 'Reboot'
for name in menus['Main'].options:
checkmark = ' '
selected = [
_v['Selected'] for _k, _v in menus[name].options.items() if _k != skip
]
if all(selected):
checkmark = ''
elif any(selected):
checkmark = '-'
display_name = f' {index}: [{checkmark}] {name}'
index += 1
menus['Main'].options[name]['Display Name'] = display_name
# Auto Repairs: Wrapper Functions
def auto_adwcleaner(group, name):
"""Run AdwCleaner scan.
save_settings() is called first since AdwCleaner may kill this script.
"""
save_settings(group, name, done=True, failed=False, message='DONE')
result = TRY_PRINT.run('AdwCleaner...', run_adwcleaner, msg_good='DONE')
# Update with actual results (assuming this script wasn't killed)
save_settings(group, name, result=result)
def auto_backup_browser_profiles(group, name):
"""Backup browser profiles."""
backup_all_browser_profiles(use_try_print=True)
save_settings(group, name, done=True, failed=False, message='DONE')
def auto_backup_power_plans(group, name):
"""Backup power plans."""
result = TRY_PRINT.run('Backup Power Plans...', export_power_plans)
save_settings(group, name, result=result)
def auto_backup_registry(group, name):
"""Backup registry."""
result = TRY_PRINT.run('Backup Registry...', backup_registry)
save_settings(group, name, result=result)
def auto_bleachbit(group, name):
"""Run BleachBit to clean files."""
result = TRY_PRINT.run(
'BleachBit...', run_bleachbit, BLEACH_BIT_CLEANERS, msg_good='DONE',
)
save_settings(group, name, result=result)
def auto_chkdsk(group, name):
"""Run CHKDSK repairs."""
needs_reboot = False
result = TRY_PRINT.run(f'CHKDSK ({SYSTEMDRIVE})...', run_chkdsk_online)
# Run offline CHKDSK if required
if result['Failed'] and 'Repaired' not in result['Message']:
needs_reboot = True
result = TRY_PRINT.run(
f'Scheduling offline CHKDSK ({SYSTEMDRIVE})...',
run_chkdsk_offline,
)
if not result['Failed']:
# Successfully set dirty bit to force offline check
# Set result['Failed'] to True because we failed to repair online
result['Failed'] = True
result['Message'] = 'Scheduled offline repairs'
# Done
save_settings(group, name, result=result)
if needs_reboot:
reboot()
def auto_disable_pending_renames(group, name):
"""Disable pending renames."""
result = TRY_PRINT.run(
'Disabling pending renames...', disable_pending_renames,
)
save_settings(group, name, result=result)
def auto_dism(group, name):
"""Run DISM repairs."""
needs_reboot = False
result = TRY_PRINT.run('DISM (RestoreHealth)...', run_dism)
# Try again if necessary
if result['Failed']:
TRY_PRINT.run('Enabling Windows Updates...', enable_windows_updates)
result = TRY_PRINT.run('DISM (RestoreHealth)...', run_dism)
TRY_PRINT.run('Disabling Windows Updates...', disable_windows_updates)
needs_reboot = True
# Save settings
save_settings(
group, name, done=True,
failed=result['Failed'],
warning=not result['Failed'] and needs_reboot,
message=result['Message'],
)
# Done
if needs_reboot:
reboot()
def auto_enable_regback(group, name):
"""Enable RegBack."""
result = TRY_PRINT.run(
'Enable RegBack...', reg_set_value, 'HKLM',
r'System\CurrentControlSet\Control\Session Manager\Configuration Manager',
'EnablePeriodicBackup', 1, 'DWORD',
)
save_settings(group, name, result=result)
def auto_hitmanpro(group, name):
"""Run HitmanPro scan."""
result = TRY_PRINT.run('HitmanPro...', run_hitmanpro, msg_good='DONE')
save_settings(group, name, result=result)
def auto_iobit_uninstaller(group, name):
"""Run IO Bit Uninstaller scan."""
result = TRY_PRINT.run(
'IO Bit Uninstaller...', run_iobit_uninstaller, msg_good='DONE',
)
save_settings(group, name, result=result)
def auto_kvrt(group, name):
"""Run KVRT scan."""
result = TRY_PRINT.run('KVRT...', run_kvrt, msg_good='DONE')
save_settings(group, name, result=result)
def auto_microsoft_defender(group, name):
"""Run Microsoft Defender scan."""
result = TRY_PRINT.run(
'Microsoft Defender...', run_microsoft_defender, msg_good='DONE',
)
save_settings(group, name, result=result)
def auto_reboot(group, name):
"""Reboot the system."""
save_settings(group, name, done=True, failed=False, message='DONE')
print('')
reboot(30)
def auto_remove_power_plan(group, name):
"""Remove custom power plan and set to Balanced."""
result = TRY_PRINT.run(
'Remove Custom Power Plan...', remove_custom_power_plan,
)
save_settings(group, name, result=result)
def auto_repair_registry(group, name):
"""Delete registry keys with embedded null characters."""
result = TRY_PRINT.run(
'Running Registry repairs...', delete_registry_null_keys,
)
save_settings(group, name, result=result)
def auto_reset_power_plans(group, name):
"""Reset power plans."""
result = TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
save_settings(group, name, result=result)
def auto_reset_proxy(group, name):
"""Reset proxy settings."""
result = TRY_PRINT.run('Clearing proxy settings...', reset_proxy)
save_settings(group, name, result=result)
def auto_reset_windows_policies(group, name):
"""Reset Windows policies to defaults."""
result = TRY_PRINT.run(
'Resetting Windows policies...', reset_windows_policies,
)
save_settings(group, name, result=result)
def auto_restore_uac_defaults(group, name):
"""Restore UAC default settings."""
result = TRY_PRINT.run('Restoring UAC defaults...', restore_uac_defaults)
save_settings(group, name, result=result)
def auto_set_custom_power_plan(group, name):
"""Set to a custom power plan."""
result = TRY_PRINT.run('Set Custom Power Plan...', create_custom_power_plan)
save_settings(group, name, result=result)
def auto_sfc(group, name):
"""Run SFC repairs."""
result = TRY_PRINT.run('SFC Scan...', run_sfc_scan)
save_settings(group, name, result=result)
def auto_system_restore_create(group, name):
"""Create a System Restore point."""
result = TRY_PRINT.run(
'Create System Restore...', create_system_restore_point,
)
save_settings(group, name, result=result)
def auto_system_restore_enable(group, name):
"""Enable System Restore."""
cmd = [
'powershell', '-Command', 'Enable-ComputerRestore',
'-Drive', SYSTEMDRIVE,
]
result = TRY_PRINT.run('Enable System Restore...', run_program, cmd=cmd)
save_settings(group, name, result=result)
def auto_system_restore_set_size(group, name):
"""Set System Restore size."""
result = TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
save_settings(group, name, result=result)
def auto_windows_updates_disable(group, name):
"""Disable Windows Updates."""
result = TRY_PRINT.run('Disable Windows Updates...', disable_windows_updates)
if result['Failed']:
# Reboot and try again?
reboot()
save_settings(group, name, result=result)
def auto_windows_updates_enable(group, name):
"""Enable Windows Updates."""
result = TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
save_settings(group, name, result=result)
def auto_windows_updates_reset(group, name):
"""Reset Windows Updates."""
result = TRY_PRINT.run('Reset Windows Updates...', reset_windows_updates)
if result['Failed']:
# Reboot and try again?
reboot()
save_settings(group, name, result=result)
# Misc Functions
def set_backup_path(name, date=False):
"""Set backup path, returns pathlib.Path."""
return set_local_storage_path('Backups', name, date)
def set_local_storage_path(folder, name, date=False):
"""Get path for local storage, returns pathlib.Path."""
local_path = get_path_obj(f'{SYSTEMDRIVE}/{KIT_NAME_SHORT}/{folder}/{name}')
if date:
local_path = local_path.joinpath(time.strftime('%Y-%m-%d'))
return local_path
def set_quarantine_path(name, date=False):
"""Set quarantine path, returns pathlib.Path."""
return set_local_storage_path('Quarantine', name, date)
# Tool Functions
def backup_all_browser_profiles(use_try_print=False):
"""Backup browser profiles for all users."""
users = get_path_obj(f'{SYSTEMDRIVE}/Users')
for userprofile in users.iterdir():
if use_try_print:
print_info(f'{" "*6}{userprofile.name}')
backup_browser_profiles(userprofile, use_try_print)
def backup_browser_chromium(backup_path, browser, search_path, use_try_print):
"""Backup Chromium-based browser profile."""
for item in search_path.iterdir():
match = re.match(r'^(Default|Profile).*', item.name, re.IGNORECASE)
if not match:
continue
output_path = backup_path.joinpath(f'{browser}-{item.name}.7z')
cmd = [
*BACKUP_BROWSER_BASE_CMD,
output_path, item.joinpath('*'), '-x!*Cache*', '-x!Service Worker',
]
if use_try_print:
TRY_PRINT.run(
f'{" "*8}{browser} ({item.name})...',
run_program, cmd, check=False,
)
else:
run_program(cmd, check=False)
def backup_browser_firefox(backup_path, search_path, use_try_print):
"""Backup Firefox browser profile."""
output_path = backup_path.joinpath('Firefox.7z')
cmd = [
*BACKUP_BROWSER_BASE_CMD, output_path,
search_path.joinpath('Profiles'), search_path.joinpath('profiles.ini'),
]
if use_try_print:
TRY_PRINT.run(f'{" "*8}Firefox (All)...', run_program, cmd, check=False)
else:
run_program(cmd, check=False)
def backup_browser_profiles(userprofile, use_try_print=False):
"""Backup browser profiles for userprofile."""
backup_path = set_backup_path('Browsers', date=True)
backup_path = backup_path.joinpath(userprofile.name)
backup_path.mkdir(parents=True, exist_ok=True)
# Chrome
search_path = userprofile.joinpath('AppData/Local/Google/Chrome/User Data')
if search_path.exists():
backup_browser_chromium(backup_path, 'Chrome', search_path, use_try_print)
# Edge
search_path = userprofile.joinpath('AppData/Local/Microsoft/Edge/User Data')
if search_path.exists():
backup_browser_chromium(backup_path, 'Edge', search_path, use_try_print)
# Firefox
search_path = userprofile.joinpath('AppData/Roaming/Mozilla/Firefox')
if search_path.joinpath('Profiles').exists():
backup_browser_firefox(backup_path, search_path, use_try_print)
# Remove empty folders
try:
backup_path.rmdir()
except OSError:
# Only looking for empty folders
pass
def backup_registry():
"""Backup Registry."""
backup_path = set_backup_path('Registry', date=True)
backup_path.parent.mkdir(parents=True, exist_ok=True)
run_tool('Erunt', 'ERUNT', backup_path, 'sysreg', 'curuser', 'otherusers')
def delete_registry_null_keys():
"""Delete registry keys with embedded null characters."""
run_tool('RegDelNull', 'RegDelNull', '-s', '-y', cbin=True)
def run_adwcleaner():
"""Run AdwCleaner."""
run_tool('AdwCleaner', 'AdwCleaner', download=True)
def run_bleachbit(cleaners, preview=True):
"""Run BleachBit to either clean or preview files."""
cmd_args = (
'--preview' if preview else '--clean',
*cleaners,
)
log_path = format_log_path(log_name='BleachBit', timestamp=True, tool=True)
log_path.parent.mkdir(parents=True, exist_ok=True)
proc = run_tool('BleachBit', 'bleachbit_console', *cmd_args, cbin=True)
# Save logs
log_path.write_text(proc.stdout)
log_path.with_suffix('.err').write_text(proc.stderr)
def run_hitmanpro():
"""Run HitmanPro scan."""
log_path = format_log_path(log_name='HitmanPro', timestamp=True, tool=True)
log_path = log_path.with_suffix('.xml')
log_path.parent.mkdir(parents=True, exist_ok=True)
cmd_args = ['/scanonly', f'/log={log_path}']
run_tool('HitmanPro', 'HitmanPro', *cmd_args, download=True)
def run_iobit_uninstaller():
"""Run IO Bit Uninstaller."""
run_tool('IObitUninstallerPortable', 'IObitUninstallerPortable', cbin=True)
def run_kvrt():
"""Run KVRT scan."""
log_path = format_log_path(log_name='KVRT', timestamp=True, tool=True)
log_path.parent.mkdir(parents=True, exist_ok=True)
quarantine_path = set_quarantine_path('KVRT')
quarantine_path.mkdir(parents=True, exist_ok=True)
cmd_args = (
'-accepteula',
'-d', str(quarantine_path),
'-dontencrypt', '-fixednames',
'-processlevel', '1',
'-custom', SYSTEMDRIVE,
'-silent', '-adinsilent',
)
# Run in new pane
if IN_CONEMU:
download_tool('KVRT', 'KVRT')
kvrt_path = get_tool_path('KVRT', 'KVRT')
tmp_file = fr'{os.environ.get("TMP")}\run_kvrt.cmd'
with open(tmp_file, 'w') as _f:
_f.write('@echo off\n')
_f.write(f'"{kvrt_path}" {" ".join(cmd_args)}\n')
cmd = ('cmd', '/c', tmp_file, '-new_console:nb', '-new_console:s33V')
run_program(cmd, check=False)
sleep(1)
wait_for_procs('KVRT.exe')
return
# Run in background
proc = run_tool('KVRT', 'KVRT', *cmd_args, download=True)
log_path.write_text(proc.stdout)
def run_microsoft_defender(full=True):
"""Run Microsoft Defender scan."""
reg_key = r'Software\Microsoft\Windows Defender'
def _get_defender_path():
install_path = reg_read_value('HKLM', reg_key, 'InstallLocation')
return fr'{install_path}\MpCmdRun.exe'
log_path = format_log_path(
log_name='Microsoft Defender', timestamp=True, tool=True,
)
log_path.parent.mkdir(parents=True, exist_ok=True)
# Get MS Defender status
## NOTE: disabled may be set to an int instead of bool
## This is fine because we're just checking if it's enabled.
disabled = bool(reg_read_value('HKLM', reg_key, 'DisableAntiSpyware'))
disabled = disabled or reg_read_value('HKLM', reg_key, 'DisableAntiVirus')
passive_mode = reg_read_value('HKLM', reg_key, 'PassiveMode') == 2
if disabled and not passive_mode:
raise GenericError('Defender is disabled.')
# Update signatures
defender_path = _get_defender_path()
cmd = (defender_path, '-SignatureUpdate')
proc = run_program(cmd, check=False)
sleep(2)
if proc.returncode > 0:
LOG.warning('Failed to update Defender signatures')
# Update defender path in case it changed after the update
defender_path = _get_defender_path()
# Run scan
cmd = (defender_path, '-Scan', '-ScanType', '2' if full else '1')
proc = run_program(cmd, check=False)
log_path.write_text(proc.stdout)
if proc.returncode > 0:
raise GenericError('Failed to run scan or clean items.')
def run_rkill():
"""Run RKill scan."""
log_path = format_log_path(log_name='RKill', timestamp=True, tool=True)
log_path.parent.mkdir(parents=True, exist_ok=True)
whitelist_path = log_path.with_suffix('.wl')
whitelist_path.write_text('\n'.join(map(str, RKILL_WHITELIST)))
cmd_args = (
'-l', log_path,
'-w', whitelist_path,
'-s',
)
run_tool('RKill', 'RKill', *cmd_args, download=True)
def run_tdsskiller():
"""Run TDSSKiller scan."""
log_path = format_log_path(log_name='TDSSKiller', timestamp=True, tool=True)
log_path.parent.mkdir(parents=True, exist_ok=True)
quarantine_path = set_quarantine_path('TDSSKiller')
quarantine_path.mkdir(parents=True, exist_ok=True)
cmd_args = (
'-accepteula',
'-accepteulaksn',
'-l', log_path,
'-qpath', quarantine_path,
'-qsus',
'-dcexact',
'-silent',
)
run_tool('TDSSKiller', 'TDSSKiller', *cmd_args, download=True)
# OS Built-in Functions
def create_custom_power_plan():
"""Create new power plan and set as active."""
custom_guid = POWER_PLANS['Custom']
# Duplicate High Performance plan
cmd = [
'powercfg', '-DuplicateScheme',
POWER_PLANS['High Performance'], custom_guid,
]
run_program(cmd)
# Change the name
cmd = ['powercfg', '-ChangeName', custom_guid, KIT_NAME_FULL]
run_program(cmd)
# Set as active plan
cmd = ['powercfg', '-SetActive', custom_guid]
run_program(cmd)
# Keep the display on
for setting in ('monitor-timeout-ac', 'monitor-timeout-dc'):
cmd = ['powercfg', '-Change', setting, '0']
run_program(cmd)
# Set CPU min state
for arg in ('-SetacValueIndex', '-SetdcValueIndex'):
cmd = [
'powercfg', arg, custom_guid, 'SUB_PROCESSOR', 'PROCTHROTTLEMIN', '5',
]
run_program(cmd)
def create_system_restore_point():
"""Create System Restore point."""
cmd = [
'powershell', '-Command', 'Checkpoint-Computer',
'-Description', f'{KIT_NAME_FULL}-AutoRepairs',
]
too_recent = (
'WARNING: A new system restore point cannot be created'
'because one has already been created within the past'
)
proc = run_program(cmd)
if too_recent in proc.stdout:
raise GenericWarning('Skipped, a restore point was created too recently')
def disable_pending_renames():
"""Disable pending renames."""
reg_set_value(
'HKLM', r'SYSTEM\CurrentControlSet\Control\Session Manager',
'PendingFileRenameOperations', [], 'MULTI_SZ',
)
def disable_windows_updates():
"""Disable and stop Windows Updates."""
disable_service('wuauserv')
stop_service('wuauserv')
def enable_windows_updates():
"""Enable Windows Updates."""
enable_service('wuauserv', 'demand')
def export_power_plans():
"""Export existing power plans."""
backup_path = set_backup_path('Power Plans', date=True)
backup_path.mkdir(parents=True, exist_ok=True)
cmd = ['powercfg', '/L']
proc = run_program(cmd)
plans = {}
# Get plans
for line in proc.stdout.splitlines():
line = line.strip()
match = re.match(r'^Power Scheme GUID: (.{36})\s+\((.*)\)\s*(\*?)', line)
if match:
name = match.group(2)
if match.group(3):
name += ' (Default)'
plans[name] = match.group(1)
# Backup plans to disk
for name, guid in plans.items():
out_path = backup_path.joinpath(f'{name}.pow')
cmd = ['powercfg', '-export', out_path, guid]
run_program(cmd)
def kill_explorer():
"""Kill all Explorer processes."""
cmd = ['taskkill', '/im', 'explorer.exe', '/f']
run_program(cmd, check=False)
def reboot(timeout=10):
"""Reboot the system."""
atexit.unregister(start_explorer)
print_warning(f'Rebooting the system in {timeout} seconds...')
sleep(timeout)
cmd = ['shutdown', '-r', '-t', '0']
run_program(cmd, check=False)
raise SystemExit
def remove_custom_power_plan(high_performance=False):
"""Remove custom power plan and set to a built-in plan.
If high_performance is True then set to High Performance and set
min CPU state to 5% otherwise set to Balanced with default settings.
"""
power_guid = POWER_PLANS['Balanced']
if high_performance:
power_guid = POWER_PLANS['High Performance']
# Set active plan
cmd = ['powercfg', '-SetActive', power_guid]
run_program(cmd)
# Delete custom power plan (if present)
cmd = ['powercfg', '-Delete', POWER_PLANS['Custom']]
run_program(cmd, check=False)
# Set CPU min state (if needed)
if high_performance:
for arg in ('-SetacValueIndex', '-SetdcValueIndex'):
cmd = [
'powercfg', arg, power_guid, 'SUB_PROCESSOR', 'PROCTHROTTLEMIN', '5',
]
run_program(cmd)
def reset_power_plans():
"""Reset power plans to their default settings."""
cmd = ['powercfg', '-RestoreDefaultSchemes']
run_program(cmd)
def reset_proxy():
"""Reset WinHTTP proxy settings."""
cmd = ['netsh', 'winhttp', 'reset', 'proxy']
proc = run_program(cmd, check=False)
# Check result
if 'Direct access (no proxy server)' not in proc.stdout:
raise GenericError('Failed to reset proxy settings.')
def reset_windows_policies():
"""Reset Windows policies to defaults."""
cmd = ['gpupdate', '/force']
proc = run_program(cmd, check=False)
# Check result
if not all(_s in proc.stdout for _s in GPUPDATE_SUCCESS_STRINGS):
raise GenericError('Failed to reset one or more policies.')
def reset_windows_updates():
"""Reset Windows Updates."""
system_root = os.environ.get('SYSTEMROOT', 'C:/Windows')
src_path = f'{system_root}/SoftwareDistribution'
dest_path = non_clobber_path(f'{src_path}.old')
try:
rename_item(src_path, dest_path)
delete_folder(dest_path, force=True)
except FileNotFoundError:
# Ignore
pass
def restore_uac_defaults():
"""Restore UAC default settings."""
reg_write_settings(REG_UAC_DEFAULT_SETTINGS)
def run_chkdsk_offline():
"""Set filesystem 'dirty bit' to force a CHKDSK during startup."""
cmd = ['fsutil', 'dirty', 'set', SYSTEMDRIVE]
proc = run_program(cmd, check=False)
# Check result
if proc.returncode > 0:
raise GenericError('Failed to set dirty bit.')
def run_chkdsk_online():
"""Run CHKDSK.
NOTE: If run on Windows 8+ online repairs are attempted.
"""
cmd = ['CHKDSK', SYSTEMDRIVE]
if OS_VERSION >= 8:
cmd.extend(['/scan', '/perf'])
if IN_CONEMU:
cmd.extend(['-new_console:nb', '-new_console:s33V'])
retried = False
# Run scan
run_program(cmd, check=False)
try:
proc = get_procs('chkdsk.exe')[0]
return_code = proc.wait()
except IndexError:
# Failed to get CHKDSK process, set return_code to force a retry
return_code = 255
if return_code > 1:
# Try again
retried = True
run_program(cmd, check=False)
try:
proc = get_procs('chkdsk.exe')[0]
return_code = proc.wait()
except IndexError:
# Failed to get CHKDSK process
return_code = -1
# Check result
if return_code == -1:
raise GenericError('Failed to find CHKDSK process.')
if (return_code == 0 and retried) or return_code == 1:
raise GenericWarning('Repaired (or manually aborted)')
if return_code > 1:
raise GenericError('Issue(s) detected')
def run_dism(repair=True):
"""Run DISM to either scan or repair component store health."""
conemu_args = ['-new_console:nb', '-new_console:s33V'] if IN_CONEMU else []
# Bail early
if OS_VERSION < 8:
raise GenericWarning('Unsupported OS')
# Run (repair) scan
log_path = format_log_path(
log_name=f'DISM_{"Restore" if repair else "Scan"}Health',
timestamp=True, tool=True,
)
log_path.parent.mkdir(parents=True, exist_ok=True)
cmd = [
'DISM', '/Online', '/Cleanup-Image',
'/RestoreHealth' if repair else '/ScanHealth',
f'/LogPath:{log_path}',
*conemu_args,
]
run_program(cmd, check=False, pipe=False)
wait_for_procs('dism.exe')
# Run check health
log_path = format_log_path(
log_name='DISM_CheckHealth.log', timestamp=True, tool=True,
)
cmd = [
'DISM', '/Online', '/Cleanup-Image',
'/CheckHealth',
f'/LogPath:{log_path}',
]
proc = run_program(cmd, check=False)
# Check for errors
if 'no component store corruption detected' not in proc.stdout.lower():
raise GenericError('Issue(s) detected')
def run_sfc_scan():
"""Run SFC and save results."""
cmd = ['sfc', '/scannow']
log_path = format_log_path(log_name='SFC', timestamp=True, tool=True)
err_path = log_path.with_suffix('.err')
# Run SFC
proc = run_program(cmd, check=False, encoding='utf-16le')
# Save output
os.makedirs(log_path.parent, exist_ok=True)
with open(log_path, 'a') as _f:
_f.write(proc.stdout)
with open(err_path, 'a') as _f:
_f.write(proc.stderr)
# Check result
if 'did not find any integrity violations' in proc.stdout:
pass
elif 'successfully repaired' in proc.stdout:
raise GenericWarning('Repaired')
elif 'found corrupt files' in proc.stdout:
raise GenericError('Corruption detected')
else:
raise OSError
def set_system_restore_size(size=8):
"""Set System Restore size."""
cmd = [
'vssadmin', 'Resize', 'ShadowStorage',
f'/On={SYSTEMDRIVE}', f'/For={SYSTEMDRIVE}', f'/MaxSize={size}%',
]
run_program(cmd, pipe=False, stderr=DEVNULL, stdout=DEVNULL)
def start_explorer():
"""Start Explorer."""
popen_program(['explorer.exe'])
if __name__ == '__main__':
print("This file is not meant to be called directly.")