From d51538aac3ebafe68f7d47386af42393e308237e Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 16:52:17 -0600
Subject: [PATCH 01/27] Fixed handling of known_networks
---
.bin/Scripts/add-known-networks | 11 ++++++++++-
Build Linux | 4 ++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/.bin/Scripts/add-known-networks b/.bin/Scripts/add-known-networks
index 6047c438..76316055 100644
--- a/.bin/Scripts/add-known-networks
+++ b/.bin/Scripts/add-known-networks
@@ -4,6 +4,7 @@
import os
import re
+import sys
import uuid
KNOWN_NETWORKS = '/root/known_networks'
@@ -34,13 +35,21 @@ method=auto
'''
def get_user_name():
- """Get real user name, returns str."""
+ """Get user name, returns str."""
user = None
+
+ # Get running user
if 'SUDO_USER' in os.environ:
user = os.environ.get('SUDO_USER')
else:
user = os.environ.get('USER')
+ # Check if user manually specified
+ for a in sys.argv:
+ a = a.strip().lower()
+ if a.startswith('--user='):
+ user = a.replace('--user=', '')
+
return user
if __name__ == '__main__':
diff --git a/Build Linux b/Build Linux
index 980c2e77..f9acd897 100755
--- a/Build Linux
+++ b/Build Linux
@@ -283,8 +283,8 @@ function update_live_env() {
fi
# WiFi
- cp "$ROOT_DIR/.linux_items/known_networks" "/root/known_networks"
- echo "add-known-networks" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
+ cp "$ROOT_DIR/.linux_items/known_networks" "$LIVE_DIR/airootfs/root/known_networks"
+ echo "add-known-networks --user=$username" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
}
function update_repo() {
From 617bb1484a2494ddf28d95b8c49dd08de0750937 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 16:54:40 -0600
Subject: [PATCH 02/27] Name connections by SSID
---
.bin/Scripts/add-known-networks | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/add-known-networks b/.bin/Scripts/add-known-networks
index 76316055..bdfc7a1c 100644
--- a/.bin/Scripts/add-known-networks
+++ b/.bin/Scripts/add-known-networks
@@ -63,7 +63,7 @@ if __name__ == '__main__':
for ssid, password in known_networks.items():
out_path = '{}/{}.nmconnection'.format(
'/etc/NetworkManager/system-connections',
- password,
+ ssid,
)
if not os.path.exists(out_path):
with open(out_path, 'w') as f:
From 798876eb10fc5ce62a0b8ea069f41eb08fc620c0 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 17:06:11 -0600
Subject: [PATCH 03/27] Updated UFD sections
---
.bin/Scripts/build-ufd | 21 +++++++++++----------
.bin/Scripts/functions/ufd.py | 20 ++++++++++++++------
2 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/.bin/Scripts/build-ufd b/.bin/Scripts/build-ufd
index bb0813a8..bd878a12 100755
--- a/.bin/Scripts/build-ufd
+++ b/.bin/Scripts/build-ufd
@@ -54,16 +54,8 @@ if __name__ == '__main__':
confirm_selections(args)
# Prep UFD
- print_info('Prep UFD')
- if args['--update']:
- # Remove arch folder
- try_and_print(
- indent=2,
- message='Removing Linux...',
- function=remove_arch,
- )
- else:
- # Format and partition
+ if not args['--update']:
+ print_info('Prep UFD')
prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr'])
# Mount UFD
@@ -73,8 +65,17 @@ if __name__ == '__main__':
function=mount,
mount_source=find_first_partition(ufd_dev),
mount_point='/mnt/UFD',
+ read_write=True,
)
+ # Remove Arch folder
+ if args['--update']:
+ try_and_print(
+ indent=2,
+ message='Removing Linux...',
+ function=remove_arch,
+ )
+
# Copy sources
print_standard(' ')
print_info('Copy Sources')
diff --git a/.bin/Scripts/functions/ufd.py b/.bin/Scripts/functions/ufd.py
index 598985d3..32f08201 100644
--- a/.bin/Scripts/functions/ufd.py
+++ b/.bin/Scripts/functions/ufd.py
@@ -60,16 +60,16 @@ def confirm_selections(args):
def copy_source(source, items, overwrite=False):
"""Copy source items to /mnt/UFD."""
- is_iso = source.name.lower().endswith('.iso')
+ is_image = source.is_file()
# Mount source if necessary
- if is_iso:
+ if is_image:
mount(source, '/mnt/Source')
# Copy items
for i_source, i_dest in items:
i_source = '{}{}'.format(
- '/mnt/Source' if is_iso else source,
+ '/mnt/Source' if is_image else source,
i_source,
)
i_dest = '/mnt/UFD{}'.format(i_dest)
@@ -80,7 +80,7 @@ def copy_source(source, items, overwrite=False):
pass
# Unmount source if necessary
- if is_iso:
+ if is_image:
unmount('/mnt/Source')
@@ -199,6 +199,8 @@ def is_valid_path(path_obj, path_type):
valid_path = path_obj.is_dir()
elif path_type == 'KIT':
valid_path = path_obj.is_dir() and path_obj.joinpath('.bin').exists()
+ elif path_type == 'IMG':
+ valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.img'
elif path_type == 'ISO':
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
elif path_type == 'UFD':
@@ -207,10 +209,16 @@ def is_valid_path(path_obj, path_type):
return valid_path
-def mount(mount_source, mount_point):
+def mount(mount_source, mount_point, read_write=False):
"""Mount mount_source on mount_point."""
os.makedirs(mount_point, exist_ok=True)
- cmd = ['mount', mount_source, mount_point]
+ cmd = [
+ 'mount',
+ mount_source,
+ mount_point,
+ '-o',
+ 'rw' if read_write else 'ro',
+ ]
run_program(cmd)
From b9276aa0d7a7c5fc440ff45a5c60515b579747b5 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 17:09:59 -0600
Subject: [PATCH 04/27] Updated build-ufd
---
.bin/Scripts/build-ufd | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/build-ufd b/.bin/Scripts/build-ufd
index bd878a12..45c3ff35 100755
--- a/.bin/Scripts/build-ufd
+++ b/.bin/Scripts/build-ufd
@@ -1,6 +1,6 @@
#!/bin/env python3
#
-# pylint: disable=no-name-in-module,wildcard-import
+# pylint: disable=no-name-in-module,wildcard-import,wrong-import-position
# vim: sts=2 sw=2 ts=2
"""Wizard Kit: UFD build tool"""
From 5ccd6282599f9109a6d47f8eab83ae7ba01c4da8 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 17:10:38 -0600
Subject: [PATCH 05/27] Updated check_disk.py
---
.bin/Scripts/check_disk.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/check_disk.py b/.bin/Scripts/check_disk.py
index 8919063e..fe18650b 100644
--- a/.bin/Scripts/check_disk.py
+++ b/.bin/Scripts/check_disk.py
@@ -38,7 +38,7 @@ if __name__ == '__main__':
if repair:
cs = 'Scheduled'
else:
- cs = 'CS'
+ cs = 'No issues'
message = 'CHKDSK ({SYSTEMDRIVE})...'.format(**global_vars['Env'])
try_and_print(message=message, function=run_chkdsk,
cs=cs, other_results=other_results, repair=repair)
From 7ccd4c605599fc74868fa385c4c2ada95d06d935 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:24:01 -0600
Subject: [PATCH 06/27] BITWISE operators =/= LOGICAL operators
---
.bin/Scripts/functions/browsers.py | 4 ++--
.bin/Scripts/functions/ddrescue.py | 13 +++++++------
.bin/Scripts/functions/hw_diags.py | 15 +++++++++------
3 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/.bin/Scripts/functions/browsers.py b/.bin/Scripts/functions/browsers.py
index dcb0ed2f..c4d110f7 100644
--- a/.bin/Scripts/functions/browsers.py
+++ b/.bin/Scripts/functions/browsers.py
@@ -32,8 +32,8 @@ def archive_all_users():
user_path = os.path.join(users_root, user_name)
appdata_local = os.path.join(user_path, r'AppData\Local')
appdata_roaming = os.path.join(user_path, r'AppData\Roaming')
- valid_user &= os.path.exists(appdata_local)
- valid_user &= os.path.exists(appdata_roaming)
+ valid_user = valid_user and os.path.exists(appdata_local)
+ valid_user = valid_user and os.path.exists(appdata_roaming)
if valid_user:
user_envs.append({
'USERNAME': user_name,
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index 545d08e0..4e6f7809 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -318,7 +318,7 @@ class RecoveryState():
"""Checks if pass is done for all block-pairs, returns bool."""
done = True
for bp in self.block_pairs:
- done &= bp.pass_done[self.current_pass]
+ done = done and bp.pass_done[self.current_pass]
return done
def current_pass_min(self):
@@ -376,7 +376,8 @@ class RecoveryState():
self.total_size = 0
for bp in self.block_pairs:
bp.self_check()
- self.resumed |= bp.resumed
+ if bp.resumed:
+ self.resumed = True
self.total_size += bp.size
def set_pass_num(self):
@@ -386,7 +387,7 @@ class RecoveryState():
# Iterate backwards through passes
pass_done = True
for bp in self.block_pairs:
- pass_done &= bp.pass_done[pass_num]
+ pass_done = pass_done and bp.pass_done[pass_num]
if pass_done:
# All block-pairs reported being done
# Set to next pass, unless we're on the last pass (2)
@@ -742,9 +743,9 @@ def is_writable_dir(dir_obj):
"""Check if we have read-write-execute permissions, returns bool."""
is_ok = True
path_st_mode = os.stat(dir_obj.path).st_mode
- is_ok == is_ok and path_st_mode & stat.S_IRUSR
- is_ok == is_ok and path_st_mode & stat.S_IWUSR
- is_ok == is_ok and path_st_mode & stat.S_IXUSR
+ is_ok = is_ok and path_st_mode & stat.S_IRUSR
+ is_ok = is_ok and path_st_mode & stat.S_IWUSR
+ is_ok = is_ok and path_st_mode & stat.S_IXUSR
return is_ok
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index bea5e01e..c085a5ed 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -186,8 +186,8 @@ class DiskObj():
disk_ok = False
# Disable override if necessary
- self.override_disabled |= ATTRIBUTES[attr_type][k].get(
- 'Critical', False)
+ if ATTRIBUTES[attr_type][k].get('Critical', False):
+ self.override_disabled = True
# SMART overall assessment
## NOTE: Only fail drives if the overall value exists and reports failed
@@ -788,7 +788,7 @@ def menu_diags(state, args):
# If so, verify no other tests are enabled and set quick_mode
state.quick_mode = True
for opt in main_options[3:4] + main_options[5:]:
- state.quick_mode &= not opt['Enabled']
+ state.quick_mode = state.quick_mode and not opt['Enabled']
else:
state.quick_mode = False
@@ -1001,7 +1001,8 @@ def run_hw_tests(state):
# Run disk safety checks (if necessary)
_disk_tests_enabled = False
for k in TESTS_DISK:
- _disk_tests_enabled |= state.tests[k]['Enabled']
+ if state.tests[k]['Enabled']:
+ _disk_tests_enabled = True
if _disk_tests_enabled:
for disk in state.disks:
try:
@@ -1611,7 +1612,8 @@ def show_results(state):
# CPU tests
_enabled = False
for k in TESTS_CPU:
- _enabled |= state.tests[k]['Enabled']
+ if state.tests[k]['Enabled']:
+ _enabled = True
if _enabled:
print_success('CPU:'.format(k))
show_report(state.cpu.generate_cpu_report(), log_report=True)
@@ -1620,7 +1622,8 @@ def show_results(state):
# Disk tests
_enabled = False
for k in TESTS_DISK:
- _enabled |= state.tests[k]['Enabled']
+ if state.tests[k]['Enabled']:
+ _enabled = True
if _enabled:
print_success('Disk{}:'.format(
'' if len(state.disks) == 1 else 's'))
From 35890d6bb31544ff379858f161ffd77a6496031b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:29:32 -0600
Subject: [PATCH 07/27] Fixed setting timezone in Windows
---
.bin/Scripts/functions/setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/functions/setup.py b/.bin/Scripts/functions/setup.py
index 05a9dca3..20c3eaf3 100644
--- a/.bin/Scripts/functions/setup.py
+++ b/.bin/Scripts/functions/setup.py
@@ -75,7 +75,7 @@ def config_windows_updates():
def update_clock():
"""Set Timezone and sync clock."""
- run_program(['tzutil' ,'/s', WINDOWS_TIME_ZONE], check=False)
+ run_program(['tzutil', '/s', WINDOWS_TIME_ZONE], check=False)
run_program(['net', 'stop', 'w32ime'], check=False)
run_program(
['w32tm', '/config', '/syncfromflags:manual',
From 214df527230a0e7c779481ec4fc597f58f755908 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:33:57 -0600
Subject: [PATCH 08/27] Expanded SW bundle sections
---
.bin/Scripts/functions/browsers.py | 31 ++++--
.bin/Scripts/functions/setup.py | 168 +++++++++++++++++++++++++----
.bin/Scripts/functions/update.py | 16 +++
.bin/Scripts/settings/sources.py | 25 +++--
.bin/Scripts/update_kit.py | 1 +
5 files changed, 205 insertions(+), 36 deletions(-)
diff --git a/.bin/Scripts/functions/browsers.py b/.bin/Scripts/functions/browsers.py
index c4d110f7..8a1f4dcb 100644
--- a/.bin/Scripts/functions/browsers.py
+++ b/.bin/Scripts/functions/browsers.py
@@ -325,7 +325,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
if just_firefox and browser_data[browser]['base'] != 'mozilla':
continue
exe_path = browser_data[browser].get('exe_path', None)
- function=run_program
if not exe_path:
if browser_data[browser]['profiles']:
print_standard(
@@ -375,7 +374,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
elif browser_data[browser]['base'] == 'ie':
urls.append(IE_GALLERY)
- function=popen_program
# By using check=False we're skipping any return codes so
# it should only fail if the program can't be run
@@ -384,10 +382,16 @@ def install_adblock(indent=8, width=32, just_firefox=False):
# installation status.
try_and_print(message='{}...'.format(browser),
indent=indent, width=width,
- cs='Done', function=function,
+ cs='Started', function=popen_program,
cmd=[exe_path, *urls], check=False)
+def is_installed(browser_name):
+ """Checks if browser is installed based on exe_path, returns bool."""
+ browser_name = browser_name.replace(' Chromium', '')
+ return bool(browser_data.get(browser_name, {}).get('exe_path', False))
+
+
def list_homepages(indent=8, width=32):
"""List current homepages for reference."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['exe_path']]
@@ -419,6 +423,12 @@ def list_homepages(indent=8, width=32):
indent=' '*indent, width=width, name=name, page=page))
+def profile_present(browser_name):
+ """Checks if a profile was detected for browser, returns bool."""
+ browser_name = browser_name.replace(' Chromium', '')
+ return bool(browser_data.get(browser_name, {}).get('profiles', False))
+
+
def reset_browsers(indent=8, width=32):
"""Reset all detected browsers to safe defaults."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['profiles']]
@@ -437,14 +447,21 @@ def reset_browsers(indent=8, width=32):
other_results=other_results, profile=profile)
-def scan_for_browsers(just_firefox=False):
+def scan_for_browsers(just_firefox=False, silent=False):
"""Scan system for any supported browsers."""
for name, details in sorted(SUPPORTED_BROWSERS.items()):
if just_firefox and details['base'] != 'mozilla':
continue
- try_and_print(message='{}...'.format(name),
- function=get_browser_details, cs='Detected',
- other_results=other_results, name=name)
+ if silent:
+ try:
+ get_browser_details(name)
+ except Exception:
+ # Ignore errors in silent mode
+ pass
+ else:
+ try_and_print(message='{}...'.format(name),
+ function=get_browser_details, cs='Detected',
+ other_results=other_results, name=name)
if __name__ == '__main__':
diff --git a/.bin/Scripts/functions/setup.py b/.bin/Scripts/functions/setup.py
index 20c3eaf3..ad575f75 100644
--- a/.bin/Scripts/functions/setup.py
+++ b/.bin/Scripts/functions/setup.py
@@ -1,7 +1,10 @@
# Wizard Kit: Functions - Setup
+from functions.browsers import *
+from functions.json import *
from functions.update import *
from settings.setup import *
+from settings.sources import *
# Configuration
@@ -63,9 +66,13 @@ def config_explorer_system():
write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True)
-def config_explorer_user():
- """Configure Windows Explorer for current user."""
- write_registry_settings(SETTINGS_EXPLORER_USER, all_users=False)
+def config_explorer_user(setup_mode='All'):
+ """Configure Windows Explorer for current user per setup_mode."""
+ settings_explorer_user = {
+ k: v for k, v in SETTINGS_EXPLORER_USER.items()
+ if setup_mode not in v.get('Invalid modes', [])
+ }
+ write_registry_settings(settings_explorer_user, all_users=False)
def config_windows_updates():
@@ -107,6 +114,39 @@ def write_registry_settings(settings, all_users=False):
# Installations
+def find_current_software():
+ """Find currently installed software, returns list."""
+ ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
+ installers = []
+
+ # Browsers
+ scan_for_browsers(silent=True)
+ for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
+ if is_installed(browser):
+ installers.append(
+ r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
+
+ # TODO: Add more sections
+
+ return installers
+
+def find_missing_software():
+ """Find missing software based on dirs/files present, returns list."""
+ ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
+ installers = []
+
+ # Browsers
+ scan_for_browsers(silent=True)
+ for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
+ if profile_present(browser):
+ installers.append(
+ r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
+
+ # TODO: Add more sections
+
+ return installers
+
+
def install_adobe_reader():
"""Install Adobe Reader."""
cmd = [
@@ -159,29 +199,115 @@ def install_firefox_extensions():
run_program(cmd)
-def install_ninite_bundle(mse=False, libreoffice=False):
+def install_libreoffice(
+ quickstart=True, register_mso_types=True,
+ use_mso_formats=False, vcredist=False):
+ """Install LibreOffice using specified settings."""
+ cmd = [
+ 'msiexec', '/passive', '/norestart',
+ '/i', r'{}\Installers\Extras\Office\LibreOffice.msi'.format(
+ global_vars['BaseDir']),
+ 'REBOOTYESNO=No',
+ 'ISCHECKFORPRODUCTUPDATES=0',
+ 'QUICKSTART={}'.format(1 if quickstart else 0),
+ 'UI_LANGS=en_US',
+ 'VC_REDIST={}'.format(1 if vcredist else 0),
+ ]
+ if register_mso_types:
+ cmd.append('REGISTER_ALL_MSO_TYPES=1')
+ else:
+ cmd.append('REGISTER_NO_MSO_TYPES=1')
+ xcu_dir = r'{APPDATA}\LibreOffice\4\user'.format(**global_vars['Env'])
+ xcu_file = r'{}\registrymodifications.xcu'.format(xcu_dir)
+
+ # Set default save format
+ if use_mso_formats and not os.path.exists(xcu_file):
+ os.makedirs(xcu_dir, exist_ok=True)
+ with open(xcu_file, 'w', encoding='utf-8', newline='\n') as f:
+ f.write(LIBREOFFICE_XCU_DATA)
+
+ # Install LibreOffice
+ run_program(cmd, check=True)
+
+def install_ninite_bundle(
+ # pylint: disable=too-many-arguments,too-many-branches
+ base=True,
+ browsers_only=False,
+ libreoffice=False,
+ missing=False,
+ mse=False,
+ standard=True,
+ ):
"""Run Ninite installer(s), returns list of Popen objects."""
popen_objects = []
- if global_vars['OS']['Version'] in ('8', '8.1', '10'):
- # Modern selection
- popen_objects.append(
- popen_program(r'{BaseDir}\Installers\Extras\Bundles\Modern.exe'.format(
- **global_vars)))
- else:
- # Legacy selection
- if mse:
- cmd = r'{BaseDir}\Installers\Extras\Security'.format(**global_vars)
- cmd += r'\Microsoft Security Essentials.exe'
- popen_objects.append(popen_program(cmd))
- popen_objects.append(
- popen_program(r'{BaseDir}\Installers\Extras\Bundles\Legacy.exe'.format(
- **global_vars)))
+ if browsers_only:
+ # This option is deprecated
+ installer_path = r'{BaseDir}\Installers\Extras\Web Browsers'.format(
+ **global_vars)
+ scan_for_browsers(silent=True)
+ for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
+ if is_installed(browser):
+ cmd = r'{}\{}.exe'.format(installer_path, browser)
+ popen_objects.append(popen_program(cmd))
+
+ # Bail
+ return popen_objects
+
+ # Main selections
+ main_selections = []
+ if base:
+ main_selections.append('base')
+ if standard:
+ if global_vars['OS']['Version'] in ('8', '8.1', '10'):
+ main_selections.append('standard')
+ else:
+ main_selections.append('standard7')
+ if main_selections:
+ # Only run if base and/or standard are enabled
+ cmd = r'{}\Installers\Extras\Bundles\{}.exe'.format(
+ global_vars['BaseDir'],
+ '-'.join(main_selections),
+ )
+ popen_objects.append(popen_program([cmd]))
+
+ # Extra selections
+ extra_selections = {}
+ for cmd in find_current_software():
+ extra_selections[cmd] = True
+ if missing:
+ for cmd in find_missing_software():
+ extra_selections[cmd] = True
+
+ # Remove overlapping selections
+ regex = []
+ for n_name, n_group in NINITE_REGEX.items():
+ if n_name in main_selections:
+ regex.extend(n_group)
+ regex = '({})'.format('|'.join(regex))
+ extra_selections = {
+ cmd: True for cmd in extra_selections
+ if not re.search(regex, cmd, re.IGNORECASE)
+ }
+
+ # Start extra selections
+ for cmd in extra_selections:
+ popen_objects.append(popen_program([cmd]))
+
+ # Microsoft Security Essentials
+ if mse:
+ cmd = r'{}\Installers\Extras\Security\{}'.format(
+ global_vars['BaseDir'],
+ 'Microsoft Security Essentials.exe',
+ )
+ popen_objects.append(popen_program([cmd]))
# LibreOffice
if libreoffice:
- cmd = r'{BaseDir}\Installers\Extras\Office'.format(**global_vars)
- cmd += r'\LibreOffice.exe'
- popen_objects.append(popen_program(cmd))
+ cmd = r'{}\Installers\Extras\Office\{}'.format(
+ global_vars['BaseDir'],
+ 'LibreOffice.exe',
+ )
+ popen_objects.append(popen_program([cmd]))
# Done
return popen_objects
diff --git a/.bin/Scripts/functions/update.py b/.bin/Scripts/functions/update.py
index 40bc3a27..38f881d5 100755
--- a/.bin/Scripts/functions/update.py
+++ b/.bin/Scripts/functions/update.py
@@ -615,6 +615,22 @@ def update_adobe_reader_dc():
dest, 'Adobe Reader DC.exe', SOURCE_URLS['Adobe Reader DC'])
+def update_libreoffice():
+ # Prep
+ dest = r'{}\Installers\Extras\Office'.format(
+ global_vars['BaseDir'])
+
+ # Remove existing installer
+ try:
+ os.remove(r'{}\LibreOffice.msi'.format(dest))
+ except FileNotFoundError:
+ pass
+
+ # Download
+ download_generic(
+ dest, 'LibreOffice.msi', SOURCE_URLS['LibreOffice'])
+
+
def update_macs_fan_control():
# Prep
dest = r'{}\Installers'.format(
diff --git a/.bin/Scripts/settings/sources.py b/.bin/Scripts/settings/sources.py
index e6cf5c3a..2abf079f 100644
--- a/.bin/Scripts/settings/sources.py
+++ b/.bin/Scripts/settings/sources.py
@@ -1,4 +1,6 @@
-# Wizard Kit: Settings - Sources
+'''Wizard Kit: Settings - Sources'''
+# pylint: disable=line-too-long
+# vim: sts=2 sw=2 ts=2 tw=0
SOURCE_URLS = {
'Adobe Reader DC': 'http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1901020098/AcroRdrDC1901020098_en_US.exe',
@@ -15,7 +17,7 @@ SOURCE_URLS = {
'ERUNT': 'http://www.aumha.org/downloads/erunt.zip',
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.935.x86.en-US.zip',
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.935.x64.en-US.zip',
- 'FastCopy': 'http://ftp.vector.co.jp/71/31/2323/FastCopy363_installer.exe',
+ 'FastCopy': 'https://fastcopy.jp/archive/FastCopy380_installer.exe',
'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/1709472/ublock_origin-1.18.6-an+fx.xpi',
'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
@@ -23,6 +25,7 @@ SOURCE_URLS = {
'Intel SSD Toolbox': r'https://downloadmirror.intel.com/28593/eng/Intel%20SSD%20Toolbox%20-%20v3.5.9.exe',
'IOBit_Uninstaller': r'https://portableapps.com/redirect/?a=IObitUninstallerPortable&s=s&d=pa&f=IObitUninstallerPortable_7.5.0.7.paf.exe',
'KVRT': 'http://devbuilds.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe',
+ 'LibreOffice': 'https://download.documentfoundation.org/libreoffice/stable/6.2.4/win/x86_64/LibreOffice_6.2.4_Win_x64.msi',
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip',
'NirCmd64': 'https://www.nirsoft.net/utils/nircmd-x64.zip',
@@ -37,8 +40,8 @@ SOURCE_URLS = {
'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent',
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip',
- 'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-i686-bin.zip',
- 'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-x86_64-bin.zip',
+ 'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-i686-bin.zip',
+ 'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-x86_64-bin.zip',
'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip',
'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.zip',
'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962',
@@ -66,10 +69,18 @@ VCREDIST_SOURCES = {
'64': 'https://aka.ms/vs/15/release/vc_redist.x64.exe',
},
}
+NINITE_REGEX = {
+ 'base': ['7-Zip', 'VLC'],
+ 'standard': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
+ 'standard7': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
+ }
NINITE_SOURCES = {
'Bundles': {
- 'Legacy.exe': '.net4.7.2-7zip-chrome-firefox-vlc',
- 'Modern.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-vlc',
+ 'base.exe': '.net4.7.2-7zip-vlc',
+ 'base-standard.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-sumatrapdf-vlc',
+ 'base-standard7.exe': '.net4.7.2-7zip-chrome-firefox-sumatrapdf-vlc',
+ 'standard.exe': 'chrome-classicstart-firefox-sumatrapdf',
+ 'standard7.exe': 'chrome-firefox-sumatrapdf',
},
'Audio-Video': {
'AIMP.exe': 'aimp',
@@ -216,5 +227,3 @@ WINDOWS_UPDATE_SOURCES = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2 tw=0
diff --git a/.bin/Scripts/update_kit.py b/.bin/Scripts/update_kit.py
index 133fe744..77f527aa 100644
--- a/.bin/Scripts/update_kit.py
+++ b/.bin/Scripts/update_kit.py
@@ -57,6 +57,7 @@ if __name__ == '__main__':
# Installers
print_info(' Installers')
try_and_print(message='Adobe Reader DC...', function=update_adobe_reader_dc, other_results=other_results, width=40)
+ try_and_print(message='LibreOffice...', function=update_libreoffice, other_results=other_results, width=40)
try_and_print(message='Macs Fan Control...', function=update_macs_fan_control, other_results=other_results, width=40)
try_and_print(message='MS Office...', function=update_office, other_results=other_results, width=40)
try_and_print(message='Visual C++ Runtimes...', function=update_vcredists, other_results=other_results, width=40)
From 7816602685b1c36c4ee3aae9fedef47867f87571 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:40:42 -0600
Subject: [PATCH 09/27] 4K Alignment checks
---
.bin/Scripts/functions/common.py | 7 +++++--
.bin/Scripts/functions/hw_diags.py | 26 +++++++++++++++++++++++++-
.bin/Scripts/functions/sw_diags.py | 29 +++++++++++++++++++++++++++++
3 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 28834660..9db75814 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -64,10 +64,13 @@ class GenericRepair(Exception):
class MultipleInstallationsError(Exception):
pass
-class NotInstalledError(Exception):
+class NoProfilesError(Exception):
pass
-class NoProfilesError(Exception):
+class Not4KAlignedError(Exception):
+ pass
+
+class NotInstalledError(Exception):
pass
class OSInstalledLegacyError(Exception):
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index c085a5ed..36be4a30 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -307,6 +307,11 @@ class DiskObj():
attr_type=self.attr_type, **COLORS))
report.extend(sorted(self.nvme_smart_notes.keys()))
+ # 4K alignment check
+ if not self.is_4k_aligned():
+ report.append('{YELLOW}Warning{CLEAR}'.format(**COLORS))
+ report.append(' One or more partitions are not 4K aligned')
+
# Tests
for test in self.tests.values():
report.extend(test.report)
@@ -410,6 +415,26 @@ class DiskObj():
'self_test', {}).get(
k, {})
+ def is_4k_aligned(self):
+ """Check if partitions are 4K aligned, returns bool."""
+ cmd = [
+ 'sudo',
+ 'sfdisk',
+ '--json',
+ self.path,
+ ]
+ aligned = True
+
+ # Get partition details
+ json_data = get_json_from_command(cmd)
+
+ # Check partitions
+ for part in json_data.get('partitiontable', {}).get('partitions', []):
+ aligned = aligned and part.get('start', -1) % 4096 == 0
+
+ # Done
+ return aligned
+
def safety_check(self, silent=False):
"""Run safety checks and disable tests if necessary."""
test_running = False
@@ -454,7 +479,6 @@ class DiskObj():
disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?')
print_standard(' ')
-
# Disable tests if necessary (statuses won't be overwritten)
if test_running:
if not silent:
diff --git a/.bin/Scripts/functions/sw_diags.py b/.bin/Scripts/functions/sw_diags.py
index 1b965766..b122528a 100644
--- a/.bin/Scripts/functions/sw_diags.py
+++ b/.bin/Scripts/functions/sw_diags.py
@@ -6,6 +6,35 @@ from functions.common import *
from settings.sw_diags import *
+def check_4k_alignment(show_alert=False):
+ """Check that all partitions are 4K aligned."""
+ aligned = True
+ cmd = ['WMIC', 'partition', 'get', 'StartingOffset']
+ offsets = []
+
+ # Get offsets
+ result = run_program(cmd, encoding='utf-8', errors='ignore', check=False)
+ offsets = result.stdout.splitlines()
+
+ # Check offsets
+ for off in offsets:
+ off = off.strip()
+ if not off.isnumeric():
+ # Skip
+ continue
+
+ try:
+ aligned = aligned and int(off) % 4096 == 0
+ except ValueError:
+ # Ignore, this check is low priority
+ pass
+
+ # Show alert
+ if show_alert:
+ show_alert_box('One or more partitions are not 4K aligned')
+ raise Not4KAlignedError
+
+
def check_connection():
"""Check if the system is online and optionally abort the script."""
while True:
From 2d3ccac369e214d787a11d4b71f45157c6e98c08 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:46:35 -0600
Subject: [PATCH 10/27] Updated convert_to_bytes and human_readable_size
---
.bin/Scripts/functions/common.py | 52 ++++++++++++++++++--------------
1 file changed, 30 insertions(+), 22 deletions(-)
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 9db75814..99b7b662 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -167,18 +167,22 @@ def clear_screen():
def convert_to_bytes(size):
"""Convert human-readable size str to bytes and return an int."""
size = str(size)
- tmp = re.search(r'(\d+\.?\d*)\s+([KMGT]B)', size.upper())
+ tmp = re.search(r'(\d+\.?\d*)\s+([PTGMKB])B?', size.upper())
if tmp:
size = float(tmp.group(1))
units = tmp.group(2)
- if units == 'TB':
- size *= 1099511627776
- elif units == 'GB':
- size *= 1073741824
- elif units == 'MB':
- size *= 1048576
- elif units == 'KB':
- size *= 1024
+ if units == 'P':
+ size *= 1024 ** 5
+ if units == 'T':
+ size *= 1024 ** 4
+ elif units == 'G':
+ size *= 1024 ** 3
+ elif units == 'M':
+ size *= 1024 ** 2
+ elif units == 'K':
+ size *= 1024 ** 1
+ elif units == 'B':
+ size *= 1024 ** 0
size = int(size)
else:
return -1
@@ -297,20 +301,24 @@ def human_readable_size(size, decimals=0):
return '{size:>{width}} b'.format(size='???', width=width)
# Convert to sensible units
- if size >= 1099511627776:
- size /= 1099511627776
- units = 'Tb'
- elif size >= 1073741824:
- size /= 1073741824
- units = 'Gb'
- elif size >= 1048576:
- size /= 1048576
- units = 'Mb'
- elif size >= 1024:
- size /= 1024
- units = 'Kb'
+ if size >= 1024 ** 5:
+ size /= 1024 ** 5
+ units = 'PB'
+ elif size >= 1024 ** 4:
+ size /= 1024 ** 4
+ units = 'TB'
+ elif size >= 1024 ** 3:
+ size /= 1024 ** 3
+ units = 'GB'
+ elif size >= 1024 ** 2:
+ size /= 1024 ** 2
+ units = 'MB'
+ elif size >= 1024 ** 1:
+ size /= 1024 ** 1
+ units = 'KB'
else:
- units = ' b'
+ size /= 1024 ** 0
+ units = ' B'
# Return
return '{size:>{width}.{decimals}f} {units}'.format(
From b83f2b0c5ff6a8f2e18bf5d6cde1c52662dbdc26 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:48:03 -0600
Subject: [PATCH 11/27] Updated pause()
---
.bin/Scripts/functions/common.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 99b7b662..2828bcb4 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -433,6 +433,8 @@ def non_clobber_rename(full_path):
def pause(prompt='Press Enter to continue... '):
"""Simple pause implementation."""
+ if prompt[-1] != ' ':
+ prompt += ' '
input(prompt)
From 576cb29281ed3e900f8a84b7aa3515d101a02e10 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:57:07 -0600
Subject: [PATCH 12/27] Updated settings.setup.py
---
.bin/Scripts/settings/setup.py | 30 ++++++++++++++++++++++--------
1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index 232e3a0d..dd875992 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -1,13 +1,19 @@
-# Wizard Kit: Settings - Setup
+'''Wizard Kit: Settings - Setup'''
+# pylint: disable=bad-continuation,line-too-long
+# vim: sts=2 sw=2 ts=2
import os
-import winreg
+try:
+ import winreg
+ HKU = winreg.HKEY_USERS
+ HKCR = winreg.HKEY_CLASSES_ROOT
+ HKCU = winreg.HKEY_CURRENT_USER
+ HKLM = winreg.HKEY_LOCAL_MACHINE
+except ImportError:
+ if os.name != 'posix':
+ raise
# General
-HKU = winreg.HKEY_USERS
-HKCR = winreg.HKEY_CLASSES_ROOT
-HKCU = winreg.HKEY_CURRENT_USER
-HKLM = winreg.HKEY_LOCAL_MACHINE
OTHER_RESULTS = {
'Error': {
'CalledProcessError': 'Unknown Error',
@@ -92,6 +98,15 @@ SETTINGS_EXPLORER_SYSTEM = {
},
}
SETTINGS_EXPLORER_USER = {
+ # Desktop theme
+ r'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize': {
+ 'Invalid modes': ['Cur'],
+ 'DWORD Items': {
+ # <= v1809 default
+ 'AppsUseLightTheme': 1,
+ 'SystemUsesLightTheme': 0,
+ },
+ },
# Disable features
r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': {
'DWORD Items': {
@@ -104,6 +119,7 @@ SETTINGS_EXPLORER_USER = {
},
# File Explorer
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
+ 'Invalid modes': ['Cur'],
'DWORD Items': {
# Change default Explorer view to "Computer"
'LaunchTo': 1,
@@ -157,5 +173,3 @@ SETTINGS_WINDOWS_UPDATES = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
From 6a821de0b56c5b4fc8b1a0c10c56ba2a6a993273 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:58:24 -0600
Subject: [PATCH 13/27] Added LibreOffice settings
---
.bin/Scripts/settings/setup.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index dd875992..215f56e2 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -135,6 +135,16 @@ SETTINGS_EXPLORER_USER = {
},
}
+# LibreOffice
+LIBREOFFICE_XCU_DATA = '''
+
+- Impress MS PowerPoint 2007 XML
+- Calc MS Excel 2007 XML
+- MS Word 2007 XML
+- false
+
+'''
+
# Visual C++ Runtimes
VCR_REDISTS = [
{'Name': 'Visual C++ 2010 x32...',
From 434bb977655e145253f775b7cac90a99e9ba530a Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:01:21 -0600
Subject: [PATCH 14/27] Updated sw_diags.py
---
.bin/Scripts/functions/sw_diags.py | 62 ++++++++++++++++--------------
1 file changed, 34 insertions(+), 28 deletions(-)
diff --git a/.bin/Scripts/functions/sw_diags.py b/.bin/Scripts/functions/sw_diags.py
index b122528a..1c5b943f 100644
--- a/.bin/Scripts/functions/sw_diags.py
+++ b/.bin/Scripts/functions/sw_diags.py
@@ -48,6 +48,37 @@ def check_connection():
abort()
+def check_os_support_status():
+ """Check if current OS is supported."""
+ msg = ''
+ outdated = False
+ unsupported = False
+
+ # Check OS version/notes
+ os_info = global_vars['OS'].copy()
+ if os_info['Notes'] == 'unsupported':
+ msg = 'The installed version of Windows is no longer supported'
+ unsupported = True
+ elif os_info['Notes'] == 'preview build':
+ msg = 'Preview builds are not officially supported'
+ unsupported = True
+ elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
+ msg = 'The installed version of Windows is outdated'
+ outdated = True
+ if 'Preview' not in msg:
+ msg += '\n\nPlease consider upgrading before continuing setup.'
+
+ # Show alert
+ if outdated or unsupported:
+ show_alert_box(msg)
+
+ # Raise exception if necessary
+ if outdated:
+ raise WindowsOutdatedError
+ if unsupported:
+ raise WindowsUnsupportedError
+
+
def check_secure_boot_status(show_alert=False):
"""Checks UEFI Secure Boot status via PowerShell."""
boot_mode = get_boot_mode()
@@ -110,33 +141,6 @@ def get_boot_mode():
return type_str
-def os_is_unsupported(show_alert=False):
- """Checks if the current OS is unsupported, returns bool."""
- msg = ''
- unsupported = False
-
- # Check OS version/notes
- os_info = global_vars['OS'].copy()
- if os_info['Notes'] == 'unsupported':
- msg = 'The installed version of Windows is no longer supported'
- unsupported = True
- elif os_info['Notes'] == 'preview build':
- msg = 'Preview builds are not officially supported'
- unsupported = True
- elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
- msg = 'The installed version of Windows is outdated'
- unsupported = True
- if 'Preview' not in msg:
- msg += '\n\nPlease consider upgrading before continuing setup.'
-
- # Show alert
- if unsupported and show_alert:
- show_alert_box(msg)
-
- # Done
- return unsupported
-
-
def run_autoruns():
"""Run AutoRuns in the background with VirusTotal checks enabled."""
extract_item('Autoruns', filter='autoruns*', silent=True)
@@ -226,8 +230,10 @@ def run_rkill():
shutil.move(item.path, dest)
-def show_alert_box(message, title='Wizard Kit Warning'):
+def show_alert_box(message, title=None):
"""Show Windows alert box with message."""
+ if not title:
+ title = '{} Warning'.format(KIT_NAME_FULL)
message_box = ctypes.windll.user32.MessageBoxW
message_box(None, message, title, 0x00001030)
From 7a67e683085c36f403d7ae6adea7acd3e68624fe Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:07:37 -0600
Subject: [PATCH 15/27] Updated install_sw_bundle
---
.bin/Scripts/install_sw_bundle.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.bin/Scripts/install_sw_bundle.py b/.bin/Scripts/install_sw_bundle.py
index b536fd64..2979362e 100644
--- a/.bin/Scripts/install_sw_bundle.py
+++ b/.bin/Scripts/install_sw_bundle.py
@@ -25,7 +25,6 @@ if __name__ == '__main__':
'UnsupportedOSError': 'Unsupported OS',
}}
answer_extensions = ask('Install Extensions?')
- answer_adobe_reader = ask('Install Adobe Reader?')
answer_vcr = ask('Install Visual C++ Runtimes?')
answer_ninite = ask('Install Ninite Bundle?')
if answer_ninite and global_vars['OS']['Version'] in ['7']:
@@ -35,9 +34,6 @@ if __name__ == '__main__':
answer_mse = False
print_info('Installing Programs')
- if answer_adobe_reader:
- try_and_print(message='Adobe Reader DC...',
- function=install_adobe_reader, other_results=other_results)
if answer_vcr:
install_vcredists()
if answer_ninite:
From c4ad9055d4a0b4f44a2c66e05d5d27f3e4ac79ce Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:14:36 -0600
Subject: [PATCH 16/27] Launch Explorer windows in separate process
---
.bin/Scripts/settings/setup.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index 215f56e2..c11073ec 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -125,6 +125,13 @@ SETTINGS_EXPLORER_USER = {
'LaunchTo': 1,
},
},
+ r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
+ # Dup path so it Will be applied to all modes
+ 'DWORD Items': {
+ # Launch Folder Windows in a Separate Process
+ 'SeparateProcess': 1,
+ },
+ },
# Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
'DWORD Items': {'PeopleBand': 0},
From 248e321438619899a65a999b7e17dfdac8b1530b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:16:25 -0600
Subject: [PATCH 17/27] Updated windows_builds
---
.bin/Scripts/settings/windows_builds.py | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/.bin/Scripts/settings/windows_builds.py b/.bin/Scripts/settings/windows_builds.py
index db538bf2..f7481294 100644
--- a/.bin/Scripts/settings/windows_builds.py
+++ b/.bin/Scripts/settings/windows_builds.py
@@ -1,8 +1,10 @@
-# Wizard Kit: Settings - Windows Builds
+'''Wizard Kit: Settings - Windows Builds'''
+# pylint: disable=bad-continuation,bad-whitespace
+# vim: sts=2 sw=2 ts=2
+## NOTE: Data from here: https://en.wikipedia.org/wiki/Windows_10_version_history
WINDOWS_BUILDS = {
# Build, Version, Release, Codename, Marketing Name, Notes
- '6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6001': ('Vista', 'SP1', 'Longhorn', None, 'unsupported'),
'6002': ('Vista', 'SP2', 'Longhorn', None, 'unsupported'),
@@ -202,15 +204,22 @@ WINDOWS_BUILDS = {
'18356': ('10', None, '19H1', None, 'preview build'),
'18358': ('10', None, '19H1', None, 'preview build'),
'18361': ('10', None, '19H1', None, 'preview build'),
+ '18362': ('10', 'v1903', '19H1', 'May 2019 Update', None),
'18836': ('10', None, '20H1', None, 'preview build'),
'18841': ('10', None, '20H1', None, 'preview build'),
'18845': ('10', None, '20H1', None, 'preview build'),
'18850': ('10', None, '20H1', None, 'preview build'),
'18855': ('10', None, '20H1', None, 'preview build'),
+ '18860': ('10', None, '20H1', None, 'preview build'),
+ '18865': ('10', None, '20H1', None, 'preview build'),
+ '18875': ('10', None, '20H1', None, 'preview build'),
+ '18885': ('10', None, '20H1', None, 'preview build'),
+ '18890': ('10', None, '20H1', None, 'preview build'),
+ '18894': ('10', None, '20H1', None, 'preview build'),
+ '18895': ('10', None, '20H1', None, 'preview build'),
+ '18898': ('10', None, '20H1', None, 'preview build'),
}
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
From 52e4415b438a8a82c9c61c0aa83d3f0386c7cb8b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:00:11 -0600
Subject: [PATCH 18/27] Updated ddrescue sections
---
.bin/Scripts/functions/ddrescue.py | 534 ++++++++++++++++-------------
.bin/Scripts/settings/ddrescue.py | 8 +
2 files changed, 303 insertions(+), 239 deletions(-)
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index 4e6f7809..463adf4d 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -1,25 +1,25 @@
-# Wizard Kit: Functions - ddrescue-tui
+# pylint: disable=no-name-in-module,too-many-lines,wildcard-import
+# vim: sts=2 sw=2 ts=2
+'''Wizard Kit: Functions - ddrescue-tui'''
import datetime
import pathlib
-import psutil
-import pytz
import re
-import signal
import stat
import time
+from operator import itemgetter
-from collections import OrderedDict
+import pytz
from functions.data import *
from functions.hw_diags import *
from functions.json import *
from functions.tmux import *
-from operator import itemgetter
from settings.ddrescue import *
# Clases
class BaseObj():
+ # pylint: disable=missing-docstring
"""Base object used by DevObj, DirObj, and ImageObj."""
def __init__(self, path):
self.type = 'base'
@@ -44,6 +44,7 @@ class BaseObj():
class BlockPair():
+ # pylint: disable=too-many-instance-attributes
"""Object to track data and methods together for source and dest."""
def __init__(self, mode, source, dest):
self.mode = mode
@@ -60,9 +61,10 @@ class BlockPair():
if self.mode == 'clone':
# Cloning
self.dest_path = dest.path
- self.map_path = '{pwd}/Clone_{prefix}.map'.format(
- pwd=os.path.realpath(global_vars['Env']['PWD']),
- prefix=source.prefix)
+ self.map_path = '{cwd}/Clone_{prefix}.map'.format(
+ cwd=os.path.realpath(os.getcwd()),
+ prefix=source.prefix,
+ )
else:
# Imaging
self.dest_path = '{path}/{prefix}.dd'.format(
@@ -105,19 +107,19 @@ class BlockPair():
def load_map_data(self):
"""Load data from map file and set progress."""
map_data = read_map_file(self.map_path)
- self.rescued_percent = map_data['rescued']
- self.rescued = (self.rescued_percent * self.size) / 100
+ self.rescued = map_data.get('rescued', 0)
+ self.rescued_percent = (self.rescued / self.size) * 100
if map_data['full recovery']:
self.pass_done = [True, True, True]
self.rescued = self.size
self.status = ['Skipped', 'Skipped', 'Skipped']
- elif map_data['non-tried'] > 0:
+ elif map_data.get('non-tried', 0) > 0:
# Initial pass incomplete
pass
- elif map_data['non-trimmed'] > 0:
+ elif map_data.get('non-trimmed', 0) > 0:
self.pass_done = [True, False, False]
self.status = ['Skipped', 'Pending', 'Pending']
- elif map_data['non-scraped'] > 0:
+ elif map_data.get('non-scraped', 0) > 0:
self.pass_done = [True, True, False]
self.status = ['Skipped', 'Skipped', 'Pending']
else:
@@ -145,14 +147,15 @@ class BlockPair():
"""Update progress using map file."""
if os.path.exists(self.map_path):
map_data = read_map_file(self.map_path)
- self.rescued_percent = map_data.get('rescued', 0)
- self.rescued = (self.rescued_percent * self.size) / 100
+ self.rescued = map_data.get('rescued', 0)
+ self.rescued_percent = (self.rescued / self.size) * 100
self.status[pass_num] = get_formatted_status(
label='Pass {}'.format(pass_num+1),
data=(self.rescued/self.size)*100)
class DevObj(BaseObj):
+ # pylint: disable=too-many-instance-attributes
"""Block device object."""
def self_check(self):
"""Verify that self.path points to a block device."""
@@ -186,6 +189,7 @@ class DevObj(BaseObj):
self.update_filename_prefix()
def update_filename_prefix(self):
+ # pylint: disable=attribute-defined-outside-init
"""Set filename prefix based on details."""
self.prefix = '{m_size}_{model}'.format(
m_size=self.model_size,
@@ -205,6 +209,7 @@ class DevObj(BaseObj):
class DirObj(BaseObj):
+ """Directory object."""
def self_check(self):
"""Verify that self.path points to a directory."""
if not pathlib.Path(self.path).is_dir():
@@ -222,6 +227,7 @@ class DirObj(BaseObj):
class ImageObj(BaseObj):
+ """Image file object."""
def self_check(self):
"""Verify that self.path points to a file."""
if not pathlib.Path(self.path).is_file():
@@ -243,10 +249,11 @@ class ImageObj(BaseObj):
self.report = get_device_report(self.loop_dev)
self.report = self.report.replace(
self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)')
- run_program(['losetup', '--detach', self.loop_dev], check=False)
+ run_program(['sudo', 'losetup', '--detach', self.loop_dev], check=False)
class RecoveryState():
+ # pylint: disable=too-many-instance-attributes
"""Object to track BlockPair objects and overall state."""
def __init__(self, mode, source, dest):
self.mode = mode.lower()
@@ -270,6 +277,7 @@ class RecoveryState():
if mode not in ('clone', 'image'):
raise GenericError('Unsupported mode')
self.get_smart_source()
+ self.set_working_dir()
def add_block_pair(self, source, dest):
"""Run safety checks and append new BlockPair to internal list."""
@@ -314,20 +322,134 @@ class RecoveryState():
# Safety checks passed
self.block_pairs.append(BlockPair(self.mode, source, dest))
+ def build_outer_panes(self):
+ """Build top and side panes."""
+ clear_screen()
+
+ # Top
+ self.panes['Source'] = tmux_split_window(
+ behind=True, vertical=True, lines=2,
+ text='{BLUE}Source{CLEAR}'.format(**COLORS))
+
+ # Started
+ self.panes['Started'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH, target_pane=self.panes['Source'],
+ text='{BLUE}Started{CLEAR}\n{s}'.format(
+ s=time.strftime("%Y-%m-%d %H:%M %Z"),
+ **COLORS))
+
+ # Destination
+ self.panes['Destination'] = tmux_split_window(
+ percent=50, target_pane=self.panes['Source'],
+ text='{BLUE}Destination{CLEAR}'.format(**COLORS))
+
+ # Progress
+ update_sidepane(self)
+ self.panes['Progress'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH, watch=self.progress_out)
+
def current_pass_done(self):
"""Checks if pass is done for all block-pairs, returns bool."""
done = True
- for bp in self.block_pairs:
- done = done and bp.pass_done[self.current_pass]
+ for b_pair in self.block_pairs:
+ done = done and b_pair.pass_done[self.current_pass]
return done
def current_pass_min(self):
"""Gets minimum pass rescued percentage, returns float."""
min_percent = 100
- for bp in self.block_pairs:
- min_percent = min(min_percent, bp.rescued_percent)
+ for b_pair in self.block_pairs:
+ min_percent = min(min_percent, b_pair.rescued_percent)
return min_percent
+ def fix_tmux_panes(self, forced=False):
+ # pylint: disable=too-many-branches,too-many-locals
+ """Fix pane sizes if the winodw has been resized."""
+ needs_fixed = False
+
+ # Check layout
+ for pane, pane_data in TMUX_LAYOUT.items():
+ if not pane_data.get('Check'):
+ # Not concerned with the size of this pane
+ continue
+ # Get target
+ target = None
+ if pane != 'Current':
+ if pane not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[pane]
+
+ # Check pane size
+ size_x, size_y = tmux_get_pane_size(pane_id=target)
+ if pane_data.get('x', False) and pane_data['x'] != size_x:
+ needs_fixed = True
+ if pane_data.get('y', False) and pane_data['y'] != size_y:
+ needs_fixed = True
+
+ # Bail?
+ if not needs_fixed and not forced:
+ return
+
+ # Remove Destination pane (temporarily)
+ tmux_kill_pane(self.panes['Destination'])
+
+ # Update layout
+ for pane, pane_data in TMUX_LAYOUT.items():
+ # Get target
+ target = None
+ if pane != 'Current':
+ if pane not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[pane]
+
+ # Resize pane
+ tmux_resize_pane(pane_id=target, **pane_data)
+
+ # Calc Source/Destination pane sizes
+ width, height = tmux_get_pane_size()
+ width = int(width / 2) - 1
+
+ # Update Source string
+ source_str = self.source.name
+ if len(source_str) > width:
+ source_str = '{}...'.format(source_str[:width-3])
+
+ # Update Destination string
+ dest_str = self.dest.name
+ if len(dest_str) > width:
+ if self.mode == 'clone':
+ dest_str = '{}...'.format(dest_str[:width-3])
+ else:
+ dest_str = '...{}'.format(dest_str[-width+3:])
+
+ # Rebuild Source/Destination panes
+ tmux_update_pane(
+ pane_id=self.panes['Source'],
+ text='{BLUE}Source{CLEAR}\n{s}'.format(
+ s=source_str, **COLORS))
+ self.panes['Destination'] = tmux_split_window(
+ percent=50, target_pane=self.panes['Source'],
+ text='{BLUE}Destination{CLEAR}\n{s}'.format(
+ s=dest_str, **COLORS))
+
+ if 'SMART' in self.panes:
+ # Calc SMART/ddrescue/Journal panes sizes
+ ratio = [12, 22, 4]
+ width, height = tmux_get_pane_size(pane_id=self.panes['Progress'])
+ height -= 2
+ total = sum(ratio)
+ p_ratio = [int((x/total) * height) for x in ratio]
+ p_ratio[1] = height - p_ratio[0] - p_ratio[2]
+
+ # Resize SMART/Journal panes
+ tmux_resize_pane(self.panes['SMART'], y=ratio[0])
+ tmux_resize_pane(y=ratio[1])
+ tmux_resize_pane(self.panes['Journal'], y=ratio[2])
+
def get_smart_source(self):
"""Get source for SMART dispay."""
disk_path = self.source.path
@@ -339,18 +461,15 @@ class RecoveryState():
def retry_all_passes(self):
"""Mark all passes as pending for all block-pairs."""
self.finished = False
- for bp in self.block_pairs:
- bp.pass_done = [False, False, False]
- bp.status = ['Pending', 'Pending', 'Pending']
- bp.fix_status_strings()
+ for b_pair in self.block_pairs:
+ b_pair.pass_done = [False, False, False]
+ b_pair.status = ['Pending', 'Pending', 'Pending']
+ b_pair.fix_status_strings()
self.set_pass_num()
def self_checks(self):
"""Run self-checks and update state values."""
cmd = ['findmnt', '--json', '--target', os.getcwd()]
- map_allowed_fstypes = RECOMMENDED_FSTYPES.copy()
- map_allowed_fstypes.extend(['cifs', 'ext2', 'vfat'])
- map_allowed_fstypes.sort()
json_data = get_json_from_command(cmd)
# Abort if json_data is empty
@@ -361,24 +480,24 @@ class RecoveryState():
# Avoid saving map to non-persistent filesystem
fstype = json_data.get(
'filesystems', [{}])[0].get(
- 'fstype', 'unknown')
- if fstype not in map_allowed_fstypes:
+ 'fstype', 'unknown')
+ if fstype not in RECOMMENDED_MAP_FSTYPES:
print_error(
"Map isn't being saved to a recommended filesystem ({})".format(
fstype.upper()))
print_info('Recommended types are: {}'.format(
- ' / '.join(map_allowed_fstypes).upper()))
+ ' / '.join(RECOMMENDED_MAP_FSTYPES).upper()))
print_standard(' ')
if not ask('Proceed anyways? (Strongly discouraged)'):
raise GenericAbort()
# Run BlockPair self checks and get total size
self.total_size = 0
- for bp in self.block_pairs:
- bp.self_check()
- if bp.resumed:
+ for b_pair in self.block_pairs:
+ b_pair.self_check()
+ if b_pair.resumed:
self.resumed = True
- self.total_size += bp.size
+ self.total_size += b_pair.size
def set_pass_num(self):
"""Set current pass based on all block-pair's progress."""
@@ -386,8 +505,8 @@ class RecoveryState():
for pass_num in (2, 1, 0):
# Iterate backwards through passes
pass_done = True
- for bp in self.block_pairs:
- pass_done = pass_done and bp.pass_done[pass_num]
+ for b_pair in self.block_pairs:
+ pass_done = pass_done and b_pair.pass_done[pass_num]
if pass_done:
# All block-pairs reported being done
# Set to next pass, unless we're on the last pass (2)
@@ -405,6 +524,34 @@ class RecoveryState():
elif self.current_pass == 2:
self.current_pass_str = '3 "Scraping bad areas"'
+ def set_working_dir(self):
+ # pylint: disable=no-self-use
+ """Set working dir to MAP_DIR if possible.
+
+ NOTE: This is to help ensure the map file
+ is saved to non-volatile storage."""
+ map_dir = '{}/{}'.format(MAP_DIR, global_vars['Date-Time'])
+
+ # Mount backup shares
+ mount_backup_shares(read_write=True)
+
+ # Get MAP_DIR filesystem type
+ # NOTE: If the backup share fails to mount then this will
+ # likely be the type of /
+ cmd = [
+ 'findmnt',
+ '--noheadings',
+ '--target', MAP_DIR,
+ '--output', 'FSTYPE',
+ ]
+ result = run_program(cmd, check=False, encoding='utf-8', errors='ingnore')
+ map_dir_type = result.stdout.strip().lower()
+
+ # Change working dir if map_dir_type is acceptable
+ if map_dir_type in RECOMMENDED_MAP_FSTYPES:
+ os.makedirs(map_dir, exist_ok=True)
+ os.chdir(map_dir)
+
def update_etoc(self):
"""Search ddrescue output for the current EToC, returns str."""
now = datetime.datetime.now(tz=self.timezone)
@@ -414,7 +561,7 @@ class RecoveryState():
# Just set to N/A (NOTE: this overrules the refresh rate below)
self.etoc = 'N/A'
return
- elif 'In Progress' not in self.status:
+ if 'In Progress' not in self.status:
# Don't update when EToC is hidden
return
if now.second % ETOC_REFRESH_RATE != 0:
@@ -428,13 +575,14 @@ class RecoveryState():
# Capture main tmux pane
try:
text = tmux_capture_pane()
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Ignore
pass
# Search for EToC delta
matches = re.findall(r'remaining time:.*$', text, re.MULTILINE)
if matches:
+ # pylint: disable=invalid-name
r = REGEX_REMAINING_TIME.search(matches[-1])
if r.group('na'):
self.etoc = 'N/A'
@@ -451,7 +599,7 @@ class RecoveryState():
minutes=int(minutes),
seconds=int(seconds),
)
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Ignore and leave as raw string
pass
@@ -461,15 +609,16 @@ class RecoveryState():
now = datetime.datetime.now(tz=self.timezone)
_etoc = now + etoc_delta
self.etoc = _etoc.strftime('%Y-%m-%d %H:%M %Z')
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Ignore and leave as current string
pass
def update_progress(self):
+ # pylint: disable=attribute-defined-outside-init
"""Update overall progress using block_pairs."""
self.rescued = 0
- for bp in self.block_pairs:
- self.rescued += bp.rescued
+ for b_pair in self.block_pairs:
+ self.rescued += b_pair.rescued
self.rescued_percent = (self.rescued / self.total_size) * 100
self.status_percent = get_formatted_status(
label='Recovered:', data=self.rescued_percent)
@@ -478,26 +627,6 @@ class RecoveryState():
# Functions
-def build_outer_panes(state):
- """Build top and side panes."""
- state.panes['Source'] = tmux_split_window(
- behind=True, vertical=True, lines=2,
- text='{BLUE}Source{CLEAR}'.format(**COLORS))
- state.panes['Started'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH, target_pane=state.panes['Source'],
- text='{BLUE}Started{CLEAR}\n{s}'.format(
- s=time.strftime("%Y-%m-%d %H:%M %Z"),
- **COLORS))
- state.panes['Destination'] = tmux_split_window(
- percent=50, target_pane=state.panes['Source'],
- text='{BLUE}Destination{CLEAR}'.format(**COLORS))
-
- # Side pane
- update_sidepane(state)
- state.panes['Progress'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH, watch=state.progress_out)
-
-
def create_path_obj(path):
"""Create Dev, Dir, or Image obj based on path given."""
obj = None
@@ -515,101 +644,16 @@ def create_path_obj(path):
def double_confirm_clone():
"""Display warning and get 2nd confirmation, returns bool."""
print_standard('\nSAFETY CHECK')
- print_warning('All data will be DELETED from the '
- 'destination device and partition(s) listed above.')
- print_warning('This is irreversible and will lead '
- 'to {CLEAR}{RED}DATA LOSS.'.format(**COLORS))
+ print_warning(
+ 'All data will be DELETED from the '
+ 'destination device and partition(s) listed above.'
+ )
+ print_warning(
+ 'This is irreversible and will lead to {CLEAR}{RED}DATA LOSS.'.format(
+ **COLORS))
return ask('Asking again to confirm, is this correct?')
-def fix_tmux_panes(state, forced=False):
- """Fix pane sizes if the winodw has been resized."""
- needs_fixed = False
-
- # Check layout
- for k, v in TMUX_LAYOUT.items():
- if not v.get('Check'):
- # Not concerned with the size of this pane
- continue
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Check pane size
- x, y = tmux_get_pane_size(pane_id=target)
- if v.get('x', False) and v['x'] != x:
- needs_fixed = True
- if v.get('y', False) and v['y'] != y:
- needs_fixed = True
-
- # Bail?
- if not needs_fixed and not forced:
- return
-
- # Remove Destination pane (temporarily)
- tmux_kill_pane(state.panes['Destination'])
-
- # Update layout
- for k, v in TMUX_LAYOUT.items():
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Resize pane
- tmux_resize_pane(pane_id=target, **v)
-
- # Calc Source/Destination pane sizes
- width, height = tmux_get_pane_size()
- width = int(width / 2) - 1
-
- # Update Source string
- source_str = state.source.name
- if len(source_str) > width:
- source_str = '{}...'.format(source_str[:width-3])
-
- # Update Destination string
- dest_str = state.dest.name
- if len(dest_str) > width:
- if state.mode == 'clone':
- dest_str = '{}...'.format(dest_str[:width-3])
- else:
- dest_str = '...{}'.format(dest_str[-width+3:])
-
- # Rebuild Source/Destination panes
- tmux_update_pane(
- pane_id=state.panes['Source'],
- text='{BLUE}Source{CLEAR}\n{s}'.format(
- s=source_str, **COLORS))
- state.panes['Destination'] = tmux_split_window(
- percent=50, target_pane=state.panes['Source'],
- text='{BLUE}Destination{CLEAR}\n{s}'.format(
- s=dest_str, **COLORS))
-
- if 'SMART' in state.panes:
- # Calc SMART/ddrescue/Journal panes sizes
- ratio = [12, 22, 4]
- width, height = tmux_get_pane_size(pane_id=state.panes['Progress'])
- height -= 2
- total = sum(ratio)
- p_ratio = [int((x/total) * height) for x in ratio]
- p_ratio[1] = height - p_ratio[0] - p_ratio[2]
-
- # Resize SMART/Journal panes
- tmux_resize_pane(state.panes['SMART'], y=ratio[0])
- tmux_resize_pane(y=ratio[1])
- tmux_resize_pane(state.panes['Journal'], y=ratio[2])
-
-
def get_device_details(dev_path):
"""Get device details via lsblk, returns JSON dict."""
cmd = ['lsblk', '--json', '--output-all', '--paths', dev_path]
@@ -678,22 +722,22 @@ def get_dir_report(dir_path):
output.append('{BLUE}{label:<{width}}{line}{CLEAR}'.format(
label='PATH',
width=width,
- line=line.replace('\n',''),
+ line=line.replace('\n', ''),
**COLORS))
else:
output.append('{path:<{width}}{line}'.format(
path=dir_path,
width=width,
- line=line.replace('\n','')))
+ line=line.replace('\n', '')))
# Done
return '\n'.join(output)
-def get_size_in_bytes(s):
+def get_size_in_bytes(size):
"""Convert size string from lsblk string to bytes, returns int."""
- s = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', s, re.IGNORECASE)
- return convert_to_bytes(s)
+ size = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', size, re.IGNORECASE)
+ return convert_to_bytes(size)
def get_formatted_status(label, data):
@@ -701,13 +745,15 @@ def get_formatted_status(label, data):
data_width = SIDE_PANE_WIDTH - len(label)
try:
data_str = '{data:>{data_width}.2f} %'.format(
- data=data,
- data_width=data_width-2)
+ data=data,
+ data_width=data_width-2,
+ )
except ValueError:
# Assuming non-numeric data
data_str = '{data:>{data_width}}'.format(
- data=data,
- data_width=data_width)
+ data=data,
+ data_width=data_width,
+ )
status = '{label}{s_color}{data_str}{CLEAR}'.format(
label=label,
s_color=get_status_color(data),
@@ -716,19 +762,19 @@ def get_formatted_status(label, data):
return status
-def get_status_color(s, t_success=99, t_warn=90):
+def get_status_color(status, t_success=99, t_warn=90):
"""Get color based on status, returns str."""
color = COLORS['CLEAR']
p_recovered = -1
try:
- p_recovered = float(s)
+ p_recovered = float(status)
except ValueError:
# Status is either in lists below or will default to red
pass
- if s in ('Pending',) or str(s)[-2:] in (' b', 'Kb', 'Mb', 'Gb', 'Tb'):
+ if status == 'Pending' or str(status)[-2:] in (' b', 'Kb', 'Mb', 'Gb', 'Tb'):
color = COLORS['CLEAR']
- elif s in ('Skipped', 'Unknown'):
+ elif status in ('Skipped', 'Unknown'):
color = COLORS['YELLOW']
elif p_recovered >= t_success:
color = COLORS['GREEN']
@@ -755,6 +801,7 @@ def is_writable_filesystem(dir_obj):
def menu_ddrescue(source_path, dest_path, run_mode):
+ # pylint: disable=too-many-branches
"""ddrescue menu."""
source = None
dest = None
@@ -798,9 +845,8 @@ def menu_ddrescue(source_path, dest_path, run_mode):
raise GenericAbort()
# Main menu
- clear_screen()
- build_outer_panes(state)
- fix_tmux_panes(state, forced=True)
+ state.build_outer_panes()
+ state.fix_tmux_panes(forced=True)
menu_main(state)
# Done
@@ -809,6 +855,7 @@ def menu_ddrescue(source_path, dest_path, run_mode):
def menu_main(state):
+ # pylint: disable=too-many-branches,too-many-statements
"""Main menu is used to set ddrescue settings."""
checkmark = '*'
if 'DISPLAY' in global_vars['Env']:
@@ -819,16 +866,15 @@ def menu_main(state):
# Build menu
main_options = [
{'Base Name': 'Auto continue (if recovery % over threshold)',
- 'Enabled': True},
+ 'Enabled': True},
{'Base Name': 'Retry (mark non-rescued sectors "non-tried")',
- 'Enabled': False},
+ 'Enabled': False},
{'Base Name': 'Reverse direction', 'Enabled': False},
]
actions = [
{'Name': 'Start', 'Letter': 'S'},
- {'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format(
- **COLORS),
- 'Letter': 'C'},
+ {'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format(**COLORS),
+ 'Letter': 'C'},
{'Name': 'Quit', 'Letter': 'Q', 'CRLF': True},
]
@@ -859,13 +905,13 @@ def menu_main(state):
elif selection == 'S':
# Set settings for pass
pass_settings = []
- for k, v in state.settings.items():
- if not v['Enabled']:
+ for option, option_data in state.settings.items():
+ if not option_data['Enabled']:
continue
- if 'Value' in v:
- pass_settings.append('{}={}'.format(k, v['Value']))
+ if 'Value' in option_data:
+ pass_settings.append('{}={}'.format(option, option_data['Value']))
else:
- pass_settings.append(k)
+ pass_settings.append(option)
for opt in main_options:
if 'Auto' in opt['Base Name']:
auto_run = opt['Enabled']
@@ -888,7 +934,7 @@ def menu_main(state):
state.current_pass_min() < AUTO_PASS_1_THRESHOLD):
auto_run = False
elif (state.current_pass == 1 and
- state.current_pass_min() < AUTO_PASS_2_THRESHOLD):
+ state.current_pass_min() < AUTO_PASS_2_THRESHOLD):
auto_run = False
else:
auto_run = False
@@ -917,13 +963,15 @@ def menu_settings(state):
# Build menu
settings = []
- for k, v in sorted(state.settings.items()):
- if not v.get('Hidden', False):
- settings.append({'Base Name': k, 'Flag': k})
+ for option, option_data in sorted(state.settings.items()):
+ if not option_data.get('Hidden', False):
+ settings.append({'Base Name': option, 'Flag': option})
actions = [{'Name': 'Main Menu', 'Letter': 'M'}]
# Show menu
while True:
+ # pylint: disable=invalid-name
+ # TODO: Clean up and/or replace with new menu-select function
for s in settings:
s['Name'] = '{}{}{}'.format(
s['Base Name'],
@@ -960,25 +1008,27 @@ def menu_settings(state):
def read_map_file(map_path):
"""Read map file with ddrescuelog and return data as dict."""
- map_data = {'full recovery': False}
+ cmd = [
+ 'ddrescuelog',
+ '--binary-prefixes',
+ '--show-status',
+ map_path,
+ ]
+ map_data = {'full recovery': False, 'pass completed': False}
try:
- result = run_program(['ddrescuelog', '-t', map_path])
+ result = run_program(cmd, encoding='utf-8', errors='ignore')
except CalledProcessError:
# (Grossly) assuming map_data hasn't been saved yet, return empty dict
return map_data
# Parse output
- for line in result.stdout.decode().splitlines():
- m = re.match(
- r'^\s*(?P\S+):.*\(\s*(?P\d+\.?\d*)%.*', line.strip())
- if m:
- try:
- map_data[m.group('key')] = float(m.group('value'))
- except ValueError:
- raise GenericError('Failed to read map data')
- m = re.match(r'.*current status:\s+(?P.*)', line.strip())
- if m:
- map_data['pass completed'] = bool(m.group('status') == 'finished')
+ for line in result.stdout.splitlines():
+ line = line.strip()
+ _r = REGEX_DDRESCUE_LOG.search(line)
+ if _r:
+ map_data[_r.group('key')] = convert_to_bytes('{size} {unit}B'.format(
+ **_r.groupdict()))
+ map_data['pass completed'] = 'current status: finished' in line
# Check if 100% done
try:
@@ -992,6 +1042,7 @@ def read_map_file(map_path):
def run_ddrescue(state, pass_settings):
+ # pylint: disable=too-many-branches,too-many-statements
"""Run ddrescue pass."""
return_code = -1
aborted = False
@@ -1006,8 +1057,8 @@ def run_ddrescue(state, pass_settings):
# Create SMART monitor pane
state.smart_out = '{}/smart_{}.out'.format(
global_vars['TmpDir'], state.smart_source.name)
- with open(state.smart_out, 'w') as f:
- f.write('Initializing...')
+ with open(state.smart_out, 'w') as _f:
+ _f.write('Initializing...')
state.panes['SMART'] = tmux_split_window(
behind=True, lines=12, vertical=True, watch=state.smart_out)
@@ -1017,19 +1068,19 @@ def run_ddrescue(state, pass_settings):
command=['sudo', 'journalctl', '-f'])
# Fix layout
- fix_tmux_panes(state, forced=True)
+ state.fix_tmux_panes(forced=True)
# Run pass for each block-pair
- for bp in state.block_pairs:
- if bp.pass_done[state.current_pass]:
+ for b_pair in state.block_pairs:
+ if b_pair.pass_done[state.current_pass]:
# Skip to next block-pair
continue
update_sidepane(state)
# Set ddrescue cmd
cmd = [
- 'ddrescue', *pass_settings,
- bp.source_path, bp.dest_path, bp.map_path]
+ 'sudo', 'ddrescue', *pass_settings,
+ b_pair.source_path, b_pair.dest_path, b_pair.map_path]
if state.mode == 'clone':
cmd.append('--force')
if state.current_pass == 0:
@@ -1044,36 +1095,36 @@ def run_ddrescue(state, pass_settings):
# Start ddrescue
try:
clear_screen()
- print_info('Current dev: {}'.format(bp.source_path))
+ print_info('Current dev: {}'.format(b_pair.source_path))
ddrescue_proc = popen_program(cmd)
i = 0
while True:
# Update SMART display (every 30 seconds)
if i % 30 == 0:
state.smart_source.get_smart_details()
- with open(state.smart_out, 'w') as f:
+ with open(state.smart_out, 'w') as _f:
report = state.smart_source.generate_attribute_report(
- timestamp=True)
+ timestamp=True)
for line in report:
- f.write('{}\n'.format(line))
+ _f.write('{}\n'.format(line))
i += 1
# Update progress
- bp.update_progress(state.current_pass)
+ b_pair.update_progress(state.current_pass)
update_sidepane(state)
# Fix panes
- fix_tmux_panes(state)
+ state.fix_tmux_panes()
# Check if ddrescue has finished
try:
ddrescue_proc.wait(timeout=1)
sleep(2)
- bp.update_progress(state.current_pass)
+ b_pair.update_progress(state.current_pass)
update_sidepane(state)
break
except subprocess.TimeoutExpired:
- # Catch to update smart/bp/sidepane
+ # Catch to update smart/b_pair/sidepane
pass
except KeyboardInterrupt:
@@ -1082,7 +1133,7 @@ def run_ddrescue(state, pass_settings):
ddrescue_proc.wait(timeout=10)
# Update progress/sidepane again
- bp.update_progress(state.current_pass)
+ b_pair.update_progress(state.current_pass)
update_sidepane(state)
# Was ddrescue aborted?
@@ -1104,7 +1155,7 @@ def run_ddrescue(state, pass_settings):
break
else:
# Mark pass finished
- bp.finish_pass(state.current_pass)
+ b_pair.finish_pass(state.current_pass)
update_sidepane(state)
# Done
@@ -1120,6 +1171,8 @@ def run_ddrescue(state, pass_settings):
def select_parts(source_device):
+ # pylint: disable=too-many-branches
+ # TODO: Clean up and/or replace with new menu-select function
"""Select partition(s) or whole device, returns list of DevObj()s."""
selected_parts = []
children = source_device.details.get('children', [])
@@ -1181,24 +1234,26 @@ def select_parts(source_device):
raise GenericAbort()
# Build list of selected parts
- for d in dev_options:
- if d['Selected']:
- d['Dev'].model = source_device.model
- d['Dev'].model_size = source_device.model_size
- d['Dev'].update_filename_prefix()
- selected_parts.append(d['Dev'])
+ for _d in dev_options:
+ if _d['Selected']:
+ _d['Dev'].model = source_device.model
+ _d['Dev'].model_size = source_device.model_size
+ _d['Dev'].update_filename_prefix()
+ selected_parts.append(_d['Dev'])
return selected_parts
def select_path(skip_device=None):
+ # pylint: disable=too-many-branches,too-many-locals
+ # TODO: Clean up and/or replace with new menu-select function
"""Optionally mount local dev and select path, returns DirObj."""
- wd = os.path.realpath(global_vars['Env']['PWD'])
+ work_dir = os.path.realpath(global_vars['Env']['PWD'])
selected_path = None
# Build menu
path_options = [
- {'Name': 'Current directory: {}'.format(wd), 'Path': wd},
+ {'Name': 'Current directory: {}'.format(work_dir), 'Path': work_dir},
{'Name': 'Local device', 'Path': None},
{'Name': 'Enter manually', 'Path': None}]
actions = [{'Name': 'Quit', 'Letter': 'Q'}]
@@ -1213,9 +1268,9 @@ def select_path(skip_device=None):
raise GenericAbort()
elif selection.isnumeric():
index = int(selection) - 1
- if path_options[index]['Path'] == wd:
+ if path_options[index]['Path'] == work_dir:
# Current directory
- selected_path = DirObj(wd)
+ selected_path = DirObj(work_dir)
elif path_options[index]['Name'] == 'Local device':
# Local device
@@ -1231,15 +1286,15 @@ def select_path(skip_device=None):
# Select volume
vol_options = []
- for k, v in sorted(report.items()):
- disabled = v['show_data']['data'] == 'Failed to mount'
+ for _k, _v in sorted(report.items()):
+ disabled = _v['show_data']['data'] == 'Failed to mount'
if disabled:
- name = '{name} (Failed to mount)'.format(**v)
+ name = '{name} (Failed to mount)'.format(**_v)
else:
- name = '{name} (mounted on "{mount_point}")'.format(**v)
+ name = '{name} (mounted on "{mount_point}")'.format(**_v)
vol_options.append({
'Name': name,
- 'Path': v['mount_point'],
+ 'Path': _v['mount_point'],
'Disabled': disabled})
selection = menu_select(
title='Please select a volume',
@@ -1314,15 +1369,17 @@ def select_device(description='device', skip_device=None):
action_entries=actions,
disabled_label='ALREADY SELECTED')
+ if selection == 'Q':
+ raise GenericAbort()
+
if selection.isnumeric():
return dev_options[int(selection)-1]['Dev']
- elif selection == 'Q':
- raise GenericAbort()
def setup_loopback_device(source_path):
"""Setup loopback device for source_path, returns dev_path as str."""
cmd = (
+ 'sudo',
'losetup',
'--find',
'--partscan',
@@ -1356,6 +1413,7 @@ def show_selection_details(state):
def show_usage(script_name):
+ """Show usage."""
print_info('Usage:')
print_standard(USAGE.format(script_name=script_name))
pause()
@@ -1379,14 +1437,14 @@ def update_sidepane(state):
output.append('─────────────────────')
# Source(s) progress
- for bp in state.block_pairs:
+ for b_pair in state.block_pairs:
if state.source.is_image():
output.append('{BLUE}Image File{CLEAR}'.format(**COLORS))
else:
output.append('{BLUE}{source}{CLEAR}'.format(
- source=bp.source_path,
+ source=b_pair.source_path,
**COLORS))
- output.extend(bp.status)
+ output.extend(b_pair.status)
output.append(' ')
# EToC
@@ -1405,11 +1463,9 @@ def update_sidepane(state):
# Add line-endings
output = ['{}\n'.format(line) for line in output]
- with open(state.progress_out, 'w') as f:
- f.writelines(output)
+ with open(state.progress_out, 'w') as _f:
+ _f.writelines(output)
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/settings/ddrescue.py b/.bin/Scripts/settings/ddrescue.py
index 675019ca..ffe6e215 100644
--- a/.bin/Scripts/settings/ddrescue.py
+++ b/.bin/Scripts/settings/ddrescue.py
@@ -5,7 +5,9 @@ import re
from collections import OrderedDict
# General
+MAP_DIR = '/Backups/ddrescue-tui'
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
+RECOMMENDED_MAP_FSTYPES = ['cifs', 'ext2', 'ext3', 'ext4', 'vfat', 'xfs']
USAGE = """ {script_name} clone [source [destination]]
{script_name} image [source [destination]]
(e.g. {script_name} clone /dev/sda /dev/sdb)
@@ -36,6 +38,12 @@ DDRESCUE_SETTINGS = {
'-vvvv': {'Enabled': True, 'Hidden': True, },
}
ETOC_REFRESH_RATE = 30 # in seconds
+REGEX_DDRESCUE_LOG = re.compile(
+ r'^\s*(?P\S+):\s+'
+ r'(?P\d+)\s+'
+ r'(?P[PTGMKB])i?B?',
+ re.IGNORECASE,
+ )
REGEX_REMAINING_TIME = re.compile(
r'remaining time:'
r'\s*((?P\d+)d)?'
From 28bedc0873ac973817ed40ca9c912e0a0197f015 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:01:26 -0600
Subject: [PATCH 19/27] Run ddrescue-tui as current user
---
.linux_items/include/airootfs/etc/skel/.aliases | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.linux_items/include/airootfs/etc/skel/.aliases b/.linux_items/include/airootfs/etc/skel/.aliases
index d6486258..b0068be3 100644
--- a/.linux_items/include/airootfs/etc/skel/.aliases
+++ b/.linux_items/include/airootfs/etc/skel/.aliases
@@ -34,5 +34,5 @@ alias srsz='sudo rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias testdisk='sudo testdisk'
alias umount='sudo umount'
alias unmount='sudo umount'
-alias wkclone='sudo ddrescue-tui clone'
-alias wkimage='sudo ddrescue-tui image'
+alias wkclone='ddrescue-tui clone'
+alias wkimage='ddrescue-tui image'
From 606efac3fe96b05de7993506d2347605aa7c1a2b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:09:56 -0600
Subject: [PATCH 20/27] Updated mounting sections
---
.bin/Scripts/functions/data.py | 51 ++++++++++++++++++++++++++------
.bin/Scripts/mount-backup-shares | 3 --
2 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/.bin/Scripts/functions/data.py b/.bin/Scripts/functions/data.py
index fa0bb20c..c359ab6c 100644
--- a/.bin/Scripts/functions/data.py
+++ b/.bin/Scripts/functions/data.py
@@ -151,12 +151,16 @@ def is_valid_wim_file(item):
def get_mounted_volumes():
"""Get mounted volumes, returns dict."""
cmd = [
- 'findmnt', '-J', '-b', '-i',
- '-t', (
+ 'findmnt',
+ '--list',
+ '--json',
+ '--bytes',
+ '--invert',
+ '--types', (
'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,'
'devtmpfs,hugetlbfs,mqueue,proc,pstore,securityfs,sysfs,tmpfs'
),
- '-o', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
+ '--output', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
json_data = get_json_from_command(cmd)
mounted_volumes = []
for item in json_data.get('filesystems', []):
@@ -195,6 +199,8 @@ def mount_volumes(
volumes.update({child['name']: child})
for grandchild in child.get('children', []):
volumes.update({grandchild['name']: grandchild})
+ for great_grandchild in grandchild.get('children', []):
+ volumes.update({great_grandchild['name']: great_grandchild})
# Get list of mounted volumes
mounted_volumes = get_mounted_volumes()
@@ -237,6 +243,7 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None
else:
+ fstype = vol_data.get('fstype', 'UNKNOWN FS')
size_used = human_readable_size(
mounted_volumes[vol_path]['used'],
decimals=1,
@@ -250,8 +257,9 @@ def mount_volumes(
vol_data['mount_point'] = mounted_volumes[vol_path]['target']
vol_data['show_data']['data'] = 'Mounted on {}'.format(
mounted_volumes[vol_path]['target'])
- vol_data['show_data']['data'] = '{:40} ({} used, {} free)'.format(
+ vol_data['show_data']['data'] = '{:40} ({}, {} used, {} free)'.format(
vol_data['show_data']['data'],
+ fstype,
size_used,
size_avail)
@@ -285,6 +293,14 @@ def mount_backup_shares(read_write=False):
def mount_network_share(server, read_write=False):
"""Mount a network share defined by server."""
+ uid = '1000'
+
+ # Get UID
+ cmd = ['id', '--user', 'tech']
+ result = run_program(cmd, check=False, encoding='utf-8', errors='ignore')
+ if result.stdout.strip().isnumeric():
+ uid = result.stdout.strip()
+
if read_write:
username = server['RW-User']
password = server['RW-Pass']
@@ -300,18 +316,35 @@ def mount_network_share(server, read_write=False):
error = r'Failed to mount \\{Name}\{Share} ({IP})'.format(**server)
success = 'Mounted {Name}'.format(**server)
elif psutil.LINUX:
+ # Make mountpoint
cmd = [
'sudo', 'mkdir', '-p',
'/Backups/{Name}'.format(**server)]
run_program(cmd)
+
+ # Set mount options
+ cmd_options = [
+ # Assuming GID matches UID
+ 'gid={}'.format(uid),
+ 'uid={}'.format(uid),
+ ]
+ cmd_options.append('rw' if read_write else 'ro')
+ cmd_options.append('username={}'.format(username))
+ if password:
+ cmd_options.append('password={}'.format(password))
+ else:
+ # Skip password check
+ cmd_options.append('guest')
+
+ # Set mount command
cmd = [
'sudo', 'mount',
- '//{IP}/{Share}'.format(**server),
+ '//{IP}/{Share}'.format(**server).replace('\\', '/'),
'/Backups/{Name}'.format(**server),
- '-o', '{}username={},password={}'.format(
- '' if read_write else 'ro,',
- username,
- password)]
+ '-o', ','.join(cmd_options),
+ ]
+
+ # Set result messages
warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format(
**server)
error = 'Failed to mount /Backups/{Name}'.format(**server)
diff --git a/.bin/Scripts/mount-backup-shares b/.bin/Scripts/mount-backup-shares
index 6a1e88d7..0d8b7fd3 100755
--- a/.bin/Scripts/mount-backup-shares
+++ b/.bin/Scripts/mount-backup-shares
@@ -16,9 +16,6 @@ if __name__ == '__main__':
# Prep
clear_screen()
- # Connect
- connect_to_network()
-
# Mount
if is_connected():
mount_backup_shares(read_write=True)
From ec5591453e12543d4921ebb25e8d5fbddfe01b37 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:16:09 -0600
Subject: [PATCH 21/27] Updated data.py
---
.bin/Scripts/functions/data.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.bin/Scripts/functions/data.py b/.bin/Scripts/functions/data.py
index c359ab6c..bf091b37 100644
--- a/.bin/Scripts/functions/data.py
+++ b/.bin/Scripts/functions/data.py
@@ -218,7 +218,7 @@ def mount_volumes(
report[vol_path] = vol_data
elif 'children' in vol_data:
# Skip LVM/RAID partitions (the real volume is mounted separately)
- vol_data['show_data']['data'] = vol_data.get('fstype', 'UNKNOWN')
+ vol_data['show_data']['data'] = vol_data.get('fstype', 'Unknown')
if vol_data.get('label', None):
vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label'])
vol_data['show_data']['info'] = True
@@ -243,7 +243,7 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None
else:
- fstype = vol_data.get('fstype', 'UNKNOWN FS')
+ fstype = vol_data.get('fstype', 'Unknown FS')
size_used = human_readable_size(
mounted_volumes[vol_path]['used'],
decimals=1,
From 70823d2cd80c123932fcce862fc6c3e3d3c50be6 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:53:34 -0600
Subject: [PATCH 22/27] Updated HW-Diags and sensor sections
---
.bin/Scripts/debug/hw_diags.py | 9 +-
.bin/Scripts/functions/hw_diags.py | 294 +++++++++++++++++------------
.bin/Scripts/functions/sensors.py | 89 ++++-----
.bin/Scripts/hw-diags-menu | 2 +-
4 files changed, 226 insertions(+), 168 deletions(-)
diff --git a/.bin/Scripts/debug/hw_diags.py b/.bin/Scripts/debug/hw_diags.py
index 87a35990..44517fb4 100644
--- a/.bin/Scripts/debug/hw_diags.py
+++ b/.bin/Scripts/debug/hw_diags.py
@@ -149,11 +149,14 @@ def save_debug_reports(state, global_vars):
f.write('{}\n'.format(line))
-def upload_logdir(global_vars):
+def upload_logdir(global_vars, reason='Crash'):
"""Upload compressed LogDir to CRASH_SERVER."""
source = global_vars['LogDir']
source = source[source.rfind('/')+1:]
- dest = '{}.txz'.format(source)
+ dest = 'HW-Diags_{reason}_{Date-Time}.txz'.format(
+ reason=reason,
+ **global_vars,
+ )
data = None
# Compress LogDir
@@ -166,7 +169,7 @@ def upload_logdir(global_vars):
data = f.read()
# Upload data
- url = '{}/Crash_{}.txz'.format(CRASH_SERVER['Url'], source)
+ url = '{}/{}'.format(CRASH_SERVER['Url'], dest)
r = requests.put(
url,
data=data,
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index 36be4a30..db5d01ca 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -36,6 +36,7 @@ class CpuObj():
self.tests = OrderedDict()
self.get_details()
self.name = self.lscpu.get('Model name', 'Unknown CPU')
+ self.description = self.name
def get_details(self):
"""Get CPU details from lscpu."""
@@ -57,6 +58,13 @@ class CpuObj():
report.append('{BLUE}Device{CLEAR}'.format(**COLORS))
report.append(' {}'.format(self.name))
+ # Include RAM details
+ ram_details = get_ram_details()
+ ram_total = human_readable_size(ram_details.pop('Total', 0)).strip()
+ ram_dimms = ['{}x {}'.format(v, k) for k, v in sorted(ram_details.items())]
+ report.append('{BLUE}RAM{CLEAR}'.format(**COLORS))
+ report.append(' {} ({})'.format(ram_total, ', '.join(ram_dimms)))
+
# Tests
for test in self.tests.values():
report.extend(test.report)
@@ -220,11 +228,12 @@ class DiskObj():
# Done
return test_running
- def disable_test(self, name, status):
+ def disable_test(self, name, status, test_failed=False):
"""Disable test by name and update status."""
if name in self.tests:
self.tests[name].update_status(status)
self.tests[name].disabled = True
+ self.tests[name].failed = test_failed
def generate_attribute_report(
self, description=False, timestamp=False):
@@ -487,7 +496,7 @@ class DiskObj():
for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied')
elif not disk_ok:
- self.disable_test('NVMe / SMART', 'NS')
+ self.disable_test('NVMe / SMART', 'NS', test_failed=True)
for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied')
@@ -495,6 +504,7 @@ class DiskObj():
class State():
"""Object to track device objects and overall state."""
def __init__(self):
+ self.args = None
self.cpu = None
self.disks = []
self.panes = {}
@@ -522,6 +532,83 @@ class State():
},
})
+ def build_outer_panes(self):
+ """Build top and side panes."""
+ clear_screen()
+
+ # Top
+ self.panes['Top'] = tmux_split_window(
+ behind=True, lines=2, vertical=True,
+ text=TOP_PANE_TEXT)
+
+ # Started
+ self.panes['Started'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH, target_pane=self.panes['Top'],
+ text='{BLUE}Started{CLEAR}\n{s}'.format(
+ s=time.strftime("%Y-%m-%d %H:%M %Z"),
+ **COLORS))
+
+ # Progress
+ self.panes['Progress'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH,
+ watch=self.progress_out)
+
+ def fix_tmux_panes(self):
+ """Fix pane sizes if the window has been resized."""
+ needs_fixed = False
+
+ # Bail?
+ if not self.panes:
+ return
+
+ # Check layout
+ for k, v in self.tmux_layout.items():
+ if not v.get('Check'):
+ # Not concerned with the size of this pane
+ continue
+ # Get target
+ target = None
+ if k != 'Current':
+ if k not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[k]
+
+ # Check pane size
+ x, y = tmux_get_pane_size(pane_id=target)
+ if v.get('x', False) and v['x'] != x:
+ needs_fixed = True
+ if v.get('y', False) and v['y'] != y:
+ needs_fixed = True
+
+ # Bail?
+ if not needs_fixed:
+ return
+
+ # Update layout
+ for k, v in self.tmux_layout.items():
+ # Get target
+ target = None
+ if k != 'Current':
+ if k not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[k]
+
+ # Resize pane
+ tmux_resize_pane(pane_id=target, **v)
+
+ def fix_tmux_panes_loop(self):
+ while True:
+ try:
+ self.fix_tmux_panes()
+ sleep(1)
+ except RuntimeError:
+ # Assuming layout definitions changes mid-run, ignoring
+ pass
+
def init(self):
"""Remove test objects, set log, and add devices."""
self.disks = []
@@ -529,14 +616,18 @@ class State():
v['Objects'] = []
# Update LogDir
- if not self.quick_mode:
+ if self.quick_mode:
+ global_vars['LogDir'] = '{}/Logs/{}'.format(
+ global_vars['Env']['HOME'],
+ time.strftime('%Y-%m-%d_%H%M_%z'))
+ else:
global_vars['LogDir'] = '{}/Logs/{}_{}'.format(
global_vars['Env']['HOME'],
get_ticket_number(),
time.strftime('%Y-%m-%d_%H%M_%z'))
- os.makedirs(global_vars['LogDir'], exist_ok=True)
- global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
- global_vars['LogDir'])
+ os.makedirs(global_vars['LogDir'], exist_ok=True)
+ global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
+ global_vars['LogDir'])
self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
# Add CPU
@@ -565,7 +656,13 @@ class State():
# Start tmux thread
self.tmux_layout = TMUX_LAYOUT.copy()
- start_thread(fix_tmux_panes_loop, args=[self])
+ start_thread(self.fix_tmux_panes_loop)
+
+ def set_top_pane_text(self, text):
+ """Set top pane text using TOP_PANE_TEXT and provided text."""
+ tmux_update_pane(
+ self.panes['Top'],
+ text='{}\n{}'.format(TOP_PANE_TEXT, text))
class TestObj():
@@ -600,28 +697,6 @@ class TestObj():
# Functions
-def build_outer_panes(state):
- """Build top and side panes."""
- clear_screen()
-
- # Top
- state.panes['Top'] = tmux_split_window(
- behind=True, lines=2, vertical=True,
- text=TOP_PANE_TEXT)
-
- # Started
- state.panes['Started'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH, target_pane=state.panes['Top'],
- text='{BLUE}Started{CLEAR}\n{s}'.format(
- s=time.strftime("%Y-%m-%d %H:%M %Z"),
- **COLORS))
-
- # Progress
- state.panes['Progress'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH,
- watch=state.progress_out)
-
-
def build_status_string(label, status, info_label=False):
"""Build status string with appropriate colors."""
status_color = COLORS['CLEAR']
@@ -638,64 +713,6 @@ def build_status_string(label, status, info_label=False):
**COLORS)
-def fix_tmux_panes_loop(state):
- while True:
- try:
- fix_tmux_panes(state)
- sleep(1)
- except RuntimeError:
- # Assuming layout definitions changes mid-run, ignoring
- pass
-
-
-def fix_tmux_panes(state):
- """Fix pane sizes if the window has been resized."""
- needs_fixed = False
-
- # Bail?
- if not state.panes:
- return
-
- # Check layout
- for k, v in state.tmux_layout.items():
- if not v.get('Check'):
- # Not concerned with the size of this pane
- continue
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Check pane size
- x, y = tmux_get_pane_size(pane_id=target)
- if v.get('x', False) and v['x'] != x:
- needs_fixed = True
- if v.get('y', False) and v['y'] != y:
- needs_fixed = True
-
- # Bail?
- if not needs_fixed:
- return
-
- # Update layout
- for k, v in state.tmux_layout.items():
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Resize pane
- tmux_resize_pane(pane_id=target, **v)
-
-
def generate_horizontal_graph(rates, oneline=False):
"""Generate horizontal graph from rates, returns list."""
graph = ['', '', '', '']
@@ -755,6 +772,44 @@ def get_graph_step(rate, scale=16):
return step
+def get_ram_details():
+ """Get RAM details via dmidecode, returns dict."""
+ cmd = ['sudo', 'dmidecode', '--type', 'memory']
+ manufacturer = 'UNKNOWN'
+ ram_details = {'Total': 0}
+ size = 0
+
+ # Get DMI data
+ result = run_program(cmd, encoding='utf-8', errors='ignore')
+ dmi_data = result.stdout.splitlines()
+
+ # Parse data
+ for line in dmi_data:
+ line = line.strip()
+ if line == 'Memory Device':
+ # Reset vars
+ manufacturer = 'UNKNOWN'
+ size = 0
+ elif line.startswith('Size:'):
+ size = convert_to_bytes(line.replace('Size: ', ''))
+ elif line.startswith('Manufacturer:'):
+ manufacturer = line.replace('Manufacturer: ', '')
+ if size > 0:
+ # Add RAM to list if slot populated
+ ram_str = '{} {}'.format(
+ human_readable_size(size).strip(),
+ manufacturer,
+ )
+ ram_details['Total'] += size
+ if ram_str in ram_details:
+ ram_details[ram_str] += 1
+ else:
+ ram_details[ram_str] = 1
+
+ # Done
+ return ram_details
+
+
def get_read_rate(s):
"""Get read rate in bytes/s from dd progress output."""
real_rate = None
@@ -767,6 +822,7 @@ def get_read_rate(s):
def menu_diags(state, args):
"""Main menu to select and run HW tests."""
args = [a.lower() for a in args]
+ state.args = args
checkmark = '*'
if 'DISPLAY' in global_vars['Env']:
checkmark = '✓'
@@ -908,10 +964,7 @@ def run_badblocks_test(state, test):
update_progress_pane(state)
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(
- TOP_PANE_TEXT, dev.description))
+ state.set_top_pane_text(dev.description)
# Create monitor pane
test.badblocks_out = '{}/badblocks_{}.out'.format(
@@ -994,10 +1047,11 @@ def run_hw_tests(state):
"""Run enabled hardware tests."""
print_standard('Scanning devices...')
state.init()
+ tests_enabled = False
# Build Panes
update_progress_pane(state)
- build_outer_panes(state)
+ state.build_outer_panes()
# Show selected tests and create TestObj()s
print_info('Selected Tests:')
@@ -1009,6 +1063,8 @@ def run_hw_tests(state):
COLORS['CLEAR'],
QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
if v['Enabled']:
+ tests_enabled = True
+
# Create TestObj and track under both CpuObj/DiskObj and State
if k in TESTS_CPU:
test_obj = TestObj(
@@ -1022,6 +1078,11 @@ def run_hw_tests(state):
v['Objects'].append(test_obj)
print_standard('')
+ # Bail if no tests selected
+ if not tests_enabled:
+ tmux_kill_pane(*state.panes.values())
+ return
+
# Run disk safety checks (if necessary)
_disk_tests_enabled = False
for k in TESTS_DISK:
@@ -1064,7 +1125,7 @@ def run_hw_tests(state):
# Rebuild panes
update_progress_pane(state)
- build_outer_panes(state)
+ state.build_outer_panes()
# Mark unfinished tests as aborted
for k, v in state.tests.items():
@@ -1076,8 +1137,22 @@ def run_hw_tests(state):
# Update side pane
update_progress_pane(state)
- # Done
+ # Show results
show_results(state)
+
+ # Upload for review
+ if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
+ try_and_print(
+ message='Saving debug reports...',
+ function=save_debug_reports,
+ state=state, global_vars=global_vars)
+ try_and_print(
+ message='Uploading Data...',
+ function=upload_logdir,
+ global_vars=global_vars,
+ reason='Review')
+
+ # Done
sleep(1)
if state.quick_mode:
pause('Press Enter to exit... ')
@@ -1104,10 +1179,7 @@ def run_io_benchmark(state, test):
update_progress_pane(state)
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(
- TOP_PANE_TEXT, dev.description))
+ state.set_top_pane_text(dev.description)
state.tmux_layout['Current'] = {'y': 15, 'Check': True}
# Create monitor pane
@@ -1266,9 +1338,7 @@ def run_mprime_test(state, test):
test.thermal_abort = False
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(TOP_PANE_TEXT, dev.name))
+ state.set_top_pane_text(dev.name)
# Start live sensor monitor
test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir'])
@@ -1431,7 +1501,7 @@ def run_mprime_test(state, test):
# Add temps to report
test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS))
for line in generate_sensor_report(
- test.sensor_data, 'Idle', 'Max', 'Cooldown', core_only=True):
+ test.sensor_data, 'Idle', 'Max', 'Cooldown', cpu_only=True):
test.report.append(' {}'.format(line))
# Add abort message(s)
@@ -1481,10 +1551,7 @@ def run_nvme_smart_tests(state, test, update_mode=False):
update_progress_pane(state)
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(
- TOP_PANE_TEXT, dev.description))
+ state.set_top_pane_text(dev.description)
# SMART short self-test
if dev.smart_attributes and not (state.quick_mode or update_mode):
@@ -1629,9 +1696,7 @@ def show_report(report, log_report=False):
def show_results(state):
"""Show results for all tests."""
clear_screen()
- tmux_update_pane(
- state.panes['Top'],
- text='{}\nResults'.format(TOP_PANE_TEXT))
+ state.set_top_pane_text('Results')
# CPU tests
_enabled = False
@@ -1661,17 +1726,6 @@ def show_results(state):
# Update progress
update_progress_pane(state)
- # Ask for review
- if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
- try_and_print(
- message='Saving debug reports...',
- function=save_debug_reports,
- state=state, global_vars=global_vars)
- try_and_print(
- message='Uploading Data...',
- function=upload_logdir,
- global_vars=global_vars)
-
def update_main_options(state, selection, main_options):
"""Update menu and state based on selection."""
diff --git a/.bin/Scripts/functions/sensors.py b/.bin/Scripts/functions/sensors.py
index 993306bd..49a7472c 100644
--- a/.bin/Scripts/functions/sensors.py
+++ b/.bin/Scripts/functions/sensors.py
@@ -1,4 +1,6 @@
-# Wizard Kit: Functions - Sensors
+'''Wizard Kit: Functions - Sensors'''
+# pylint: disable=no-name-in-module,wildcard-import
+# vim: sts=2 sw=2 ts=2
import json
import re
@@ -9,7 +11,7 @@ from settings.sensors import *
# Error Classes
class ThermalLimitReachedError(Exception):
- pass
+ '''Thermal limit reached error.'''
def clear_temps(sensor_data):
@@ -20,28 +22,30 @@ def clear_temps(sensor_data):
_data['Temps'] = []
-def fix_sensor_str(s):
+def fix_sensor_str(_s):
"""Cleanup string and return str."""
- s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', s, re.IGNORECASE)
- s = s.title()
- s = s.replace('Coretemp', 'CoreTemp')
- s = s.replace('Acpi', 'ACPI')
- s = s.replace('ACPItz', 'ACPI TZ')
- s = s.replace('Isa ', 'ISA ')
- s = s.replace('Id ', 'ID ')
- s = re.sub(r'(\D+)(\d+)', r'\1 \2', s, re.IGNORECASE)
- s = s.replace(' ', ' ')
- return s
+ _s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', _s, re.IGNORECASE)
+ _s = _s.title()
+ _s = _s.replace('Coretemp', 'CPUTemp')
+ _s = _s.replace('Acpi', 'ACPI')
+ _s = _s.replace('ACPItz', 'ACPI TZ')
+ _s = _s.replace('Isa ', 'ISA ')
+ _s = _s.replace('Pci ', 'PCI ')
+ _s = _s.replace('Id ', 'ID ')
+ _s = re.sub(r'(\D+)(\d+)', r'\1 \2', _s, re.IGNORECASE)
+ _s = re.sub(r'^K (\d+)Temp', r'AMD K\1 Temps', _s, re.IGNORECASE)
+ _s = re.sub(r'T(ctl|die)', r'CPU (T\1)', _s, re.IGNORECASE)
+ return _s
def generate_sensor_report(
sensor_data, *temp_labels,
- colors=True, core_only=False):
+ colors=True, cpu_only=False):
"""Generate report based on temp_labels, returns list if str."""
report = []
for _section, _adapters in sorted(sensor_data.items()):
- # CoreTemps then Other temps
- if core_only and 'Core' not in _section:
+ # CPU temps then Other temps
+ if cpu_only and 'CPU' not in _section:
continue
for _adapter, _sources in sorted(_adapters.items()):
# Adapter
@@ -56,7 +60,7 @@ def generate_sensor_report(
': ' if _label != 'Current' else '',
get_temp_str(_data.get(_label, '???'), colors=colors))
report.append(_line)
- if not core_only:
+ if not cpu_only:
report.append(' ')
# Handle empty reports (i.e. no sensors detected)
@@ -91,17 +95,17 @@ def get_colored_temp_str(temp):
else:
color = COLORS['CLEAR']
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
- color = color,
- prefix = '-' if temp < 0 else '',
- temp = temp,
+ color=color,
+ prefix='-' if temp < 0 else '',
+ temp=temp,
**COLORS)
def get_raw_sensor_data():
"""Read sensor data and return dict."""
- data = {}
+ json_data = {}
cmd = ['sensors', '-j']
-
+
# Get raw data
try:
result = run_program(cmd)
@@ -122,8 +126,8 @@ def get_raw_sensor_data():
try:
json_data = json.loads('\n'.join(raw_data))
except json.JSONDecodeError:
- # Still broken, just set to empty dict
- json_data = {}
+ # Still broken, just return the empty dict
+ pass
# Done
return json_data
@@ -132,10 +136,10 @@ def get_raw_sensor_data():
def get_sensor_data():
"""Parse raw sensor data and return new dict."""
json_data = get_raw_sensor_data()
- sensor_data = {'CoreTemps': {}, 'Other': {}}
+ sensor_data = {'CPUTemps': {}, 'Other': {}}
for _adapter, _sources in json_data.items():
- if 'coretemp' in _adapter:
- _section = 'CoreTemps'
+ if is_cpu_adapter(_adapter):
+ _section = 'CPUTemps'
else:
_section = 'Other'
sensor_data[_section][_adapter] = {}
@@ -157,8 +161,8 @@ def get_sensor_data():
}
# Remove empty sections
- for k, v in sensor_data.items():
- v = {k2: v2 for k2, v2 in v.items() if v2}
+ for _k, _v in sensor_data.items():
+ _v = {_k2: _v2 for _k2, _v2 in _v.items() if _v2}
# Done
return sensor_data
@@ -178,14 +182,20 @@ def get_temp_str(temp, colors=True):
temp)
+def is_cpu_adapter(adapter):
+ """Checks if adapter is a known CPU adapter, returns bool."""
+ is_cpu = re.search(r'(core|k\d+)temp', adapter, re.IGNORECASE)
+ return bool(is_cpu)
+
+
def monitor_sensors(monitor_pane, monitor_file):
"""Continually update sensor data and report to screen."""
sensor_data = get_sensor_data()
while True:
update_sensor_data(sensor_data)
- with open(monitor_file, 'w') as f:
+ with open(monitor_file, 'w') as _f:
report = generate_sensor_report(sensor_data, 'Current', 'Max')
- f.write('\n'.join(report))
+ _f.write('\n'.join(report))
sleep(1)
if monitor_pane and not tmux_poll_pane(monitor_pane):
break
@@ -196,7 +206,7 @@ def save_average_temp(sensor_data, temp_label, seconds=10):
clear_temps(sensor_data)
# Get temps
- for i in range(seconds):
+ for _i in range(seconds): # pylint: disable=unused-variable
update_sensor_data(sensor_data)
sleep(1)
@@ -219,24 +229,15 @@ def update_sensor_data(sensor_data, thermal_limit=None):
_data['Current'] = _temp
_data['Max'] = max(_temp, _data['Max'])
_data['Temps'].append(_temp)
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Dumb workound for Dell sensors with changing source names
pass
# Check if thermal limit reached
- if thermal_limit and _section == 'CoreTemps':
+ if thermal_limit and _section == 'CPUTemps':
if max(_data['Current'], _data['Max']) >= thermal_limit:
- raise ThermalLimitReachedError('CoreTemps reached limit')
-
-
-def join_columns(column1, column2, width=55):
- return '{:<{}}{}'.format(
- column1,
- 55+len(column1)-len(REGEX_COLORS.sub('', column1)),
- column2)
+ raise ThermalLimitReachedError('CPU temps reached limit')
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/hw-diags-menu b/.bin/Scripts/hw-diags-menu
index 1c241bf9..fc95e04a 100755
--- a/.bin/Scripts/hw-diags-menu
+++ b/.bin/Scripts/hw-diags-menu
@@ -49,7 +49,7 @@ if __name__ == '__main__':
global_vars=global_vars)
# Done
- sleep(10)
+ sleep(1)
pause('Press Enter to exit...')
exit_script(1)
From 86f17757dbd4ac3f67d1f3ba8001919e335226c1 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 17:49:10 -0600
Subject: [PATCH 23/27] Updated cleanup sections
---
.bin/Scripts/functions/cleanup.py | 22 ++++++++++++------
.bin/Scripts/settings/cleanup.py | 37 +++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 7 deletions(-)
create mode 100644 .bin/Scripts/settings/cleanup.py
diff --git a/.bin/Scripts/functions/cleanup.py b/.bin/Scripts/functions/cleanup.py
index 5ee20be1..744ee048 100644
--- a/.bin/Scripts/functions/cleanup.py
+++ b/.bin/Scripts/functions/cleanup.py
@@ -1,7 +1,9 @@
-# Wizard Kit: Functions - Cleanup
-
-from functions.common import *
+'''Wizard Kit: Functions - Cleanup'''
+# pylint: disable=no-name-in-module,wildcard-import
+# vim: sts=2 sw=2 ts=2
+from functions.setup import *
+from settings.cleanup import *
def cleanup_adwcleaner():
"""Move AdwCleaner folders into the ClientDir."""
@@ -75,8 +77,7 @@ def cleanup_desktop():
desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env'])
for entry in os.scandir(desktop_path):
- # JRT, RKill, Shortcut cleaner
- if re.search(r'^(JRT|RKill|sc-cleaner)', entry.name, re.IGNORECASE):
+ if DESKTOP_ITEMS.search(entry.name):
dest_name = r'{}\{}'.format(dest_folder, entry.name)
dest_name = non_clobber_rename(dest_name)
shutil.move(entry.path, dest_name)
@@ -130,7 +131,14 @@ def delete_registry_value(hive, key, value):
winreg.DeleteValue(k, value)
+def restore_default_uac():
+ """Restores default UAC settings via the registry."""
+ if global_vars['OS']['Version'] == '10':
+ write_registry_settings(UAC_DEFAULTS_WIN10, all_users=True)
+ else:
+ # Haven't checked Win8 settings, only applying minimum set
+ write_registry_settings(UAC_DEFAULTS_WIN7, all_users=True)
+
+
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/settings/cleanup.py b/.bin/Scripts/settings/cleanup.py
new file mode 100644
index 00000000..2188fc6b
--- /dev/null
+++ b/.bin/Scripts/settings/cleanup.py
@@ -0,0 +1,37 @@
+'''Wizard Kit: Settings - Cleanup'''
+# vim: sts=2 sw=2 ts=2
+
+import re
+
+# Regex
+DESKTOP_ITEMS = re.compile(
+ r'^(JRT|RKill|sc-cleaner)',
+ re.IGNORECASE,
+ )
+
+# Registry
+UAC_DEFAULTS_WIN7 = {
+ r'Software\Microsoft\Windows\CurrentVersion\Policies\System': {
+ 'DWORD Items': {
+ 'ConsentPromptBehaviorAdmin': 5,
+ 'EnableLUA': 1,
+ 'PromptOnSecureDesktop': 1,
+ },
+ },
+ }
+UAC_DEFAULTS_WIN10 = {
+ r'Software\Microsoft\Windows\CurrentVersion\Policies\System': {
+ 'DWORD Items': {
+ 'ConsentPromptBehaviorAdmin': 5,
+ 'ConsentPromptBehaviorUser': 3,
+ 'EnableInstallerDetection': 1,
+ 'EnableLUA': 1,
+ 'EnableVirtualization': 1,
+ 'PromptOnSecureDesktop': 1,
+ },
+ },
+ }
+
+
+if __name__ == '__main__':
+ print("This file is not meant to be called directly.")
From c537a01fbf16d6f85dada1dd30b3c6f2b09654ad Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 17:55:50 -0600
Subject: [PATCH 24/27] Updated info.py
---
.bin/Scripts/functions/info.py | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/.bin/Scripts/functions/info.py b/.bin/Scripts/functions/info.py
index 84d92663..b1959090 100644
--- a/.bin/Scripts/functions/info.py
+++ b/.bin/Scripts/functions/info.py
@@ -95,7 +95,7 @@ def get_installed_antivirus():
out = out.stdout.decode().strip()
state = out.split('=')[1]
state = hex(int(state))
- if str(state)[3:5] != '10':
+ if str(state)[3:5] not in ['10', '11']:
programs.append('[Disabled] {}'.format(prod))
else:
programs.append(prod)
@@ -446,16 +446,19 @@ def show_os_name():
def show_temp_files_size():
"""Show total size of temp files identified by BleachBit."""
- size = None
+ size_str = None
+ total = 0
with open(r'{LogDir}\Tools\BleachBit.log'.format(**global_vars), 'r') as f:
for line in f.readlines():
- if re.search(r'^disk space to be recovered:', line, re.IGNORECASE):
+ if re.search(r'^Disk space (to be |)recovered:', line, re.IGNORECASE):
size = re.sub(r'.*: ', '', line.strip())
size = re.sub(r'(\w)iB$', r' \1b', size)
- if size is None:
- print_warning(size, timestamp=False)
+ total += convert_to_bytes(size)
+ size_str = human_readable_size(total, decimals=1)
+ if size_str is None:
+ print_warning('UNKNOWN', timestamp=False)
else:
- print_standard(size, timestamp=False)
+ print_standard(size_str, timestamp=False)
def show_user_data_summary(indent=8, width=32):
From f30d195cc444eb493d057b40b91e020ab2274f92 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:08:50 -0600
Subject: [PATCH 25/27] Updated Explorer registry entries
---
.bin/Scripts/settings/setup.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index c11073ec..ec3219a2 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -134,10 +134,12 @@ SETTINGS_EXPLORER_USER = {
},
# Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
+ 'Invalid modes': ['Cur'],
'DWORD Items': {'PeopleBand': 0},
},
# Hide Search button / box
r'Software\Microsoft\Windows\CurrentVersion\Search': {
+ 'Invalid modes': ['Cur'],
'DWORD Items': {'SearchboxTaskbarMode': 0},
},
}
From 3007c22c41d0c2fd1d2657938a753ffd5fb7a60f Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:40:25 -0600
Subject: [PATCH 26/27] Added Windows Update sections
---
.bin/Scripts/functions/windows_updates.py | 143 ++++++++++++++++++++++
.bin/Scripts/windows_updates.py | 46 +++++++
2 files changed, 189 insertions(+)
create mode 100644 .bin/Scripts/functions/windows_updates.py
create mode 100644 .bin/Scripts/windows_updates.py
diff --git a/.bin/Scripts/functions/windows_updates.py b/.bin/Scripts/functions/windows_updates.py
new file mode 100644
index 00000000..3618fbb2
--- /dev/null
+++ b/.bin/Scripts/functions/windows_updates.py
@@ -0,0 +1,143 @@
+# Wizard Kit: Functions - Windows updates
+
+from functions.common import *
+
+
+# Functions
+def delete_folder(folder_path):
+ """Near-useless wrapper for shutil.rmtree."""
+ shutil.rmtree(folder_path)
+
+
+def disable_service(service_name):
+ """Set service startup to disabled."""
+ run_program(['sc', 'config', service_name, 'start=', 'disabled'])
+
+ # Verify service was disabled
+ start_type = get_service_start_type(service_name)
+ if not start_type.lower().startswith('disabled'):
+ raise GenericError('Failed to disable service {}'.format(service_name))
+
+
+def disable_windows_updates():
+ """Disable windows updates and clear SoftwareDistribution folder."""
+ indent=2
+ width=52
+ update_folders = [
+ r'{WINDIR}\SoftwareDistribution'.format(**global_vars['Env']),
+ r'{SYSTEMDRIVE}\$WINDOWS.~BT'.format(**global_vars['Env']),
+ ]
+
+ for service in ('wuauserv', 'bits'):
+ # Stop service
+ result = try_and_print(
+ 'Stopping service {}...'.format(service),
+ indent=indent, width=width,
+ function=stop_service, service_name=service)
+ if not result['CS']:
+ result = try_and_print(
+ 'Stopping service {}...'.format(service),
+ indent=indent, width=width,
+ function=stop_service, service_name=service)
+ if not result['CS']:
+ raise GenericError('Service {} could not be stopped.'.format(service))
+
+ # Disable service
+ result = try_and_print(
+ 'Disabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=disable_service, service_name=service)
+ if not result['CS']:
+ result = try_and_print(
+ 'Disabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=disable_service, service_name=service)
+ if not result['CS']:
+ raise GenericError('Service {} could not be disabled.'.format(service))
+
+ # Delete update folders
+ for folder_path in update_folders:
+ if os.path.exists(folder_path):
+ result = try_and_print(
+ 'Deleting folder {}...'.format(folder_path),
+ indent=indent, width=width,
+ function=delete_folder, folder_path=folder_path)
+ if not result['CS']:
+ raise GenericError('Failed to remove folder {}'.format(folder_path))
+
+
+def enable_service(service_name, start_type='auto'):
+ """Enable service by setting start type."""
+ run_program(['sc', 'config', service_name, 'start=', start_type])
+
+
+def enable_windows_updates(silent=False):
+ """Enable windows updates"""
+ indent=2
+ width=52
+
+ for service in ('bits', 'wuauserv'):
+ # Enable service
+ start_type = 'auto'
+ if service == 'wuauserv':
+ start_type = 'demand'
+ if silent:
+ try:
+ enable_service(service, start_type=start_type)
+ except Exception:
+ # Try again
+ enable_service(service, start_type=start_type)
+ else:
+ result = try_and_print(
+ 'Enabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=enable_service, service_name=service, start_type=start_type)
+ if not result['CS']:
+ result = try_and_print(
+ 'Enabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=enable_service, service_name=service, start_type=start_type)
+ if not result['CS']:
+ raise GenericError('Service {} could not be enabled.'.format(service))
+
+
+def get_service_status(service_name):
+ """Get service status using psutil, returns str."""
+ status = 'Unknown'
+ try:
+ service = psutil.win_service_get(service_name)
+ status = service.status()
+ except psutil.NoSuchProcess:
+ # Ignore and return 'Unknown' below
+ pass
+
+ return status
+
+
+def get_service_start_type(service_name):
+ """Get service startup type using psutil, returns str."""
+ start_type = 'Unknown'
+ try:
+ service = psutil.win_service_get(service_name)
+ start_type = service.start_type()
+ except psutil.NoSuchProcess:
+ # Ignore and return 'Unknown' below
+ pass
+
+ return start_type
+
+
+def stop_service(service_name):
+ """Stop service."""
+ run_program(['net', 'stop', service_name], check=False)
+
+ # Verify service was stopped
+ status = get_service_status(service_name)
+ if not status.lower().startswith('stopped'):
+ raise GenericError('Failed to stop service {}'.format(service_name))
+
+
+if __name__ == '__main__':
+ print("This file is not meant to be called directly.")
+
+# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/windows_updates.py b/.bin/Scripts/windows_updates.py
new file mode 100644
index 00000000..e29f8c48
--- /dev/null
+++ b/.bin/Scripts/windows_updates.py
@@ -0,0 +1,46 @@
+# Wizard Kit: Windows updates
+
+import os
+import sys
+
+# Init
+sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+from functions.windows_updates import *
+init_global_vars()
+os.system('title {}: Windows Updates Tool'.format(KIT_NAME_FULL))
+set_log_file('Windows Updates Tool.log')
+
+if __name__ == '__main__':
+ try:
+ clear_screen()
+ print_info('{}: Windows Updates Tool\n'.format(KIT_NAME_FULL))
+
+ # Check args
+ if '--disable' in sys.argv:
+ disable_windows_updates()
+ elif '--enable' in sys.argv:
+ enable_windows_updates()
+ else:
+ print_error('Bad mode.')
+ abort()
+
+ # Done
+ exit_script()
+ except GenericError as err:
+ # Failed to complete request, show error(s) and prompt tech
+ print_standard(' ')
+ for line in str(err).splitlines():
+ print_warning(line)
+ print_standard(' ')
+ print_error('Error(s) encountered, see above.')
+ print_standard(' ')
+ if '--disable' in sys.argv:
+ print_standard('Please reboot and try again.')
+ pause('Press Enter to exit... ')
+ exit_script(1)
+ except SystemExit as sys_exit:
+ exit_script(sys_exit.code)
+ except:
+ major_exception()
+
+# vim: sts=2 sw=2 ts=2
From b95586a590cc63949efa7ad9d34954a11f08c381 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:42:31 -0600
Subject: [PATCH 27/27] Moved to a unified system setup script
* Replaces:
* Install SW Bundle
* New System Setup
* User Checklist
* System Checklist
---
.bin/Scripts/functions/common.py | 6 +
.bin/Scripts/functions/setup.py | 4 +
.bin/Scripts/new_system_setup.py | 160 -------------
.bin/Scripts/settings/launchers.py | 62 ++---
.bin/Scripts/system_checklist.py | 133 -----------
.bin/Scripts/system_setup.py | 354 +++++++++++++++++++++++++++++
.bin/Scripts/user_checklist.py | 90 --------
7 files changed, 397 insertions(+), 412 deletions(-)
delete mode 100644 .bin/Scripts/new_system_setup.py
delete mode 100644 .bin/Scripts/system_checklist.py
create mode 100644 .bin/Scripts/system_setup.py
delete mode 100644 .bin/Scripts/user_checklist.py
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 2828bcb4..689cc85f 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -91,6 +91,12 @@ class SecureBootNotAvailError(Exception):
class SecureBootUnknownError(Exception):
pass
+class WindowsOutdatedError(Exception):
+ pass
+
+class WindowsUnsupportedError(Exception):
+ pass
+
# General functions
def abort(show_prompt=True):
diff --git a/.bin/Scripts/functions/setup.py b/.bin/Scripts/functions/setup.py
index ad575f75..f9f864e9 100644
--- a/.bin/Scripts/functions/setup.py
+++ b/.bin/Scripts/functions/setup.py
@@ -334,6 +334,10 @@ def open_device_manager():
popen_program(['mmc', 'devmgmt.msc'])
+def open_speedtest():
+ popen_program(['start', '', 'https://fast.com'], shell=True)
+
+
def open_windows_activation():
popen_program(['slui'])
diff --git a/.bin/Scripts/new_system_setup.py b/.bin/Scripts/new_system_setup.py
deleted file mode 100644
index 68e508d2..00000000
--- a/.bin/Scripts/new_system_setup.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# Wizard Kit: New system setup
-
-import os
-import sys
-
-# Init
-sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-from functions.activation import *
-from functions.browsers import *
-from functions.cleanup import *
-from functions.info import *
-from functions.product_keys import *
-from functions.setup import *
-from functions.sw_diags import *
-init_global_vars()
-os.system('title {}: New System Setup'.format(KIT_NAME_FULL))
-set_log_file('New System Setup.log')
-
-if __name__ == '__main__':
- other_results = {
- 'Error': {
- 'BIOSKeyNotFoundError': 'BIOS key not found',
- 'CalledProcessError': 'Unknown Error',
- 'FileNotFoundError': 'File not found',
- 'GenericError': 'Unknown Error',
- 'SecureBootDisabledError': 'Disabled',
- },
- 'Warning': {
- 'GenericRepair': 'Repaired',
- 'NoProfilesError': 'No profiles found',
- 'NotInstalledError': 'Not installed',
- 'OSInstalledLegacyError': 'OS installed Legacy',
- 'SecureBootNotAvailError': 'Not available',
- 'SecureBootUnknownError': 'Unknown',
- 'UnsupportedOSError': 'Unsupported OS',
- }}
- try:
- stay_awake()
- clear_screen()
-
- # Check installed OS
- if os_is_unsupported(show_alert=False):
- print_warning('OS version not supported by this script')
- if not ask('Continue anyway? (NOT RECOMMENDED)'):
- abort()
-
- # Install Adobe Reader?
- answer_adobe_reader = ask('Install Adobe Reader?')
-
- # Install LibreOffice?
- answer_libreoffice = ask('Install LibreOffice?')
-
- # Install MSE?
- if global_vars['OS']['Version'] == '7':
- answer_mse = ask('Install MSE?')
- else:
- answer_mse = False
-
- # Install software
- print_info('Installing Programs')
- install_vcredists()
- if answer_adobe_reader:
- try_and_print(message='Adobe Reader DC...',
- function=install_adobe_reader, other_results=other_results)
- result = try_and_print(
- message='Ninite bundle...',
- function=install_ninite_bundle, cs='Started',
- mse=answer_mse, libreoffice=answer_libreoffice,
- other_results=other_results)
- for proc in result['Out']:
- # Wait for all processes to finish
- proc.wait()
-
- # Scan for supported browsers
- print_info('Scanning for browsers')
- scan_for_browsers()
-
- # Install extensions
- print_info('Installing Extensions')
- try_and_print(message='Classic Shell skin...',
- function=install_classicstart_skin,
- other_results=other_results)
- try_and_print(message='Google Chrome extensions...',
- function=install_chrome_extensions)
- try_and_print(message='Mozilla Firefox extensions...',
- function=install_firefox_extensions,
- other_results=other_results)
-
- # Configure software
- print_info('Configuring programs')
- install_adblock()
- if global_vars['OS']['Version'] == '10':
- try_and_print(message='ClassicStart...',
- function=config_classicstart, cs='Done')
- try_and_print(message='Explorer (user)...',
- function=config_explorer_user, cs='Done')
-
- # Configure system
- print_info('Configuring system')
- if global_vars['OS']['Version'] == '10':
- try_and_print(message='Explorer (system)...',
- function=config_explorer_system, cs='Done')
- try_and_print(message='Windows Updates...',
- function=config_windows_updates, cs='Done')
- try_and_print(message='Updating Clock...',
- function=update_clock, cs='Done')
-
- # Restart Explorer
- try_and_print(message='Restarting Explorer...',
- function=restart_explorer, cs='Done')
-
- # Summary
- print_info('Summary')
- try_and_print(message='Operating System:',
- function=show_os_name, ns='Unknown', silent_function=False)
- try_and_print(message='Activation:',
- function=show_os_activation, ns='Unknown', silent_function=False)
- if (not windows_is_activated()
- and global_vars['OS']['Version'] in ('8', '8.1', '10')):
- try_and_print(message='BIOS Activation:',
- function=activate_with_bios,
- other_results=other_results)
- try_and_print(message='Secure Boot Status:',
- function=check_secure_boot_status, other_results=other_results)
- try_and_print(message='Installed RAM:',
- function=show_installed_ram, ns='Unknown', silent_function=False)
- show_free_space()
- try_and_print(message='Installed Antivirus:',
- function=get_installed_antivirus, ns='Unknown',
- other_results=other_results, print_return=True)
-
- # Play audio, show devices, open Windows updates, and open Activation
- try_and_print(message='Opening Device Manager...',
- function=open_device_manager, cs='Started')
- try_and_print(message='Opening HWiNFO (Sensors)...',
- function=run_hwinfo_sensors, cs='Started', other_results=other_results)
- try_and_print(message='Opening Windows Updates...',
- function=open_windows_updates, cs='Started')
- if not windows_is_activated():
- try_and_print(message='Opening Windows Activation...',
- function=open_windows_activation, cs='Started')
- sleep(3)
- try_and_print(message='Running XMPlay...',
- function=run_xmplay, cs='Started', other_results=other_results)
- try:
- check_secure_boot_status(show_alert=True)
- except:
- # Only trying to open alert message boxes
- pass
-
- # Done
- print_standard('\nDone.')
- pause('Press Enter to exit...')
- exit_script()
- except SystemExit as sys_exit:
- exit_script(sys_exit.code)
- except:
- major_exception()
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/settings/launchers.py b/.bin/Scripts/settings/launchers.py
index 73a70923..7161c98e 100644
--- a/.bin/Scripts/settings/launchers.py
+++ b/.bin/Scripts/settings/launchers.py
@@ -1,35 +1,20 @@
-# Wizard Kit: Settings - Launchers
+'''Wizard Kit: Settings - Launchers'''
+# pylint: disable=line-too-long
+# vim: sts=2 sw=2 ts=2
LAUNCHERS = {
r'(Root)': {
- 'Activate Windows': {
- 'L_TYPE': 'PyScript',
- 'L_PATH': 'Scripts',
- 'L_ITEM': 'activate.py',
- 'L_ELEV': 'True',
- },
- 'New System Setup': {
- 'L_TYPE': 'PyScript',
- 'L_PATH': 'Scripts',
- 'L_ITEM': 'new_system_setup.py',
- 'L_ELEV': 'True',
- },
- 'System Checklist': {
- 'L_TYPE': 'PyScript',
- 'L_PATH': 'Scripts',
- 'L_ITEM': 'system_checklist.py',
- 'L_ELEV': 'True',
- },
'System Diagnostics': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'system_diagnostics.py',
'L_ELEV': 'True',
},
- 'User Checklist': {
+ 'System Setup': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
- 'L_ITEM': 'user_checklist.py',
+ 'L_ITEM': 'system_setup.py',
+ 'L_ELEV': 'True',
},
},
r'Data Recovery': {
@@ -55,6 +40,7 @@ LAUNCHERS = {
},
},
r'Data Transfers': {
+ # pylint: disable=bad-continuation
'FastCopy (as ADMIN)': {
'L_TYPE': 'Executable',
'L_PATH': 'FastCopy',
@@ -257,7 +243,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'erunt',
'L_ITEM': 'ERUNT.EXE',
- 'L_ARGS': '%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
+ 'L_ARGS': r'%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
'L_ELEV': 'True',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@@ -287,13 +273,13 @@ LAUNCHERS = {
r'Drivers': {
'Intel RST (Current Release)': {
'L_TYPE': 'Executable',
- 'L_PATH': '_Drivers\Intel RST',
+ 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': 'SetupRST_17.2.exe',
'L_7ZIP': 'SetupRST_17.2.exe',
},
'Intel RST (Previous Releases)': {
'L_TYPE': 'Folder',
- 'L_PATH': '_Drivers\Intel RST',
+ 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': '.',
'L_NCMD': 'True',
},
@@ -309,7 +295,7 @@ LAUNCHERS = {
},
'Snappy Driver Installer Origin': {
'L_TYPE': 'Executable',
- 'L_PATH': '_Drivers\SDIO',
+ 'L_PATH': r'_Drivers\SDIO',
'L_ITEM': 'SDIO.exe',
},
},
@@ -435,6 +421,12 @@ LAUNCHERS = {
},
},
r'Misc': {
+ 'Activate Windows': {
+ 'L_TYPE': 'PyScript',
+ 'L_PATH': 'Scripts',
+ 'L_ITEM': 'activate.py',
+ 'L_ELEV': 'True',
+ },
'Cleanup CBS Temp Files': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
@@ -452,6 +444,20 @@ LAUNCHERS = {
'L_PATH': 'ConEmu',
'L_ITEM': 'ConEmu.exe',
},
+ 'Disable Windows Updates': {
+ 'L_TYPE': 'PyScript',
+ 'L_PATH': 'Scripts',
+ 'L_ITEM': 'windows_updates.py',
+ 'L_ARGS': '--disable',
+ 'L_ELEV': 'True',
+ },
+ 'Enable Windows Updates': {
+ 'L_TYPE': 'PyScript',
+ 'L_PATH': 'Scripts',
+ 'L_ITEM': 'windows_updates.py',
+ 'L_ARGS': '--enable',
+ 'L_ELEV': 'True',
+ },
'Enter SafeMode': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
@@ -491,7 +497,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'XMPlay',
'L_ITEM': 'xmplay.exe',
- 'L_ARGS': '"%bin%\XMPlay\music.7z"',
+ 'L_ARGS': r'"%bin%\XMPlay\music.7z"',
},
},
r'Repairs': {
@@ -551,7 +557,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'RKill',
'L_ITEM': 'RKill.exe',
- 'L_ARGS': '-s -l %log_dir%\Tools\RKill.log',
+ 'L_ARGS': r'-s -l %log_dir%\Tools\RKill.log',
'L_ELEV': 'True',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@@ -594,5 +600,3 @@ LAUNCHERS = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/system_checklist.py b/.bin/Scripts/system_checklist.py
deleted file mode 100644
index a5b86e1e..00000000
--- a/.bin/Scripts/system_checklist.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# Wizard Kit: System Checklist
-
-import os
-import sys
-
-# Init
-sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-from functions.activation import *
-from functions.cleanup import *
-from functions.info import *
-from functions.product_keys import *
-from functions.setup import *
-from functions.sw_diags import *
-init_global_vars()
-os.system('title {}: System Checklist Tool'.format(KIT_NAME_FULL))
-set_log_file('System Checklist.log')
-
-if __name__ == '__main__':
- try:
- stay_awake()
- clear_screen()
- print_info('{}: System Checklist Tool\n'.format(KIT_NAME_FULL))
- ticket_number = get_ticket_number()
- other_results = {
- 'Error': {
- 'BIOSKeyNotFoundError': 'BIOS key not found',
- 'CalledProcessError': 'Unknown Error',
- 'FileNotFoundError': 'File not found',
- 'GenericError': 'Unknown Error',
- 'SecureBootDisabledError': 'Disabled',
- },
- 'Warning': {
- 'OSInstalledLegacyError': 'OS installed Legacy',
- 'SecureBootNotAvailError': 'Not available',
- 'SecureBootUnknownError': 'Unknown',
- }}
- if ENABLED_TICKET_NUMBERS:
- print_info('Starting System Checklist for Ticket #{}\n'.format(
- ticket_number))
-
- # Configure
- print_info('Configure')
- if global_vars['OS']['Version'] == '10':
- try_and_print(message='Explorer...',
- function=config_explorer_system, cs='Done')
- try_and_print(message='Windows Updates...',
- function=config_windows_updates, cs='Done')
- try_and_print(message='Updating Clock...',
- function=update_clock, cs='Done')
-
- # Restart Explorer
- try_and_print(message='Restarting Explorer...',
- function=restart_explorer, cs='Done')
-
- # Cleanup
- print_info('Cleanup')
- try_and_print(message='AdwCleaner...',
- function=cleanup_adwcleaner, cs='Done', other_results=other_results)
- try_and_print(message='Desktop...',
- function=cleanup_desktop, cs='Done')
- try_and_print(message='{}...'.format(KIT_NAME_FULL),
- function=delete_empty_folders, cs='Done',
- folder_path=global_vars['ClientDir'])
-
- # Export system info
- print_info('Backup System Information')
- try_and_print(message='AIDA64 reports...',
- function=run_aida64, cs='Done', other_results=other_results)
- try_and_print(message='File listing...',
- function=backup_file_list, cs='Done', other_results=other_results)
- try_and_print(message='Power plans...',
- function=backup_power_plans, cs='Done')
- try_and_print(message='Product Keys...', other_results=other_results,
- function=run_produkey, cs='Done')
- try_and_print(message='Registry...',
- function=backup_registry, cs='Done', other_results=other_results)
-
- # User data
- print_info('User Data')
- show_user_data_summary()
-
- # Summary
- print_info('Summary')
- try_and_print(message='Operating System:',
- function=show_os_name, ns='Unknown', silent_function=False)
- try_and_print(message='Activation:',
- function=show_os_activation, ns='Unknown', silent_function=False)
- if (not windows_is_activated()
- and global_vars['OS']['Version'] in ('8', '8.1', '10')):
- try_and_print(message='BIOS Activation:',
- function=activate_with_bios,
- other_results=other_results)
- try_and_print(message='Secure Boot Status:',
- function=check_secure_boot_status, other_results=other_results)
- try_and_print(message='Installed RAM:',
- function=show_installed_ram, ns='Unknown', silent_function=False)
- show_free_space()
- try_and_print(message='Installed Antivirus:',
- function=get_installed_antivirus, ns='Unknown',
- other_results=other_results, print_return=True)
- try_and_print(message='Installed Office:',
- function=get_installed_office, ns='Unknown',
- other_results=other_results, print_return=True)
-
- # Play audio, show devices, open Windows updates, and open Activation
- try_and_print(message='Opening Device Manager...',
- function=open_device_manager, cs='Started')
- try_and_print(message='Opening HWiNFO (Sensors)...',
- function=run_hwinfo_sensors, cs='Started', other_results=other_results)
- try_and_print(message='Opening Windows Updates...',
- function=open_windows_updates, cs='Started')
- if not windows_is_activated():
- try_and_print(message='Opening Windows Activation...',
- function=open_windows_activation, cs='Started')
- sleep(3)
- try_and_print(message='Running XMPlay...',
- function=run_xmplay, cs='Started', other_results=other_results)
- try:
- check_secure_boot_status(show_alert=True)
- except:
- # Only trying to open alert message boxes
- pass
-
- # Done
- print_standard('\nDone.')
- pause('Press Enter exit...')
- exit_script()
- except SystemExit as sys_exit:
- exit_script(sys_exit.code)
- except:
- major_exception()
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/system_setup.py b/.bin/Scripts/system_setup.py
new file mode 100644
index 00000000..5d49b26d
--- /dev/null
+++ b/.bin/Scripts/system_setup.py
@@ -0,0 +1,354 @@
+'''Wizard Kit: System Setup'''
+# pylint: disable=wildcard-import,wrong-import-position
+# vim: sts=2 sw=2 ts=2
+
+import os
+import sys
+
+# Init
+sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+from collections import OrderedDict
+from functions.activation import *
+from functions.browsers import *
+from functions.cleanup import *
+from functions.info import *
+from functions.product_keys import *
+from functions.setup import *
+from functions.sw_diags import *
+from functions.windows_updates import *
+init_global_vars()
+os.system('title {}: System Setup'.format(KIT_NAME_FULL))
+set_log_file('System Setup.log')
+
+
+# STATIC VARIABLES
+# pylint: disable=bad-whitespace,line-too-long
+OTHER_RESULTS = {
+ 'Error': {
+ 'BIOSKeyNotFoundError': 'BIOS KEY NOT FOUND',
+ 'CalledProcessError': 'UNKNOWN ERROR',
+ 'FileNotFoundError': 'FILE NOT FOUND',
+ 'GenericError': 'UNKNOWN ERROR',
+ 'Not4KAlignedError': 'FALSE',
+ 'SecureBootDisabledError': 'DISABLED',
+ 'WindowsUnsupportedError': 'UNSUPPORTED',
+ },
+ 'Warning': {
+ 'GenericRepair': 'REPAIRED',
+ 'NoProfilesError': 'NO PROFILES FOUND',
+ 'NotInstalledError': 'NOT INSTALLED',
+ 'OSInstalledLegacyError': 'OS INSTALLED LEGACY',
+ 'SecureBootNotAvailError': 'NOT AVAILABLE',
+ 'SecureBootUnknownError': 'UNKNOWN',
+ 'UnsupportedOSError': 'UNSUPPORTED OS',
+ 'WindowsOutdatedError': 'OUTDATED',
+ },
+ }
+SETUP_ACTIONS = OrderedDict({
+ # Install software
+ 'Installing Programs': {'Info': True},
+ 'VCR': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_vcredists, 'Just run': True,},
+ 'LibreOffice': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_libreoffice,
+ 'If answer': 'LibreOffice', 'KWArgs': {'quickstart': False, 'register_mso_types': True, 'use_mso_formats': True, 'vcredist': False},
+ },
+ 'Ninite bundle': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_ninite_bundle, 'KWArgs': {'cs': 'STARTED'},},
+
+ # Browsers
+ 'Scanning for browsers': {'Info': True},
+ 'Scan': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': scan_for_browsers, 'Just run': True, 'KWArgs': {'skip_ie': True},},
+ 'Backing up browsers': {'Info': True},
+ 'Backup browsers': {'New': False, 'Fab': True, 'Cur': True, 'HW': False, 'Function': backup_browsers, 'Just run': True,},
+
+ # Install extensions
+ 'Installing Extensions': {'Info': True},
+ 'Classic Shell skin': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Function': install_classicstart_skin, 'Win10 only': True,},
+ 'Chrome extensions': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_chrome_extensions,},
+ 'Firefox extensions': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_firefox_extensions,},
+
+ # Configure software'
+ 'Configuring Programs': {'Info': True},
+ 'Browser add-ons': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_adblock, 'Just run': True,
+ 'Pause': 'Please enable uBlock Origin for all browsers',
+ },
+ 'Classic Start': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Function': config_classicstart, 'Win10 only': True,},
+ 'Config Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': config_windows_updates, 'Win10 only': True,},
+ 'Enable Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': enable_windows_updates, 'KWArgs': {'silent': True},},
+ 'Explorer (system)': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': config_explorer_system, 'Win10 only': True,},
+ 'Explorer (user)': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': config_explorer_user, 'Win10 only': True,},
+ 'Restart Explorer': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': restart_explorer,},
+ 'Update Clock': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': update_clock,},
+
+ # Cleanup
+ 'Cleaning up': {'Info': True},
+ 'AdwCleaner': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': cleanup_adwcleaner,},
+ 'Desktop': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': cleanup_desktop,},
+ 'KIT_NAME_FULL': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': delete_empty_folders,},
+
+ # System Info
+ 'Exporting system info': {'Info': True},
+ 'AIDA64 Report': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': run_aida64,},
+ 'File listing': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': backup_file_list,},
+ 'Power plans': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': backup_power_plans,},
+ 'Product Keys': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_produkey,},
+ 'Registry': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': backup_registry,},
+
+ # Show Summary
+ 'Summary': {'Info': True},
+ 'Operating System': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_os_name, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_os_activation, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'BIOS Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': activate_with_bios, 'If not activated': True,},
+ 'Secure Boot': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': check_secure_boot_status, 'KWArgs': {'show_alert': False},},
+ 'Installed RAM': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_installed_ram, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Temp size': {'New': False, 'Fab': False, 'Cur': True, 'HW': False, 'Function': show_temp_files_size, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Show free space': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_free_space, 'Just run': True,},
+ 'Installed AV': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': get_installed_antivirus, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
+ 'Installed Office': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': get_installed_office, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
+ 'Partitions 4K aligned': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': check_4k_alignment, 'KWArgs': {'cs': 'TRUE', 'ns': 'FALSE'},},
+
+ # Open things
+ 'Opening Programs': {'Info': True},
+ 'Device Manager': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': open_device_manager, 'KWArgs': {'cs': 'STARTED'},},
+ 'HWiNFO sensors': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_hwinfo_sensors, 'KWArgs': {'cs': 'STARTED'},},
+ 'Speed test': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': open_speedtest, 'KWArgs': {'cs': 'STARTED'},},
+ 'Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': open_windows_updates, 'KWArgs': {'cs': 'STARTED'},},
+ 'Windows Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': open_windows_activation, 'If not activated': True, 'KWArgs': {'cs': 'STARTED'},},
+ 'Sleep': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': sleep, 'Just run': True, 'KWArgs': {'seconds': 3},},
+ 'XMPlay': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_xmplay, 'KWArgs': {'cs': 'STARTED'},},
+ })
+SETUP_ACTION_KEYS = (
+ 'Function',
+ 'If not activated',
+ 'Info',
+ 'Just run',
+ 'KWArgs',
+ 'Pause',
+ )
+SETUP_QUESTIONS = {
+ # AV
+ 'MSE': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
+
+ # LibreOffice
+ 'LibreOffice': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
+
+ # Ninite
+ 'Base': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Ninite': True},
+ 'Missing': {'New': False, 'Fab': True, 'Cur': False, 'HW': False, 'Ninite': True},
+ 'Standard': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Ninite': True},
+ }
+# pylint: enable=bad-whitespace,line-too-long
+
+
+# Functions
+def check_os_and_abort():
+ """Check OS and prompt to abort if not supported."""
+ result = try_and_print(
+ message='OS support status...',
+ function=check_os_support_status,
+ cs='GOOD',
+ )
+ if not result['CS'] and 'Unsupported' in result['Error']:
+ print_warning('OS version not supported by this script')
+ if not ask('Continue anyway? (NOT RECOMMENDED)'):
+ abort()
+
+
+def get_actions(setup_mode, answers):
+ """Get actions to perform based on setup_mode, returns OrderedDict."""
+ actions = OrderedDict({})
+ for _key, _val in SETUP_ACTIONS.items():
+ _action = {}
+ _if_answer = _val.get('If answer', False)
+ _win10_only = _val.get('Win10 only', False)
+
+ # Set enabled status
+ _enabled = _val.get(setup_mode, False)
+ if _if_answer:
+ _enabled = _enabled and answers[_if_answer]
+ if _win10_only:
+ _enabled = _enabled and global_vars['OS']['Version'] == '10'
+ _action['Enabled'] = _enabled
+
+ # Set other keys
+ for _sub_key in SETUP_ACTION_KEYS:
+ _action[_sub_key] = _val.get(_sub_key, None)
+
+ # Fix KWArgs
+ if _action.get('KWArgs', {}) is None:
+ _action['KWArgs'] = {}
+
+ # Handle "special" actions
+ if _key == 'KIT_NAME_FULL':
+ # Cleanup WK folders
+ _key = KIT_NAME_FULL
+ _action['KWArgs'] = {'folder_path': global_vars['ClientDir']}
+ elif _key == 'Ninite bundle':
+ # Add install_ninite_bundle() kwargs
+ _action['KWArgs'].update({
+ kw.lower(): kv for kw, kv in answers.items()
+ if SETUP_QUESTIONS.get(kw, {}).get('Ninite', False)
+ })
+ elif _key == 'Explorer (user)':
+ # Explorer settings (user)
+ _action['KWArgs'] = {'setup_mode': setup_mode}
+
+ # Add to dict
+ actions[_key] = _action
+
+ return actions
+
+
+def get_answers(setup_mode):
+ """Get setup answers based on setup_mode and user input, returns dict."""
+ answers = {k: v.get(setup_mode, False) for k, v in SETUP_QUESTIONS.items()}
+
+ # Answer setup questions as needed
+ if answers['MSE'] is None and global_vars['OS']['Version'] == '7':
+ answers.update(get_av_selection())
+
+ if answers['LibreOffice'] is None:
+ answers['LibreOffice'] = ask('Install LibreOffice?')
+
+ return answers
+
+
+def get_av_selection():
+ """Get AV selection."""
+ av_answers = {
+ 'MSE': False,
+ }
+ av_options = [
+ {
+ 'Name': 'Microsoft Security Essentials',
+ 'Disabled': global_vars['OS']['Version'] not in ['7'],
+ },
+ ]
+ actions = [
+ {'Name': 'None', 'Letter': 'N'},
+ {'Name': 'Quit', 'Letter': 'Q'},
+ ]
+
+ # Show menu
+ selection = menu_select(
+ 'Please select an option to install',
+ main_entries=av_options,
+ action_entries=actions)
+ if selection.isnumeric():
+ index = int(selection) - 1
+ if 'Microsoft' in av_options[index]['Name']:
+ av_answers['MSE'] = True
+ elif selection == 'Q':
+ abort()
+
+ return av_answers
+
+
+def get_mode():
+ """Get mode via menu_select, returns str."""
+ setup_mode = None
+ mode_options = [
+ {'Name': 'New', 'Display Name': 'New / Clean install (no data)'},
+ {'Name': 'Data', 'Display Name': 'Clean install with data migration'},
+ {'Name': 'Cur', 'Display Name': 'Original OS (post-repair or overinstall)'},
+ {'Name': 'HW', 'Display Name': 'Hardware service (i.e. no software work)'},
+ ]
+ actions = [
+ {'Name': 'Quit', 'Letter': 'Q'},
+ ]
+
+ # Get selection
+ selection = menu_select(
+ 'Please select a setup mode',
+ main_entries=mode_options,
+ action_entries=actions)
+ if selection.isnumeric():
+ index = int(selection) - 1
+ setup_mode = mode_options[index]['Name']
+ elif selection == 'Q':
+ abort()
+
+ return setup_mode
+
+
+def main():
+ """Main function."""
+ stay_awake()
+ clear_screen()
+
+ # Check installed OS
+ check_os_and_abort()
+
+ # Get setup mode
+ setup_mode = get_mode()
+
+ # Get answers to setup questions
+ answers = get_answers(setup_mode)
+
+ # Get actions to perform
+ actions = get_actions(setup_mode, answers)
+
+ # Perform actions
+ for action, values in actions.items():
+ kwargs = values.get('KWArgs', {})
+
+ # Print info lines
+ if values.get('Info', False):
+ print_info(action)
+ continue
+
+ # Print disabled actions
+ if not values.get('Enabled', False):
+ show_data(
+ message='{}...'.format(action),
+ data='DISABLED',
+ warning=True,
+ )
+ continue
+
+ # Check Windows activation if requested
+ if values.get('If not activated', False) and windows_is_activated():
+ # Skip
+ continue
+
+ # Run function
+ if values.get('Just run', False):
+ values['Function'](**kwargs)
+ else:
+ result = try_and_print(
+ message='{}...'.format(action),
+ function=values['Function'],
+ other_results=OTHER_RESULTS,
+ **kwargs)
+
+ # Wait for Ninite proc(s)
+ if action == 'Ninite bundle':
+ print_standard('Waiting for installations to finish...')
+ try:
+ for proc in result['Out']:
+ proc.wait()
+ except KeyboardInterrupt:
+ pass
+
+ # Pause
+ if values.get('Pause', False):
+ print_standard(values['Pause'])
+ pause()
+
+ # Show alert box for SecureBoot issues
+ try:
+ check_secure_boot_status(show_alert=True)
+ except Exception: # pylint: disable=broad-except
+ # Ignoring exceptions since we just want to show the popup
+ pass
+
+ # Done
+ pause('Press Enter to exit... ')
+
+
+if __name__ == '__main__':
+ try:
+ main()
+ exit_script()
+ except SystemExit as sys_exit:
+ exit_script(sys_exit.code)
+ except: # pylint: disable=bare-except
+ major_exception()
diff --git a/.bin/Scripts/user_checklist.py b/.bin/Scripts/user_checklist.py
deleted file mode 100644
index 0abd88f3..00000000
--- a/.bin/Scripts/user_checklist.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# Wizard Kit: User Checklist
-
-import os
-import sys
-
-# Init
-sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-from functions.browsers import *
-from functions.cleanup import *
-from functions.setup import *
-init_global_vars()
-os.system('title {}: User Checklist Tool'.format(KIT_NAME_FULL))
-set_log_file('User Checklist ({USERNAME}).log'.format(**global_vars['Env']))
-
-if __name__ == '__main__':
- try:
- stay_awake()
- clear_screen()
- print_info('{}: User Checklist\n'.format(KIT_NAME_FULL))
- other_results = {
- 'Warning': {
- 'NotInstalledError': 'Not installed',
- 'NoProfilesError': 'No profiles found',
- }}
- answer_config_browsers = ask('Install adblock?')
- if answer_config_browsers:
- answer_reset_browsers = ask(
- 'Reset browsers to safe defaults first?')
- if global_vars['OS']['Version'] == '10':
- answer_config_classicshell = ask('Configure ClassicShell?')
- answer_config_explorer_user = ask('Configure Explorer?')
-
- # Cleanup
- print_info('Cleanup')
- try_and_print(message='Desktop...',
- function=cleanup_desktop, cs='Done')
-
- # Scan for supported browsers
- print_info('Scanning for browsers')
- scan_for_browsers()
-
- # Homepages
- print_info('Current homepages')
- list_homepages()
-
- # Backup
- print_info('Backing up browsers')
- backup_browsers()
-
- # Reset
- if answer_config_browsers and answer_reset_browsers:
- print_info('Resetting browsers')
- reset_browsers()
-
- # Configure
- print_info('Configuring programs')
- if answer_config_browsers:
- install_adblock()
- if global_vars['OS']['Version'] == '10':
- if answer_config_classicshell:
- try_and_print(message='ClassicStart...',
- function=config_classicstart, cs='Done')
- if answer_config_explorer_user:
- try_and_print(message='Explorer...',
- function=config_explorer_user, cs='Done')
- if (not answer_config_browsers
- and not answer_config_classicshell
- and not answer_config_explorer_user):
- print_warning(' Skipped')
- else:
- if not answer_config_browsers:
- print_warning(' Skipped')
-
- # Restart Explorer
- try_and_print(message='Restarting Explorer...',
- function=restart_explorer, cs='Done')
-
- # Run speedtest
- popen_program(['start', '', 'https://fast.com'], shell=True)
-
- # Done
- print_standard('\nDone.')
- pause('Press Enter to exit...')
- exit_script()
- except SystemExit as sys_exit:
- exit_script(sys_exit.code)
- except:
- major_exception()
-
-# vim: sts=2 sw=2 ts=2