493 lines
14 KiB
Python
493 lines
14 KiB
Python
"""WizardKit: Setup - Windows"""
|
|
# vim: sts=2 sw=2 ts=2
|
|
|
|
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
|
|
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 (
|
|
ARCH,
|
|
download_tool,
|
|
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 (
|
|
reg_delete_value,
|
|
reg_read_value,
|
|
reg_set_value,
|
|
reg_write_settings,
|
|
disable_service,
|
|
enable_service,
|
|
stop_service,
|
|
)
|
|
from wk.repairs.win import (
|
|
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,
|
|
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__)
|
|
CONEMU_EXE = get_tool_path('ConEmu', 'ConEmu', check=False)
|
|
IN_CONEMU = 'ConEmuPID' in os.environ
|
|
LIBREOFFICE_XCU_DATA = '''<?xml version="1.0" encoding="UTF-8"?>
|
|
<oor:items xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.presentation.PresentationDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>Impress MS PowerPoint 2007 XML</value></prop></item>
|
|
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.sheet.SpreadsheetDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>Calc MS Excel 2007 XML</value></prop></item>
|
|
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.text.TextDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>MS Word 2007 XML</value></prop></item>
|
|
<item oor:path="/org.openoffice.Office.Common/Save/Document"><prop oor:name="WarnAlienFormat" oor:op="fuse"><value>false</value></prop></item>
|
|
</oor:items>
|
|
'''
|
|
MENU_PRESETS = Menu()
|
|
OS_VERSION = float(platform.win32_ver()[0])
|
|
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',
|
|
),
|
|
)
|
|
REG_OPEN_SHELL_SETTINGS = {
|
|
'HKCU': {
|
|
r'Software\OpenShell\StartMenu': (
|
|
('ShowedStyle2', 1, 'DWORD'),
|
|
),
|
|
r'Software\OpenShell\StartMenu\Settings': (
|
|
('MenuStyle', 'Win7', 'SZ'),
|
|
('RecentPrograms', 'Recent', 'SZ'),
|
|
('SkinW7', 'Fluent-Metro', 'SZ'),
|
|
('SkinVariationW7', '', 'SZ'),
|
|
('SkipMetro', 1, 'DWORD'),
|
|
(
|
|
'SkinOptionsW7',
|
|
[
|
|
# NOTE: Seems to need all options specified to work?
|
|
'DARK_MAIN=1', 'METRO_MAIN=0', 'LIGHT_MAIN=0', 'AUTOMODE_MAIN=0',
|
|
'DARK_SUBMENU=0', 'METRO_SUBMENU=0', 'LIGHT_SUBMENU=0', 'AUTOMODE_SUBMENU=1',
|
|
'SUBMENU_SEPARATORS=1', 'DARK_SEARCH=0', 'METRO_SEARCH=0', 'LIGHT_SEARCH=1',
|
|
'AUTOMODE_SEARCH=0', 'SEARCH_FRAME=1', 'SEARCH_COLOR=0', 'SMALL_SEARCH=0',
|
|
'MODERN_SEARCH=1', 'SEARCH_ITALICS=0', 'NONE=0', 'SEPARATOR=0',
|
|
'TWO_TONE=1', 'CLASSIC_SELECTOR=1', 'HALF_SELECTOR=0', 'CURVED_MENUSEL=1',
|
|
'CURVED_SUBMENU=0', 'SELECTOR_REVEAL=0', 'TRANSPARENT=0', 'OPAQUE_SUBMENU=1',
|
|
'OPAQUE_MENU=0', 'OPAQUE=0', 'STANDARD=1', 'SMALL_MAIN2=0',
|
|
'SMALL_ICONS=0', 'COMPACT_SUBMENU=0', 'PRESERVE_MAIN2=0', 'LESS_PADDING=0',
|
|
'EXTRA_PADDING=1', '24_PADDING=0', 'LARGE_PROGRAMS=0', 'TRANSPARENT_SHUTDOWN=0',
|
|
'OUTLINE_SHUTDOWN=0', 'BUTTON_SHUTDOWN=1', 'EXPERIMENTAL_SHUTDOWN=0', 'LARGE_FONT=0',
|
|
'CONNECTED_BORDER=1', 'FLOATING_BORDER=0', 'LARGE_SUBMENU=0', 'LARGE_LISTS=0',
|
|
'THIN_MAIN2=0', 'EXPERIMENTAL_MAIN2=1', 'USER_IMAGE=1', 'USER_OUTSIDE=0',
|
|
'SCALING_USER=1', '56=0', '64=0', 'TRANSPARENT_USER=0',
|
|
'UWP_SCROLLBAR=1', 'MODERN_SCROLLBAR=0', 'OLD_ICONS=0', 'NEW_ICONS=1',
|
|
'SMALL_ARROWS=0', 'ICON_FRAME=0', 'SEARCH_SEPARATOR=0', 'NO_PROGRAMS_BUTTON=0',
|
|
],
|
|
'MULTI_SZ',
|
|
),
|
|
),
|
|
},
|
|
}
|
|
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 Setup
|
|
def build_menus(base_menus, title, presets):
|
|
"""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})
|
|
|
|
# Run groups
|
|
for group, entries in base_menus['Groups'].items():
|
|
menus[group] = Menu(title=f'{title}\n{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 Menu
|
|
MENU_PRESETS.title = f'{title}\n{color_string("Load Preset", "GREEN")}'
|
|
MENU_PRESETS.add_option('Default')
|
|
for name in presets:
|
|
MENU_PRESETS.add_option(name)
|
|
MENU_PRESETS.add_action('Main Menu')
|
|
MENU_PRESETS.add_action('Quit')
|
|
MENU_PRESETS.update()
|
|
|
|
# Done
|
|
return menus
|
|
|
|
|
|
def load_preset(menus, presets):
|
|
"""Load menu settings from preset."""
|
|
selection = MENU_PRESETS.simple_select()
|
|
|
|
# Exit early
|
|
if 'Main Menu' in selection:
|
|
return
|
|
if 'Quit' in selection:
|
|
raise SystemExit
|
|
|
|
# Default case
|
|
if 'Default' in selection:
|
|
for menu in menus.values():
|
|
for name in menu.options:
|
|
menu.options[name]['Selected'] = True
|
|
return
|
|
|
|
# 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
|
|
|
|
|
|
def run_auto_setup(base_menus, presets):
|
|
"""Run Auto Setup."""
|
|
update_log_path(dest_name='Auto Setup', timestamp=True)
|
|
title = f'{KIT_NAME_FULL}: Auto Setup'
|
|
clear_screen()
|
|
set_title(title)
|
|
print_info(title)
|
|
print('')
|
|
|
|
# Generate menus
|
|
print_standard('Initializing...')
|
|
menus = build_menus(base_menus, title, presets)
|
|
|
|
# Show Menu
|
|
show_main_menu(base_menus, menus, presets)
|
|
|
|
# Start setup
|
|
clear_screen()
|
|
print_standard(title)
|
|
print('')
|
|
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:
|
|
abort()
|
|
|
|
# Done
|
|
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)
|
|
|
|
# Not selected
|
|
if not details.get('Selected', False):
|
|
show_data(f'{name_str}...', 'Skipped', 'YELLOW', width=WIDTH)
|
|
continue
|
|
|
|
# Selected
|
|
details['Function']()
|
|
|
|
|
|
def show_main_menu(base_menus, menus, presets):
|
|
"""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)
|
|
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
|
|
|
|
# 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):
|
|
"""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():
|
|
"""Backup registry."""
|
|
TRY_PRINT.run('Backup Registry...', backup_registry)
|
|
|
|
|
|
def auto_backup_browser_profiles():
|
|
"""Backup browser profiles."""
|
|
backup_all_browser_profiles(use_try_print=True)
|
|
|
|
|
|
def auto_backup_power_plans():
|
|
"""Backup power plans."""
|
|
TRY_PRINT.run('Backup Power Plans...', export_power_plans)
|
|
|
|
|
|
def auto_reset_power_plans():
|
|
"""Reset power plans."""
|
|
TRY_PRINT.run('Reset Power Plans...', reset_power_plans)
|
|
|
|
|
|
def auto_set_custom_power_plan():
|
|
"""Set custom power plan."""
|
|
TRY_PRINT.run('Set Custom Power Plan...', create_custom_power_plan)
|
|
|
|
|
|
def auto_enable_regback():
|
|
"""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():
|
|
"""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():
|
|
"""Set System Restore size."""
|
|
TRY_PRINT.run('Set System Restore Size...', set_system_restore_size)
|
|
|
|
|
|
def auto_system_restore_create():
|
|
"""Create System Restore point."""
|
|
TRY_PRINT.run('Create System Restore...', create_system_restore_point)
|
|
|
|
|
|
def auto_windows_updates_enable():
|
|
"""Enable Windows Updates."""
|
|
TRY_PRINT.run('Enable Windows Updates...', enable_windows_updates)
|
|
|
|
|
|
# Auto Setup: Wrapper Functions
|
|
def auto_config_open_shell():
|
|
"""Configure Open Shell."""
|
|
TRY_PRINT.run('Open Shell...', reg_write_settings, REG_OPEN_SHELL_SETTINGS)
|
|
|
|
|
|
def auto_install_libreoffice():
|
|
"""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():
|
|
"""Install Open Shell."""
|
|
TRY_PRINT.run('Open Shell...', install_open_shell)
|
|
|
|
|
|
def auto_install_vcredists():
|
|
"""Install latest supported Visual C++ runtimes."""
|
|
TRY_PRINT.run('Visual C++ Runtimes...', install_vcredists)
|
|
|
|
|
|
# Configure Functions
|
|
# TODO?
|
|
|
|
|
|
# Install Functions
|
|
def install_libreoffice(
|
|
register_mso_types=True, use_mso_formats=False, vcredist=True):
|
|
"""Install LibreOffice."""
|
|
installer = find_kit_dir('Installers').joinpath('LibreOffice.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():
|
|
"""Install Open Shell (just the Start Menu)."""
|
|
installer = get_tool_path('OpenShell', 'OpenShell', check=False)
|
|
download_tool('OpenShell', 'OpenShell')
|
|
download_tool('OpenShell', 'Fluent-Metro', suffix='zip')
|
|
cmd = [installer, '/qn', 'ADDLOCAL=StartMenu']
|
|
run_program(cmd)
|
|
|
|
# Install Skin
|
|
skin_zip = installer.with_name('Fluent-Metro.zip')
|
|
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 install_vcredists():
|
|
"""Install latest supported Visual C++ runtimes."""
|
|
for year in (2012, 2013, 2019):
|
|
cmd_args = ['/install', '/passive', '/norestart']
|
|
if year == 2012:
|
|
cmd_args.pop(0)
|
|
name = f'VCRedist_{year}_x32'
|
|
download_tool('VCRedist', name)
|
|
installer = get_tool_path('VCRedist', name)
|
|
run_program([installer, *cmd_args])
|
|
if ARCH == '64':
|
|
name = f'{name[:-2]}64'
|
|
download_tool('VCRedist', name)
|
|
installer = get_tool_path('VCRedist', name)
|
|
run_program([installer, *cmd_args])
|
|
|
|
|
|
# Misc Functions
|
|
## TODO?
|
|
|
|
# Tool Functions
|
|
## TODO?
|
|
|
|
# OS Built-in Functions
|
|
## TODO?
|
|
|
|
if __name__ == '__main__':
|
|
print("This file is not meant to be called directly.")
|