957 lines
27 KiB
Python
957 lines
27 KiB
Python
"""WizardKit: Setup - Windows"""
|
|
# vim: sts=2 sw=2 ts=2
|
|
|
|
import configparser
|
|
import logging
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
from typing import Any
|
|
|
|
from wk.cfg.main import KIT_NAME_FULL
|
|
from wk.cfg.setup import (
|
|
BROWSER_PATHS,
|
|
DISABLED_ENTRIES_WINDOWS_11,
|
|
LIBREOFFICE_XCU_DATA,
|
|
REG_CHROME_UBLOCK_ORIGIN,
|
|
REG_WINDOWS_EXPLORER,
|
|
REG_OPEN_SHELL_SETTINGS,
|
|
REG_OPEN_SHELL_LOW_POWER_IDLE,
|
|
UBLOCK_ORIGIN_URLS,
|
|
)
|
|
from wk.exe import kill_procs, run_program, popen_program
|
|
from wk.io import case_insensitive_path, get_path_obj
|
|
from wk.kit.tools import (
|
|
ARCH,
|
|
extract_archive,
|
|
extract_tool,
|
|
find_kit_dir,
|
|
get_tool_path,
|
|
run_tool,
|
|
)
|
|
from wk.log import format_log_path, update_log_path
|
|
from wk.os.win import (
|
|
OS_VERSION,
|
|
activate_with_bios,
|
|
check_4k_alignment,
|
|
get_installed_antivirus,
|
|
get_installed_ram,
|
|
get_os_activation,
|
|
get_os_name,
|
|
get_raw_disks,
|
|
get_volume_usage,
|
|
is_activated,
|
|
is_secure_boot_enabled,
|
|
reg_set_value,
|
|
reg_write_settings,
|
|
winget_check,
|
|
winget_import,
|
|
winget_upgrade,
|
|
)
|
|
from wk.repairs.win import (
|
|
WIDTH,
|
|
backup_all_browser_profiles,
|
|
backup_registry,
|
|
create_custom_power_plan,
|
|
create_system_restore_point,
|
|
enable_windows_updates,
|
|
export_power_plans,
|
|
reset_power_plans,
|
|
set_system_restore_size,
|
|
)
|
|
from wk.std import (
|
|
GenericError,
|
|
GenericWarning,
|
|
sleep,
|
|
)
|
|
from wk.ui import cli as ui
|
|
from wk.ui import ansi
|
|
|
|
|
|
# STATIC VARIABLES
|
|
LOG = logging.getLogger(__name__)
|
|
CONEMU_EXE = get_tool_path('ConEmu', 'ConEmu', check=False)
|
|
KNOWN_ENCODINGS = (
|
|
'utf-8',
|
|
'utf-16',
|
|
'utf-32',
|
|
'utf-16-be',
|
|
'utf-16-le',
|
|
'utf-32-be',
|
|
'utf-32-le',
|
|
)
|
|
IN_CONEMU = 'ConEmuPID' in os.environ
|
|
MENU_PRESETS = ui.Menu()
|
|
PROGRAMFILES_32 = os.environ.get(
|
|
'PROGRAMFILES(X86)', os.environ.get(
|
|
'PROGRAMFILES', r'C:\Program Files (x86)',
|
|
),
|
|
)
|
|
PROGRAMFILES_64 = os.environ.get(
|
|
'PROGRAMW6432', os.environ.get(
|
|
'PROGRAMFILES', r'C:\Program Files',
|
|
),
|
|
)
|
|
SYSTEMDRIVE = os.environ.get('SYSTEMDRIVE', 'C:')
|
|
TRY_PRINT = ui.TryAndPrint()
|
|
TRY_PRINT.width = WIDTH
|
|
TRY_PRINT.verbose = True
|
|
for error in ('CalledProcessError', 'FileNotFoundError'):
|
|
TRY_PRINT.add_error(error)
|
|
|
|
|
|
# Auto Setup
|
|
def build_menus(base_menus, title, presets) -> dict[str, ui.Menu]:
|
|
"""Build menus, returns dict."""
|
|
menus = {}
|
|
menus['Main'] = ui.Menu(title=f'{title}\n{ansi.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})
|
|
|
|
# Run groups
|
|
for group, entries in base_menus['Groups'].items():
|
|
menus[group] = ui.Menu(title=f'{title}\n{ansi.color_string(group, "GREEN")}')
|
|
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('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,
|
|
)
|
|
|
|
# Update presets
|
|
for group, entries in base_menus['Groups'].items():
|
|
presets['Default'][group] = tuple(
|
|
entry.name for entry in entries if entry.details['Selected']
|
|
)
|
|
|
|
# Update presets Menu
|
|
MENU_PRESETS.title = f'{title}\n{ansi.color_string("Load Preset", "GREEN")}'
|
|
MENU_PRESETS.add_option('Default')
|
|
for name in presets:
|
|
MENU_PRESETS.add_option(name)
|
|
MENU_PRESETS.add_option('Custom')
|
|
MENU_PRESETS.add_action('Main Menu')
|
|
MENU_PRESETS.add_action('Quit')
|
|
MENU_PRESETS.update()
|
|
|
|
# Done
|
|
return menus
|
|
|
|
|
|
def check_os_and_set_menu_title(title) -> str:
|
|
"""Check OS version and update title for menus, returns str."""
|
|
color = None
|
|
os_name = get_os_name(check=False)
|
|
ui.print_standard(f'Operating System: {os_name}')
|
|
|
|
# Check support status and set color
|
|
try:
|
|
get_os_name()
|
|
except GenericWarning:
|
|
# Outdated version
|
|
ui.print_warning('OS version is outdated, updating is recommended.')
|
|
if not ui.ask('Continue anyway?'):
|
|
ui.abort()
|
|
color = 'YELLOW'
|
|
except GenericError:
|
|
# Unsupported version
|
|
ui.print_error('OS version is unsupported, updating is recommended.')
|
|
if not ui.ask('Continue anyway? (NOT RECOMMENDED)'):
|
|
ui.abort()
|
|
color = 'RED'
|
|
|
|
# Done
|
|
return f'{title} ({ansi.color_string(os_name, color)})'
|
|
|
|
|
|
def load_preset(menus, presets, title, enable_menu_exit=True) -> None:
|
|
"""Load menu settings from preset and ask selection question(s)."""
|
|
if not enable_menu_exit:
|
|
MENU_PRESETS.actions['Main Menu'].update({'Disabled':True, 'Hidden':True})
|
|
|
|
# Get selection
|
|
selection = MENU_PRESETS.simple_select()
|
|
|
|
# Exit early
|
|
if 'Main Menu' in selection:
|
|
return
|
|
if 'Quit' in selection:
|
|
raise SystemExit
|
|
|
|
# Load preset
|
|
preset = presets[selection[0]]
|
|
for group, menu in menus.items():
|
|
group_enabled = group in preset
|
|
for name in menu.options:
|
|
value = group_enabled and name in preset[group]
|
|
menu.options[name]['Selected'] = value
|
|
|
|
# Ask selection question(s)
|
|
ui.clear_screen()
|
|
ui.print_standard(f'{title}')
|
|
print('')
|
|
if selection[0] == 'Default' and ui.ask('Install LibreOffice?'):
|
|
menus['Install Software'].options['LibreOffice']['Selected'] = True
|
|
|
|
# Re-enable Main Menu action if disabled
|
|
MENU_PRESETS.actions['Main Menu'].update({'Disabled':False, 'Hidden':False})
|
|
|
|
# Disable entries incompatible with Windows 11
|
|
if OS_VERSION == 11:
|
|
for group_name, entry_name in DISABLED_ENTRIES_WINDOWS_11.items():
|
|
menus[group_name].options[entry_name]['Disabled'] = True
|
|
menus[group_name].options[entry_name]['Selected'] = False
|
|
|
|
|
|
def run_auto_setup(base_menus, presets) -> None:
|
|
"""Run Auto Setup."""
|
|
update_log_path(dest_name='Auto Setup', timestamp=True)
|
|
title = f'{KIT_NAME_FULL}: Auto Setup'
|
|
ui.clear_screen()
|
|
ui.set_title(title)
|
|
ui.print_info(title)
|
|
print('')
|
|
ui.print_standard('Initializing...')
|
|
|
|
# Check OS and update title for menus
|
|
title = check_os_and_set_menu_title(title)
|
|
|
|
# Generate menus
|
|
menus = build_menus(base_menus, title, presets)
|
|
|
|
# Get setup preset and ask initial questions
|
|
load_preset(menus, presets, title, enable_menu_exit=False)
|
|
|
|
# Show Menu
|
|
show_main_menu(base_menus, menus, presets, title)
|
|
|
|
# Start setup
|
|
ui.clear_screen()
|
|
ui.print_standard(title)
|
|
print('')
|
|
ui.print_info('Running setup')
|
|
|
|
# Run setup
|
|
for group, menu in menus.items():
|
|
if group in ('Main', 'Options'):
|
|
continue
|
|
try:
|
|
run_group(group, menu)
|
|
except KeyboardInterrupt:
|
|
ui.abort()
|
|
|
|
# Done
|
|
ui.print_info('Done')
|
|
ui.pause('Press Enter to exit...')
|
|
|
|
|
|
def run_group(group, menu) -> None:
|
|
"""Run entries in group if appropriate."""
|
|
ui.print_info(f' {group}')
|
|
for name, details in menu.options.items():
|
|
name_str = ansi.strip_colors(name)
|
|
|
|
# Not selected
|
|
if not details.get('Selected', False):
|
|
ui.show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
|
continue
|
|
|
|
# Selected
|
|
details['Function']()
|
|
|
|
|
|
def show_main_menu(base_menus, menus, presets, title) -> None:
|
|
"""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]])
|
|
if selection[0] == 'Load Preset':
|
|
load_preset(menus, presets, title)
|
|
elif 'Start' in selection:
|
|
break
|
|
elif 'Quit' in selection:
|
|
raise SystemExit
|
|
|
|
|
|
def show_sub_menu(menu) -> None:
|
|
"""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
|
|
|
|
# Select all or none
|
|
value = 'All' in selection
|
|
for name in menu.options:
|
|
if not menu.options[name].get('Disabled', False):
|
|
menu.options[name]['Selected'] = value
|
|
|
|
|
|
def update_main_menu(menus) -> None:
|
|
"""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_backup_registry() -> None:
|
|
"""Backup registry."""
|
|
TRY_PRINT.run('Backup Registry...', backup_registry)
|
|
|
|
|
|
def auto_backup_browser_profiles() -> None:
|
|
"""Backup browser profiles."""
|
|
backup_all_browser_profiles(use_try_print=True)
|
|
|
|
|
|
def auto_backup_power_plans() -> None:
|
|
"""Backup power plans."""
|
|
TRY_PRINT.run('Backup Power Plans...', export_power_plans)
|
|
|
|
|
|
def auto_reset_power_plans() -> None:
|
|
"""Reset power plans."""
|
|
TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
|
|
|
|
|
|
def auto_set_custom_power_plan() -> None:
|
|
"""Set custom power plan."""
|
|
TRY_PRINT.run('Set Custom Power Plan...', create_custom_power_plan)
|
|
|
|
|
|
def auto_enable_bsod_minidumps() -> None:
|
|
"""Enable saving minidumps during BSoDs."""
|
|
TRY_PRINT.run('Enable BSoD mini dumps...', enable_bsod_minidumps)
|
|
|
|
|
|
def auto_enable_regback() -> None:
|
|
"""Enable RegBack."""
|
|
TRY_PRINT.run(
|
|
'Enable RegBack...', reg_set_value, 'HKLM',
|
|
r'System\CurrentControlSet\Control\Session Manager\Configuration Manager',
|
|
'EnablePeriodicBackup', 1, 'DWORD',
|
|
)
|
|
|
|
|
|
def auto_system_restore_enable() -> None:
|
|
"""Enable System Restore."""
|
|
cmd = [
|
|
'powershell', '-Command', 'Enable-ComputerRestore',
|
|
'-Drive', SYSTEMDRIVE,
|
|
]
|
|
TRY_PRINT.run('Enable System Restore...', run_program, cmd=cmd)
|
|
|
|
|
|
def auto_system_restore_set_size() -> None:
|
|
"""Set System Restore size."""
|
|
TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
|
|
|
|
|
|
def auto_system_restore_create() -> None:
|
|
"""Create System Restore point."""
|
|
TRY_PRINT.run('Create System Restore...', create_system_restore_point)
|
|
|
|
|
|
def auto_windows_updates_enable() -> None:
|
|
"""Enable Windows Updates."""
|
|
TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
|
|
|
|
|
|
# Auto Setup: Wrapper Functions
|
|
def auto_activate_windows() -> None:
|
|
"""Attempt to activate Windows using BIOS key."""
|
|
TRY_PRINT.run('Windows Activation...', activate_with_bios)
|
|
|
|
|
|
def auto_config_browsers() -> None:
|
|
"""Configure Browsers."""
|
|
prompt = ' Press Enter to continue...'
|
|
TRY_PRINT.run('Chrome Notifications...', disable_chrome_notifications)
|
|
TRY_PRINT.run(
|
|
'uBlock Origin...', enable_ublock_origin, msg_good='STARTED',
|
|
)
|
|
TRY_PRINT.run(
|
|
'Set default browser...', set_default_browser, msg_good='STARTED',
|
|
)
|
|
print(prompt, end='', flush=True)
|
|
ui.pause(' ')
|
|
|
|
# Move cursor to beginning of the previous line and clear prompt
|
|
print(f'\033[F\r{" "*len(prompt)}\r', end='', flush=True)
|
|
|
|
|
|
def auto_config_explorer() -> None:
|
|
"""Configure Windows Explorer and restart the process."""
|
|
TRY_PRINT.run('Windows Explorer...', config_explorer)
|
|
|
|
|
|
def auto_config_open_shell() -> None:
|
|
"""Configure Open Shell."""
|
|
TRY_PRINT.run('Open Shell...', config_open_shell)
|
|
|
|
|
|
def auto_export_aida64_report() -> None:
|
|
"""Export AIDA64 reports."""
|
|
TRY_PRINT.run('AIDA64 Report...', export_aida64_report)
|
|
|
|
|
|
def auto_install_firefox() -> None:
|
|
"""Install Firefox."""
|
|
TRY_PRINT.run('Firefox...', install_firefox)
|
|
|
|
|
|
def auto_install_libreoffice() -> None:
|
|
"""Install LibreOffice.
|
|
|
|
NOTE: It is assumed that auto_install_vcredists() will be run
|
|
before this function to satisfy the vcredist=False usage.
|
|
"""
|
|
TRY_PRINT.run('LibreOffice...', install_libreoffice, vcredist=False)
|
|
|
|
|
|
def auto_install_open_shell() -> None:
|
|
"""Install Open Shell."""
|
|
TRY_PRINT.run('Open Shell...', install_open_shell)
|
|
|
|
|
|
def auto_install_software_bundle() -> None:
|
|
"""Install standard software bundle."""
|
|
TRY_PRINT.run('Software Bundle...', winget_import, group_name='default')
|
|
|
|
|
|
def auto_install_software_upgrades() -> None:
|
|
"""Upgrade all supported installed software."""
|
|
TRY_PRINT.run('Software Upgrades...', winget_upgrade)
|
|
|
|
|
|
def auto_install_vcredists() -> None:
|
|
"""Install latest supported Visual C++ runtimes."""
|
|
TRY_PRINT.run('Visual C++ Runtimes...', winget_import, group_name='vcredists')
|
|
|
|
|
|
def auto_install_winget() -> None:
|
|
"""Install winget if needed."""
|
|
TRY_PRINT.run('Winget...', winget_check, raise_exceptions=True)
|
|
|
|
|
|
def auto_open_device_manager() -> None:
|
|
"""Open Device Manager."""
|
|
TRY_PRINT.run('Device Manager...', open_device_manager)
|
|
|
|
|
|
def auto_open_hwinfo_sensors() -> None:
|
|
"""Open HWiNFO Sensors."""
|
|
TRY_PRINT.run('HWiNFO Sensors...', open_hwinfo_sensors)
|
|
|
|
|
|
def auto_open_microsoft_store_updates() -> None:
|
|
"""Opem Microsoft Store Updates."""
|
|
TRY_PRINT.run('Microsoft Store Updates...', open_microsoft_store_updates)
|
|
|
|
|
|
def auto_open_snappy_driver_installer_origin() -> None:
|
|
"""Open Snappy Driver Installer Origin."""
|
|
TRY_PRINT.run('Snappy Driver Installer...', open_snappy_driver_installer_origin)
|
|
|
|
|
|
def auto_open_windows_activation() -> None:
|
|
"""Open Windows Activation."""
|
|
if not is_activated():
|
|
TRY_PRINT.run('Windows Activation...', open_windows_activation)
|
|
|
|
|
|
def auto_open_windows_updates() -> None:
|
|
"""Open Windows Updates."""
|
|
TRY_PRINT.run('Windows Updates...', open_windows_updates)
|
|
|
|
|
|
def auto_open_xmplay() -> None:
|
|
"""Open XMPlay."""
|
|
TRY_PRINT.run('XMPlay...', open_xmplay)
|
|
|
|
|
|
def auto_show_4k_alignment_check() -> None:
|
|
"""Display 4K alignment check."""
|
|
TRY_PRINT.run('4K alignment Check...', check_4k_alignment, show_alert=True)
|
|
|
|
|
|
def auto_show_installed_antivirus() -> None:
|
|
"""Display installed antivirus."""
|
|
TRY_PRINT.run('Virus Protection...', get_installed_antivirus)
|
|
|
|
|
|
def auto_show_installed_ram() -> None:
|
|
"""Display installed RAM."""
|
|
TRY_PRINT.run('Installed RAM...', get_installed_ram,
|
|
as_list=True, raise_exceptions=True,
|
|
)
|
|
|
|
|
|
def auto_show_os_activation() -> None:
|
|
"""Display OS activation status."""
|
|
TRY_PRINT.run('Activation...', get_os_activation, as_list=True)
|
|
|
|
|
|
def auto_show_os_name() -> None:
|
|
"""Display OS Name."""
|
|
TRY_PRINT.run('Operating System...', get_os_name, as_list=True)
|
|
|
|
|
|
def auto_show_secure_boot_status() -> None:
|
|
"""Display Secure Boot status."""
|
|
TRY_PRINT.run(
|
|
'Secure Boot...', check_secure_boot_status, msg_good='Enabled',
|
|
)
|
|
|
|
|
|
def auto_show_storage_status() -> None:
|
|
"""Display storage status."""
|
|
TRY_PRINT.run('Storage Status...', get_storage_status)
|
|
|
|
|
|
def auto_windows_temp_fix() -> None:
|
|
"""Restore default ACLs for Windows\\Temp."""
|
|
TRY_PRINT.run(r'Windows\Temp fix...', fix_windows_temp)
|
|
|
|
|
|
# Configure Functions
|
|
def config_explorer() -> None:
|
|
"""Configure Windows Explorer and restart the process."""
|
|
reg_write_settings(REG_WINDOWS_EXPLORER)
|
|
kill_procs('explorer.exe', force=True)
|
|
popen_program(['explorer.exe'])
|
|
|
|
|
|
def config_open_shell() -> None:
|
|
"""Configure Open Shell."""
|
|
has_low_power_idle = False
|
|
|
|
# Check if the system supports S0 Low Power Idle
|
|
proc = run_program(['powercfg', '/AvailableSleepStates'], check=False)
|
|
for line in proc.stdout.splitlines():
|
|
if 'Low Power Idle' in line:
|
|
# NOTE: This is safe since we break before the listed unsupported states
|
|
has_low_power_idle = True
|
|
break
|
|
if line.startswith('The following sleep states are not available'):
|
|
break
|
|
|
|
# Apply registry settings
|
|
reg_write_settings(REG_OPEN_SHELL_SETTINGS)
|
|
if has_low_power_idle:
|
|
reg_write_settings(REG_OPEN_SHELL_LOW_POWER_IDLE)
|
|
|
|
|
|
def disable_chrome_notifications() -> None:
|
|
"""Disable notifications in Google Chrome."""
|
|
defaults_key = 'default_content_setting_values'
|
|
profiles = []
|
|
try:
|
|
search_path = case_insensitive_path(
|
|
f'{os.environ.get("LOCALAPPDATA")}/Google/Chrome/User Data',
|
|
)
|
|
except FileNotFoundError as err:
|
|
raise GenericWarning('No profiles detected.') from err
|
|
|
|
# Close any running instances of Chrome
|
|
kill_procs('chrome.exe', force=True)
|
|
|
|
# Build list of profiles
|
|
for item in search_path.iterdir():
|
|
if not item.is_dir():
|
|
continue
|
|
|
|
if re.match(r'^(Default|Profile).*', item.name, re.IGNORECASE):
|
|
profiles.append(item)
|
|
|
|
# Bail if no profiles were detected
|
|
if not profiles:
|
|
raise GenericWarning('No profiles detected.')
|
|
|
|
# Set notifications preference
|
|
for profile in profiles:
|
|
pref_file = profile.joinpath('Preferences')
|
|
if not pref_file.exists():
|
|
continue
|
|
|
|
# Update config
|
|
pref_data = json.loads(pref_file.read_text())
|
|
if defaults_key not in pref_data['profile']:
|
|
pref_data['profile'][defaults_key] = {}
|
|
pref_data['profile'][defaults_key]['notifications'] = 2
|
|
|
|
# Save file
|
|
pref_file.write_text(json.dumps(pref_data, separators=(',', ':')))
|
|
|
|
|
|
def enable_bsod_minidumps() -> None:
|
|
"""Enable saving minidumps during BSoDs."""
|
|
cmd = ['wmic', 'RECOVEROS', 'set', 'DebugInfoType', '=', '3']
|
|
run_program(cmd)
|
|
|
|
|
|
def enable_ublock_origin() -> None:
|
|
"""Enable uBlock Origin in supported browsers."""
|
|
base_paths = [
|
|
PROGRAMFILES_64, PROGRAMFILES_32, os.environ.get('LOCALAPPDATA'),
|
|
]
|
|
cmds = []
|
|
|
|
# Add Google Chrome registry entries
|
|
reg_write_settings(REG_CHROME_UBLOCK_ORIGIN)
|
|
|
|
# Build cmds list
|
|
for browser, rel_path in BROWSER_PATHS.items():
|
|
browser_path = None
|
|
for base_path in base_paths:
|
|
try:
|
|
browser_path = case_insensitive_path(f'{base_path}/{rel_path}')
|
|
except FileNotFoundError:
|
|
# Ignore missing browsers
|
|
continue
|
|
else:
|
|
# Found a match, skip checking the rest
|
|
break
|
|
if browser_path:
|
|
cmds.append([browser_path, UBLOCK_ORIGIN_URLS[browser]])
|
|
|
|
# Open detected browsers
|
|
for cmd in cmds:
|
|
popen_program(cmd, pipe=True)
|
|
|
|
|
|
def fix_windows_temp() -> None:
|
|
"""Restore default permissions for Windows\\Temp."""
|
|
permissions = (
|
|
'Users:(CI)(X,WD,AD)',
|
|
'Administrators:(OI)(CI)(F)',
|
|
)
|
|
for _p in permissions:
|
|
cmd = ['icacls', fr'{SYSTEMDRIVE}\Windows\Temp', '/grant:r', _p, '/T']
|
|
run_program(cmd)
|
|
|
|
|
|
# Install Functions
|
|
def install_firefox() -> None:
|
|
"""Install Firefox.
|
|
|
|
As far as I can tell if you use the EXE installers then it will use
|
|
the same installation directory as the installed version. As such a
|
|
32-bit installation could be upgraded to a 64-bit one and still use
|
|
%PROGRAMFILES(X86)% However, if a 64-bit MSI installer is used then
|
|
it ignores the 32-bit directory and just installs a new copy to
|
|
%PROGRAMFILES% resulting in two copies of Firefox to be in place.
|
|
To address this issue this function will uninstall all copies of
|
|
Firefox under 64-bit Windows if it's detected in %PROGRAMFILES(X86)%
|
|
before installing the latest 64-bit version.
|
|
|
|
Firefox 67 changed how profiles are named to avoid reusing the same
|
|
profile between different channels of Firefox (std, beta, ESR, etc).
|
|
However the logic used when upgrading from versions before 67 to
|
|
current isn't ideal. It can, but doesn't always?, create a new
|
|
profile and set it as default; even if there's an existing profile
|
|
being used. To address this profiles.ini is read to compare with the
|
|
post-install/upgrade state. If the default is changed to a new
|
|
profile then it is reverted so the original existing profile instead.
|
|
"""
|
|
current_default_profile = None
|
|
encoding = None
|
|
firefox_exe = get_path_obj(
|
|
f'{os.environ["PROGRAMFILES"]}/Mozilla Firefox/firefox.exe',
|
|
)
|
|
profiles_ini = get_path_obj(
|
|
f'{os.environ["APPDATA"]}/Mozilla/Firefox/profiles.ini',
|
|
)
|
|
program_path_32bit_exists = False
|
|
try:
|
|
case_insensitive_path(
|
|
f'{PROGRAMFILES_32}/Mozilla Firefox/firefox.exe',
|
|
)
|
|
except FileNotFoundError:
|
|
# Ignore
|
|
pass
|
|
else:
|
|
program_path_32bit_exists = True
|
|
revert_default = False
|
|
|
|
# Save current default profile
|
|
if profiles_ini.exists():
|
|
current_default_profile, encoding = get_firefox_default_profile(
|
|
profiles_ini,
|
|
)
|
|
|
|
# Uninstall Firefox if needed
|
|
if ARCH == '64' and program_path_32bit_exists:
|
|
uninstall_firefox()
|
|
|
|
# Install Firefox
|
|
run_tool('Firefox', 'Firefox', '/S', download=True)
|
|
|
|
# Open Firefox to force profiles.ini update
|
|
popen_program([firefox_exe])
|
|
sleep(5)
|
|
kill_procs('firefox.exe', force=True)
|
|
|
|
# Check if default profile changed
|
|
if current_default_profile:
|
|
new_default, encoding = get_firefox_default_profile(profiles_ini)
|
|
revert_default = new_default and new_default != current_default_profile
|
|
|
|
# Revert default profile if needed
|
|
if revert_default:
|
|
out = []
|
|
for line in profiles_ini.read_text(encoding=encoding).splitlines():
|
|
if 'Default=Profile' in line:
|
|
out.append(f'Default={current_default_profile}')
|
|
else:
|
|
out.append(line)
|
|
profiles_ini.write_text('\n'.join(out), encoding='utf-8')
|
|
|
|
|
|
def install_libreoffice(
|
|
register_mso_types=True, use_mso_formats=False, vcredist=True):
|
|
"""Install LibreOffice."""
|
|
installer = find_kit_dir('Installers').joinpath(f'LibreOffice{ARCH}.msi')
|
|
xcu_dir = get_path_obj(f'{os.environ.get("APPDATA")}/LibreOffice/4/user')
|
|
xcu_file = xcu_dir.joinpath('registrymodifications.xcu')
|
|
|
|
# Set default save formats to MSO types
|
|
if use_mso_formats and not xcu_file.exists():
|
|
xcu_dir.mkdir(parents=True, exist_ok=True)
|
|
with open(xcu_file, 'w', encoding='utf-8', newline='\n') as _f:
|
|
_f.write(LIBREOFFICE_XCU_DATA)
|
|
|
|
# Build cmd
|
|
cmd = [
|
|
'msiexec', '/passive', '/norestart',
|
|
'/i', installer,
|
|
'REBOOTYESNO=No',
|
|
f'VC_REDIST={1 if vcredist else 0}',
|
|
]
|
|
if register_mso_types:
|
|
cmd.append('REGISTER_ALL_MSO_TYPES=1')
|
|
else:
|
|
cmd.append('REGISTER_NO_MSO_TYPES=1')
|
|
|
|
# Install LibreOffice
|
|
run_program(cmd)
|
|
|
|
|
|
def install_open_shell() -> None:
|
|
"""Install Open Shell (just the Start Menu)."""
|
|
skin_zip = get_tool_path('OpenShell', 'Fluent-Metro', suffix='zip')
|
|
|
|
# Bail early
|
|
if OS_VERSION != 10:
|
|
raise GenericWarning('Unsupported OS')
|
|
|
|
# Install OpenShell
|
|
run_tool('OpenShell', 'OpenShell', '/qn', 'ADDLOCAL=StartMenu')
|
|
|
|
# Install Skin
|
|
extract_archive(skin_zip, f'{PROGRAMFILES_64}/Open-Shell/Skins', '-aoa')
|
|
|
|
# Add scheduled task to handle OS upgrades
|
|
cmd = ['schtasks', '/query', '/tn', 'Open-Shell OS upgrade check']
|
|
proc = run_program(cmd, check=False)
|
|
if proc.returncode == 0:
|
|
# Task already exists, bail and leave current task unmodified
|
|
return
|
|
cmd = [
|
|
'schtasks', '/create',
|
|
'/ru', r'NT AUTHORITY\SYSTEM',
|
|
'/sc', 'ONSTART',
|
|
'/tn', 'Open-Shell OS upgrade check',
|
|
'/tr', r'"%PROGRAMFILES%\Open-Shell\StartMenu.exe" -upgrade -silent',
|
|
]
|
|
run_program(cmd)
|
|
|
|
|
|
def uninstall_firefox() -> None:
|
|
"""Uninstall all copies of Firefox."""
|
|
json_file = format_log_path(log_name='Installed Programs', timestamp=True)
|
|
json_file = json_file.with_name(f'{json_file.stem}.json')
|
|
uninstall_data = None
|
|
|
|
# Get uninstall_data from UninstallView
|
|
extract_tool('UninstallView')
|
|
cmd = [get_tool_path('UninstallView', 'UninstallView'), '/sjson', json_file]
|
|
run_program(cmd)
|
|
with open(json_file, 'rb') as _f:
|
|
uninstall_data = json.load(_f)
|
|
|
|
# Uninstall Firefox if found
|
|
for item in uninstall_data:
|
|
if item['Display Name'].lower().startswith('mozilla firefox'):
|
|
uninstaller = item['Uninstall String'].replace('"', '')
|
|
run_program([uninstaller, '/S'])
|
|
|
|
|
|
# Misc Functions
|
|
def check_secure_boot_status() -> None:
|
|
"""Check Secure Boot status."""
|
|
is_secure_boot_enabled(raise_exceptions=True, show_alert=True)
|
|
|
|
|
|
def get_firefox_default_profile(profiles_ini) -> Any:
|
|
"""Get Firefox default profile, returns(pathlib.Path, encoding) or None."""
|
|
# TODO: Refactor to remove dependancy on Any
|
|
default_profile = None
|
|
encoding = None
|
|
parser = None
|
|
|
|
# Bail early
|
|
if not profiles_ini.exists():
|
|
return (default_profile, encoding)
|
|
|
|
# Determine encoding (if possible)
|
|
for enc in KNOWN_ENCODINGS:
|
|
try:
|
|
profiles_ini.read_text(encoding=enc)
|
|
except UnicodeError:
|
|
# Ignore and keep trying
|
|
pass
|
|
else:
|
|
encoding = enc
|
|
break
|
|
if not encoding:
|
|
raise UnicodeError('Failed to determine encoding of profiles.ini')
|
|
|
|
# Parse INI
|
|
parser = configparser.ConfigParser()
|
|
parser.read(profiles_ini, encoding=encoding)
|
|
for section in parser.sections():
|
|
if section.lower().startswith('install'):
|
|
default_profile = parser[section].get('default')
|
|
break
|
|
value = parser[section].get('default')
|
|
if value and value == '1':
|
|
default_profile = parser[section].get('path')
|
|
|
|
# Done
|
|
return (default_profile, encoding)
|
|
|
|
|
|
def get_storage_status() -> list[str]:
|
|
"""Get storage status for fixed disks, returns list."""
|
|
report = get_volume_usage(use_colors=True)
|
|
for disk in get_raw_disks():
|
|
report.append(ansi.color_string(f'Uninitialized Disk: {disk}', 'RED'))
|
|
|
|
# Done
|
|
return report
|
|
|
|
|
|
def set_default_browser() -> None:
|
|
"""Open Windows Settings to the default apps section."""
|
|
cmd = ['start', '', 'ms-settings:defaultapps']
|
|
popen_program(cmd, shell=True)
|
|
|
|
|
|
# Tool Functions
|
|
def export_aida64_report() -> None:
|
|
"""Export AIDA64 report."""
|
|
report_path = format_log_path(
|
|
log_name='AIDA64 System Report',
|
|
tool=True, timestamp=True,
|
|
)
|
|
report_path = report_path.with_suffix('.html')
|
|
report_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Run AIDA64 and check result
|
|
proc = run_tool(
|
|
'AIDA64', 'aida64',
|
|
'/R', report_path,
|
|
'/CUSTOM', 'basic.rpf',
|
|
'/HTML', '/SILENT', '/SAFEST',
|
|
cwd=True,
|
|
)
|
|
if proc.returncode:
|
|
raise GenericError('Error(s) encountered exporting report.')
|
|
|
|
|
|
def open_device_manager() -> None:
|
|
"""Open Device Manager."""
|
|
popen_program(['mmc', 'devmgmt.msc'])
|
|
|
|
|
|
def open_hwinfo_sensors() -> None:
|
|
"""Open HWiNFO sensors."""
|
|
hwinfo_path = get_tool_path('HWiNFO', 'HWiNFO')
|
|
base_config = hwinfo_path.with_name('general.ini')
|
|
|
|
# Write new config to disk
|
|
with open(hwinfo_path.with_suffix('.ini'), 'w', encoding='utf-8') as _f:
|
|
_f.write(
|
|
f'{base_config.read_text(encoding="utf-8")}\n'
|
|
'SensorsOnly=1\nSummaryOnly=0\n'
|
|
)
|
|
|
|
# Open HWiNFO
|
|
run_tool('HWiNFO', 'HWiNFO', popen=True)
|
|
|
|
|
|
def open_microsoft_store_updates() -> None:
|
|
"""Open Microsoft Store to the updates page."""
|
|
popen_program(['explorer', 'ms-windows-store:updates'])
|
|
|
|
|
|
def open_snappy_driver_installer_origin() -> None:
|
|
"""Open Snappy Driver Installer Origin."""
|
|
run_tool('SDIO', 'SDIO', cwd=True, pipe=True, popen=True)
|
|
|
|
|
|
def open_windows_activation() -> None:
|
|
"""Open Windows Activation."""
|
|
popen_program(['slui'])
|
|
|
|
|
|
def open_windows_updates() -> None:
|
|
"""Open Windows Updates."""
|
|
popen_program(['control', '/name', 'Microsoft.WindowsUpdate'])
|
|
|
|
|
|
def open_xmplay() -> None:
|
|
"""Open XMPlay."""
|
|
sleep(2)
|
|
run_tool('XMPlay', 'XMPlay', 'music.7z', cwd=True, popen=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print("This file is not meant to be called directly.")
|