Integrated downstream updates

* Fixes issue #113
This commit is contained in:
2Shirt 2019-06-11 18:55:23 -06:00
commit eb3624e9ad
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
34 changed files with 1661 additions and 986 deletions

View file

@ -4,6 +4,7 @@
import os import os
import re import re
import sys
import uuid import uuid
KNOWN_NETWORKS = '/root/known_networks' KNOWN_NETWORKS = '/root/known_networks'
@ -34,13 +35,21 @@ method=auto
''' '''
def get_user_name(): def get_user_name():
"""Get real user name, returns str.""" """Get user name, returns str."""
user = None user = None
# Get running user
if 'SUDO_USER' in os.environ: if 'SUDO_USER' in os.environ:
user = os.environ.get('SUDO_USER') user = os.environ.get('SUDO_USER')
else: else:
user = os.environ.get('USER') 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 return user
if __name__ == '__main__': if __name__ == '__main__':
@ -54,7 +63,7 @@ if __name__ == '__main__':
for ssid, password in known_networks.items(): for ssid, password in known_networks.items():
out_path = '{}/{}.nmconnection'.format( out_path = '{}/{}.nmconnection'.format(
'/etc/NetworkManager/system-connections', '/etc/NetworkManager/system-connections',
password, ssid,
) )
if not os.path.exists(out_path): if not os.path.exists(out_path):
with open(out_path, 'w') as f: with open(out_path, 'w') as f:

View file

@ -1,6 +1,6 @@
#!/bin/env python3 #!/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 # vim: sts=2 sw=2 ts=2
"""Wizard Kit: UFD build tool""" """Wizard Kit: UFD build tool"""
@ -54,16 +54,8 @@ if __name__ == '__main__':
confirm_selections(args) confirm_selections(args)
# Prep UFD # Prep UFD
print_info('Prep UFD') if not args['--update']:
if args['--update']: print_info('Prep UFD')
# Remove arch folder
try_and_print(
indent=2,
message='Removing Linux...',
function=remove_arch,
)
else:
# Format and partition
prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr']) prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr'])
# Mount UFD # Mount UFD
@ -73,8 +65,17 @@ if __name__ == '__main__':
function=mount, function=mount,
mount_source=find_first_partition(ufd_dev), mount_source=find_first_partition(ufd_dev),
mount_point='/mnt/UFD', 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 # Copy sources
print_standard(' ') print_standard(' ')
print_info('Copy Sources') print_info('Copy Sources')

View file

@ -38,7 +38,7 @@ if __name__ == '__main__':
if repair: if repair:
cs = 'Scheduled' cs = 'Scheduled'
else: else:
cs = 'CS' cs = 'No issues'
message = 'CHKDSK ({SYSTEMDRIVE})...'.format(**global_vars['Env']) message = 'CHKDSK ({SYSTEMDRIVE})...'.format(**global_vars['Env'])
try_and_print(message=message, function=run_chkdsk, try_and_print(message=message, function=run_chkdsk,
cs=cs, other_results=other_results, repair=repair) cs=cs, other_results=other_results, repair=repair)

View file

@ -149,11 +149,14 @@ def save_debug_reports(state, global_vars):
f.write('{}\n'.format(line)) f.write('{}\n'.format(line))
def upload_logdir(global_vars): def upload_logdir(global_vars, reason='Crash'):
"""Upload compressed LogDir to CRASH_SERVER.""" """Upload compressed LogDir to CRASH_SERVER."""
source = global_vars['LogDir'] source = global_vars['LogDir']
source = source[source.rfind('/')+1:] source = source[source.rfind('/')+1:]
dest = '{}.txz'.format(source) dest = 'HW-Diags_{reason}_{Date-Time}.txz'.format(
reason=reason,
**global_vars,
)
data = None data = None
# Compress LogDir # Compress LogDir
@ -166,7 +169,7 @@ def upload_logdir(global_vars):
data = f.read() data = f.read()
# Upload data # Upload data
url = '{}/Crash_{}.txz'.format(CRASH_SERVER['Url'], source) url = '{}/{}'.format(CRASH_SERVER['Url'], dest)
r = requests.put( r = requests.put(
url, url,
data=data, data=data,

View file

@ -32,8 +32,8 @@ def archive_all_users():
user_path = os.path.join(users_root, user_name) user_path = os.path.join(users_root, user_name)
appdata_local = os.path.join(user_path, r'AppData\Local') appdata_local = os.path.join(user_path, r'AppData\Local')
appdata_roaming = os.path.join(user_path, r'AppData\Roaming') appdata_roaming = os.path.join(user_path, r'AppData\Roaming')
valid_user &= os.path.exists(appdata_local) valid_user = valid_user and os.path.exists(appdata_local)
valid_user &= os.path.exists(appdata_roaming) valid_user = valid_user and os.path.exists(appdata_roaming)
if valid_user: if valid_user:
user_envs.append({ user_envs.append({
'USERNAME': user_name, 'USERNAME': user_name,
@ -325,7 +325,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
if just_firefox and browser_data[browser]['base'] != 'mozilla': if just_firefox and browser_data[browser]['base'] != 'mozilla':
continue continue
exe_path = browser_data[browser].get('exe_path', None) exe_path = browser_data[browser].get('exe_path', None)
function=run_program
if not exe_path: if not exe_path:
if browser_data[browser]['profiles']: if browser_data[browser]['profiles']:
print_standard( print_standard(
@ -375,7 +374,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
elif browser_data[browser]['base'] == 'ie': elif browser_data[browser]['base'] == 'ie':
urls.append(IE_GALLERY) urls.append(IE_GALLERY)
function=popen_program
# By using check=False we're skipping any return codes so # By using check=False we're skipping any return codes so
# it should only fail if the program can't be run # 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. # installation status.
try_and_print(message='{}...'.format(browser), try_and_print(message='{}...'.format(browser),
indent=indent, width=width, indent=indent, width=width,
cs='Done', function=function, cs='Started', function=popen_program,
cmd=[exe_path, *urls], check=False) 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): def list_homepages(indent=8, width=32):
"""List current homepages for reference.""" """List current homepages for reference."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['exe_path']] 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)) 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): def reset_browsers(indent=8, width=32):
"""Reset all detected browsers to safe defaults.""" """Reset all detected browsers to safe defaults."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['profiles']] 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) 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.""" """Scan system for any supported browsers."""
for name, details in sorted(SUPPORTED_BROWSERS.items()): for name, details in sorted(SUPPORTED_BROWSERS.items()):
if just_firefox and details['base'] != 'mozilla': if just_firefox and details['base'] != 'mozilla':
continue continue
try_and_print(message='{}...'.format(name), if silent:
function=get_browser_details, cs='Detected', try:
other_results=other_results, name=name) 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__': if __name__ == '__main__':

View file

@ -1,7 +1,9 @@
# Wizard Kit: Functions - Cleanup '''Wizard Kit: Functions - Cleanup'''
# pylint: disable=no-name-in-module,wildcard-import
from functions.common import * # vim: sts=2 sw=2 ts=2
from functions.setup import *
from settings.cleanup import *
def cleanup_adwcleaner(): def cleanup_adwcleaner():
"""Move AdwCleaner folders into the ClientDir.""" """Move AdwCleaner folders into the ClientDir."""
@ -75,8 +77,7 @@ def cleanup_desktop():
desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env']) desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env'])
for entry in os.scandir(desktop_path): for entry in os.scandir(desktop_path):
# JRT, RKill, Shortcut cleaner if DESKTOP_ITEMS.search(entry.name):
if re.search(r'^(JRT|RKill|sc-cleaner)', entry.name, re.IGNORECASE):
dest_name = r'{}\{}'.format(dest_folder, entry.name) dest_name = r'{}\{}'.format(dest_folder, entry.name)
dest_name = non_clobber_rename(dest_name) dest_name = non_clobber_rename(dest_name)
shutil.move(entry.path, dest_name) shutil.move(entry.path, dest_name)
@ -130,7 +131,14 @@ def delete_registry_value(hive, key, value):
winreg.DeleteValue(k, 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__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -64,10 +64,13 @@ class GenericRepair(Exception):
class MultipleInstallationsError(Exception): class MultipleInstallationsError(Exception):
pass pass
class NotInstalledError(Exception): class NoProfilesError(Exception):
pass pass
class NoProfilesError(Exception): class Not4KAlignedError(Exception):
pass
class NotInstalledError(Exception):
pass pass
class OSInstalledLegacyError(Exception): class OSInstalledLegacyError(Exception):
@ -88,6 +91,12 @@ class SecureBootNotAvailError(Exception):
class SecureBootUnknownError(Exception): class SecureBootUnknownError(Exception):
pass pass
class WindowsOutdatedError(Exception):
pass
class WindowsUnsupportedError(Exception):
pass
# General functions # General functions
def abort(show_prompt=True): def abort(show_prompt=True):
@ -164,18 +173,22 @@ def clear_screen():
def convert_to_bytes(size): def convert_to_bytes(size):
"""Convert human-readable size str to bytes and return an int.""" """Convert human-readable size str to bytes and return an int."""
size = str(size) 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: if tmp:
size = float(tmp.group(1)) size = float(tmp.group(1))
units = tmp.group(2) units = tmp.group(2)
if units == 'TB': if units == 'P':
size *= 1099511627776 size *= 1024 ** 5
elif units == 'GB': if units == 'T':
size *= 1073741824 size *= 1024 ** 4
elif units == 'MB': elif units == 'G':
size *= 1048576 size *= 1024 ** 3
elif units == 'KB': elif units == 'M':
size *= 1024 size *= 1024 ** 2
elif units == 'K':
size *= 1024 ** 1
elif units == 'B':
size *= 1024 ** 0
size = int(size) size = int(size)
else: else:
return -1 return -1
@ -294,20 +307,24 @@ def human_readable_size(size, decimals=0):
return '{size:>{width}} b'.format(size='???', width=width) return '{size:>{width}} b'.format(size='???', width=width)
# Convert to sensible units # Convert to sensible units
if size >= 1099511627776: if size >= 1024 ** 5:
size /= 1099511627776 size /= 1024 ** 5
units = 'Tb' units = 'PB'
elif size >= 1073741824: elif size >= 1024 ** 4:
size /= 1073741824 size /= 1024 ** 4
units = 'Gb' units = 'TB'
elif size >= 1048576: elif size >= 1024 ** 3:
size /= 1048576 size /= 1024 ** 3
units = 'Mb' units = 'GB'
elif size >= 1024: elif size >= 1024 ** 2:
size /= 1024 size /= 1024 ** 2
units = 'Kb' units = 'MB'
elif size >= 1024 ** 1:
size /= 1024 ** 1
units = 'KB'
else: else:
units = ' b' size /= 1024 ** 0
units = ' B'
# Return # Return
return '{size:>{width}.{decimals}f} {units}'.format( return '{size:>{width}.{decimals}f} {units}'.format(
@ -422,6 +439,8 @@ def non_clobber_rename(full_path):
def pause(prompt='Press Enter to continue... '): def pause(prompt='Press Enter to continue... '):
"""Simple pause implementation.""" """Simple pause implementation."""
if prompt[-1] != ' ':
prompt += ' '
input(prompt) input(prompt)

View file

@ -151,12 +151,16 @@ def is_valid_wim_file(item):
def get_mounted_volumes(): def get_mounted_volumes():
"""Get mounted volumes, returns dict.""" """Get mounted volumes, returns dict."""
cmd = [ cmd = [
'findmnt', '-J', '-b', '-i', 'findmnt',
'-t', ( '--list',
'--json',
'--bytes',
'--invert',
'--types', (
'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,' 'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,'
'devtmpfs,hugetlbfs,mqueue,proc,pstore,securityfs,sysfs,tmpfs' '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) json_data = get_json_from_command(cmd)
mounted_volumes = [] mounted_volumes = []
for item in json_data.get('filesystems', []): for item in json_data.get('filesystems', []):
@ -195,6 +199,8 @@ def mount_volumes(
volumes.update({child['name']: child}) volumes.update({child['name']: child})
for grandchild in child.get('children', []): for grandchild in child.get('children', []):
volumes.update({grandchild['name']: grandchild}) volumes.update({grandchild['name']: grandchild})
for great_grandchild in grandchild.get('children', []):
volumes.update({great_grandchild['name']: great_grandchild})
# Get list of mounted volumes # Get list of mounted volumes
mounted_volumes = get_mounted_volumes() mounted_volumes = get_mounted_volumes()
@ -212,7 +218,7 @@ def mount_volumes(
report[vol_path] = vol_data report[vol_path] = vol_data
elif 'children' in vol_data: elif 'children' in vol_data:
# Skip LVM/RAID partitions (the real volume is mounted separately) # 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): if vol_data.get('label', None):
vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label']) vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label'])
vol_data['show_data']['info'] = True vol_data['show_data']['info'] = True
@ -237,6 +243,7 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount': if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None vol_data['mount_point'] = None
else: else:
fstype = vol_data.get('fstype', 'Unknown FS')
size_used = human_readable_size( size_used = human_readable_size(
mounted_volumes[vol_path]['used'], mounted_volumes[vol_path]['used'],
decimals=1, decimals=1,
@ -250,8 +257,9 @@ def mount_volumes(
vol_data['mount_point'] = mounted_volumes[vol_path]['target'] vol_data['mount_point'] = mounted_volumes[vol_path]['target']
vol_data['show_data']['data'] = 'Mounted on {}'.format( vol_data['show_data']['data'] = 'Mounted on {}'.format(
mounted_volumes[vol_path]['target']) 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'], vol_data['show_data']['data'],
fstype,
size_used, size_used,
size_avail) size_avail)
@ -285,6 +293,14 @@ def mount_backup_shares(read_write=False):
def mount_network_share(server, read_write=False): def mount_network_share(server, read_write=False):
"""Mount a network share defined by server.""" """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: if read_write:
username = server['RW-User'] username = server['RW-User']
password = server['RW-Pass'] 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) error = r'Failed to mount \\{Name}\{Share} ({IP})'.format(**server)
success = 'Mounted {Name}'.format(**server) success = 'Mounted {Name}'.format(**server)
elif psutil.LINUX: elif psutil.LINUX:
# Make mountpoint
cmd = [ cmd = [
'sudo', 'mkdir', '-p', 'sudo', 'mkdir', '-p',
'/Backups/{Name}'.format(**server)] '/Backups/{Name}'.format(**server)]
run_program(cmd) 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 = [ cmd = [
'sudo', 'mount', 'sudo', 'mount',
'//{IP}/{Share}'.format(**server), '//{IP}/{Share}'.format(**server).replace('\\', '/'),
'/Backups/{Name}'.format(**server), '/Backups/{Name}'.format(**server),
'-o', '{}username={},password={}'.format( '-o', ','.join(cmd_options),
'' if read_write else 'ro,', ]
username,
password)] # Set result messages
warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format( warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format(
**server) **server)
error = 'Failed to mount /Backups/{Name}'.format(**server) error = 'Failed to mount /Backups/{Name}'.format(**server)

View file

@ -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 datetime
import pathlib import pathlib
import psutil
import pytz
import re import re
import signal
import stat import stat
import time import time
from operator import itemgetter
from collections import OrderedDict import pytz
from functions.data import * from functions.data import *
from functions.hw_diags import * from functions.hw_diags import *
from functions.json import * from functions.json import *
from functions.tmux import * from functions.tmux import *
from operator import itemgetter
from settings.ddrescue import * from settings.ddrescue import *
# Clases # Clases
class BaseObj(): class BaseObj():
# pylint: disable=missing-docstring
"""Base object used by DevObj, DirObj, and ImageObj.""" """Base object used by DevObj, DirObj, and ImageObj."""
def __init__(self, path): def __init__(self, path):
self.type = 'base' self.type = 'base'
@ -44,6 +44,7 @@ class BaseObj():
class BlockPair(): class BlockPair():
# pylint: disable=too-many-instance-attributes
"""Object to track data and methods together for source and dest.""" """Object to track data and methods together for source and dest."""
def __init__(self, mode, source, dest): def __init__(self, mode, source, dest):
self.mode = mode self.mode = mode
@ -60,9 +61,10 @@ class BlockPair():
if self.mode == 'clone': if self.mode == 'clone':
# Cloning # Cloning
self.dest_path = dest.path self.dest_path = dest.path
self.map_path = '{pwd}/Clone_{prefix}.map'.format( self.map_path = '{cwd}/Clone_{prefix}.map'.format(
pwd=os.path.realpath(global_vars['Env']['PWD']), cwd=os.path.realpath(os.getcwd()),
prefix=source.prefix) prefix=source.prefix,
)
else: else:
# Imaging # Imaging
self.dest_path = '{path}/{prefix}.dd'.format( self.dest_path = '{path}/{prefix}.dd'.format(
@ -105,19 +107,19 @@ class BlockPair():
def load_map_data(self): def load_map_data(self):
"""Load data from map file and set progress.""" """Load data from map file and set progress."""
map_data = read_map_file(self.map_path) map_data = read_map_file(self.map_path)
self.rescued_percent = map_data['rescued'] self.rescued = map_data.get('rescued', 0)
self.rescued = (self.rescued_percent * self.size) / 100 self.rescued_percent = (self.rescued / self.size) * 100
if map_data['full recovery']: if map_data['full recovery']:
self.pass_done = [True, True, True] self.pass_done = [True, True, True]
self.rescued = self.size self.rescued = self.size
self.status = ['Skipped', 'Skipped', 'Skipped'] self.status = ['Skipped', 'Skipped', 'Skipped']
elif map_data['non-tried'] > 0: elif map_data.get('non-tried', 0) > 0:
# Initial pass incomplete # Initial pass incomplete
pass pass
elif map_data['non-trimmed'] > 0: elif map_data.get('non-trimmed', 0) > 0:
self.pass_done = [True, False, False] self.pass_done = [True, False, False]
self.status = ['Skipped', 'Pending', 'Pending'] 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.pass_done = [True, True, False]
self.status = ['Skipped', 'Skipped', 'Pending'] self.status = ['Skipped', 'Skipped', 'Pending']
else: else:
@ -145,14 +147,15 @@ class BlockPair():
"""Update progress using map file.""" """Update progress using map file."""
if os.path.exists(self.map_path): if os.path.exists(self.map_path):
map_data = read_map_file(self.map_path) map_data = read_map_file(self.map_path)
self.rescued_percent = map_data.get('rescued', 0) self.rescued = map_data.get('rescued', 0)
self.rescued = (self.rescued_percent * self.size) / 100 self.rescued_percent = (self.rescued / self.size) * 100
self.status[pass_num] = get_formatted_status( self.status[pass_num] = get_formatted_status(
label='Pass {}'.format(pass_num+1), label='Pass {}'.format(pass_num+1),
data=(self.rescued/self.size)*100) data=(self.rescued/self.size)*100)
class DevObj(BaseObj): class DevObj(BaseObj):
# pylint: disable=too-many-instance-attributes
"""Block device object.""" """Block device object."""
def self_check(self): def self_check(self):
"""Verify that self.path points to a block device.""" """Verify that self.path points to a block device."""
@ -186,6 +189,7 @@ class DevObj(BaseObj):
self.update_filename_prefix() self.update_filename_prefix()
def update_filename_prefix(self): def update_filename_prefix(self):
# pylint: disable=attribute-defined-outside-init
"""Set filename prefix based on details.""" """Set filename prefix based on details."""
self.prefix = '{m_size}_{model}'.format( self.prefix = '{m_size}_{model}'.format(
m_size=self.model_size, m_size=self.model_size,
@ -205,6 +209,7 @@ class DevObj(BaseObj):
class DirObj(BaseObj): class DirObj(BaseObj):
"""Directory object."""
def self_check(self): def self_check(self):
"""Verify that self.path points to a directory.""" """Verify that self.path points to a directory."""
if not pathlib.Path(self.path).is_dir(): if not pathlib.Path(self.path).is_dir():
@ -222,6 +227,7 @@ class DirObj(BaseObj):
class ImageObj(BaseObj): class ImageObj(BaseObj):
"""Image file object."""
def self_check(self): def self_check(self):
"""Verify that self.path points to a file.""" """Verify that self.path points to a file."""
if not pathlib.Path(self.path).is_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 = get_device_report(self.loop_dev)
self.report = self.report.replace( self.report = self.report.replace(
self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)') 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(): class RecoveryState():
# pylint: disable=too-many-instance-attributes
"""Object to track BlockPair objects and overall state.""" """Object to track BlockPair objects and overall state."""
def __init__(self, mode, source, dest): def __init__(self, mode, source, dest):
self.mode = mode.lower() self.mode = mode.lower()
@ -270,6 +277,7 @@ class RecoveryState():
if mode not in ('clone', 'image'): if mode not in ('clone', 'image'):
raise GenericError('Unsupported mode') raise GenericError('Unsupported mode')
self.get_smart_source() self.get_smart_source()
self.set_working_dir()
def add_block_pair(self, source, dest): def add_block_pair(self, source, dest):
"""Run safety checks and append new BlockPair to internal list.""" """Run safety checks and append new BlockPair to internal list."""
@ -314,20 +322,134 @@ class RecoveryState():
# Safety checks passed # Safety checks passed
self.block_pairs.append(BlockPair(self.mode, source, dest)) 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): def current_pass_done(self):
"""Checks if pass is done for all block-pairs, returns bool.""" """Checks if pass is done for all block-pairs, returns bool."""
done = True done = True
for bp in self.block_pairs: for b_pair in self.block_pairs:
done &= bp.pass_done[self.current_pass] done = done and b_pair.pass_done[self.current_pass]
return done return done
def current_pass_min(self): def current_pass_min(self):
"""Gets minimum pass rescued percentage, returns float.""" """Gets minimum pass rescued percentage, returns float."""
min_percent = 100 min_percent = 100
for bp in self.block_pairs: for b_pair in self.block_pairs:
min_percent = min(min_percent, bp.rescued_percent) min_percent = min(min_percent, b_pair.rescued_percent)
return min_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): def get_smart_source(self):
"""Get source for SMART dispay.""" """Get source for SMART dispay."""
disk_path = self.source.path disk_path = self.source.path
@ -339,18 +461,15 @@ class RecoveryState():
def retry_all_passes(self): def retry_all_passes(self):
"""Mark all passes as pending for all block-pairs.""" """Mark all passes as pending for all block-pairs."""
self.finished = False self.finished = False
for bp in self.block_pairs: for b_pair in self.block_pairs:
bp.pass_done = [False, False, False] b_pair.pass_done = [False, False, False]
bp.status = ['Pending', 'Pending', 'Pending'] b_pair.status = ['Pending', 'Pending', 'Pending']
bp.fix_status_strings() b_pair.fix_status_strings()
self.set_pass_num() self.set_pass_num()
def self_checks(self): def self_checks(self):
"""Run self-checks and update state values.""" """Run self-checks and update state values."""
cmd = ['findmnt', '--json', '--target', os.getcwd()] 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) json_data = get_json_from_command(cmd)
# Abort if json_data is empty # Abort if json_data is empty
@ -361,23 +480,24 @@ class RecoveryState():
# Avoid saving map to non-persistent filesystem # Avoid saving map to non-persistent filesystem
fstype = json_data.get( fstype = json_data.get(
'filesystems', [{}])[0].get( 'filesystems', [{}])[0].get(
'fstype', 'unknown') 'fstype', 'unknown')
if fstype not in map_allowed_fstypes: if fstype not in RECOMMENDED_MAP_FSTYPES:
print_error( print_error(
"Map isn't being saved to a recommended filesystem ({})".format( "Map isn't being saved to a recommended filesystem ({})".format(
fstype.upper())) fstype.upper()))
print_info('Recommended types are: {}'.format( print_info('Recommended types are: {}'.format(
' / '.join(map_allowed_fstypes).upper())) ' / '.join(RECOMMENDED_MAP_FSTYPES).upper()))
print_standard(' ') print_standard(' ')
if not ask('Proceed anyways? (Strongly discouraged)'): if not ask('Proceed anyways? (Strongly discouraged)'):
raise GenericAbort() raise GenericAbort()
# Run BlockPair self checks and get total size # Run BlockPair self checks and get total size
self.total_size = 0 self.total_size = 0
for bp in self.block_pairs: for b_pair in self.block_pairs:
bp.self_check() b_pair.self_check()
self.resumed |= bp.resumed if b_pair.resumed:
self.total_size += bp.size self.resumed = True
self.total_size += b_pair.size
def set_pass_num(self): def set_pass_num(self):
"""Set current pass based on all block-pair's progress.""" """Set current pass based on all block-pair's progress."""
@ -385,8 +505,8 @@ class RecoveryState():
for pass_num in (2, 1, 0): for pass_num in (2, 1, 0):
# Iterate backwards through passes # Iterate backwards through passes
pass_done = True pass_done = True
for bp in self.block_pairs: for b_pair in self.block_pairs:
pass_done &= bp.pass_done[pass_num] pass_done = pass_done and b_pair.pass_done[pass_num]
if pass_done: if pass_done:
# All block-pairs reported being done # All block-pairs reported being done
# Set to next pass, unless we're on the last pass (2) # Set to next pass, unless we're on the last pass (2)
@ -404,6 +524,34 @@ class RecoveryState():
elif self.current_pass == 2: elif self.current_pass == 2:
self.current_pass_str = '3 "Scraping bad areas"' 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): def update_etoc(self):
"""Search ddrescue output for the current EToC, returns str.""" """Search ddrescue output for the current EToC, returns str."""
now = datetime.datetime.now(tz=self.timezone) now = datetime.datetime.now(tz=self.timezone)
@ -413,7 +561,7 @@ class RecoveryState():
# Just set to N/A (NOTE: this overrules the refresh rate below) # Just set to N/A (NOTE: this overrules the refresh rate below)
self.etoc = 'N/A' self.etoc = 'N/A'
return return
elif 'In Progress' not in self.status: if 'In Progress' not in self.status:
# Don't update when EToC is hidden # Don't update when EToC is hidden
return return
if now.second % ETOC_REFRESH_RATE != 0: if now.second % ETOC_REFRESH_RATE != 0:
@ -427,13 +575,14 @@ class RecoveryState():
# Capture main tmux pane # Capture main tmux pane
try: try:
text = tmux_capture_pane() text = tmux_capture_pane()
except Exception: except Exception: # pylint: disable=broad-except
# Ignore # Ignore
pass pass
# Search for EToC delta # Search for EToC delta
matches = re.findall(r'remaining time:.*$', text, re.MULTILINE) matches = re.findall(r'remaining time:.*$', text, re.MULTILINE)
if matches: if matches:
# pylint: disable=invalid-name
r = REGEX_REMAINING_TIME.search(matches[-1]) r = REGEX_REMAINING_TIME.search(matches[-1])
if r.group('na'): if r.group('na'):
self.etoc = 'N/A' self.etoc = 'N/A'
@ -450,7 +599,7 @@ class RecoveryState():
minutes=int(minutes), minutes=int(minutes),
seconds=int(seconds), seconds=int(seconds),
) )
except Exception: except Exception: # pylint: disable=broad-except
# Ignore and leave as raw string # Ignore and leave as raw string
pass pass
@ -460,15 +609,16 @@ class RecoveryState():
now = datetime.datetime.now(tz=self.timezone) now = datetime.datetime.now(tz=self.timezone)
_etoc = now + etoc_delta _etoc = now + etoc_delta
self.etoc = _etoc.strftime('%Y-%m-%d %H:%M %Z') self.etoc = _etoc.strftime('%Y-%m-%d %H:%M %Z')
except Exception: except Exception: # pylint: disable=broad-except
# Ignore and leave as current string # Ignore and leave as current string
pass pass
def update_progress(self): def update_progress(self):
# pylint: disable=attribute-defined-outside-init
"""Update overall progress using block_pairs.""" """Update overall progress using block_pairs."""
self.rescued = 0 self.rescued = 0
for bp in self.block_pairs: for b_pair in self.block_pairs:
self.rescued += bp.rescued self.rescued += b_pair.rescued
self.rescued_percent = (self.rescued / self.total_size) * 100 self.rescued_percent = (self.rescued / self.total_size) * 100
self.status_percent = get_formatted_status( self.status_percent = get_formatted_status(
label='Recovered:', data=self.rescued_percent) label='Recovered:', data=self.rescued_percent)
@ -477,26 +627,6 @@ class RecoveryState():
# Functions # 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): def create_path_obj(path):
"""Create Dev, Dir, or Image obj based on path given.""" """Create Dev, Dir, or Image obj based on path given."""
obj = None obj = None
@ -514,101 +644,16 @@ def create_path_obj(path):
def double_confirm_clone(): def double_confirm_clone():
"""Display warning and get 2nd confirmation, returns bool.""" """Display warning and get 2nd confirmation, returns bool."""
print_standard('\nSAFETY CHECK') print_standard('\nSAFETY CHECK')
print_warning('All data will be DELETED from the ' print_warning(
'destination device and partition(s) listed above.') 'All data will be DELETED from the '
print_warning('This is irreversible and will lead ' 'destination device and partition(s) listed above.'
'to {CLEAR}{RED}DATA LOSS.'.format(**COLORS)) )
print_warning(
'This is irreversible and will lead to {CLEAR}{RED}DATA LOSS.'.format(
**COLORS))
return ask('Asking again to confirm, is this correct?') 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): def get_device_details(dev_path):
"""Get device details via lsblk, returns JSON dict.""" """Get device details via lsblk, returns JSON dict."""
cmd = ['lsblk', '--json', '--output-all', '--paths', dev_path] cmd = ['lsblk', '--json', '--output-all', '--paths', dev_path]
@ -677,22 +722,22 @@ def get_dir_report(dir_path):
output.append('{BLUE}{label:<{width}}{line}{CLEAR}'.format( output.append('{BLUE}{label:<{width}}{line}{CLEAR}'.format(
label='PATH', label='PATH',
width=width, width=width,
line=line.replace('\n',''), line=line.replace('\n', ''),
**COLORS)) **COLORS))
else: else:
output.append('{path:<{width}}{line}'.format( output.append('{path:<{width}}{line}'.format(
path=dir_path, path=dir_path,
width=width, width=width,
line=line.replace('\n',''))) line=line.replace('\n', '')))
# Done # Done
return '\n'.join(output) 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.""" """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) size = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', size, re.IGNORECASE)
return convert_to_bytes(s) return convert_to_bytes(size)
def get_formatted_status(label, data): def get_formatted_status(label, data):
@ -700,13 +745,15 @@ def get_formatted_status(label, data):
data_width = SIDE_PANE_WIDTH - len(label) data_width = SIDE_PANE_WIDTH - len(label)
try: try:
data_str = '{data:>{data_width}.2f} %'.format( data_str = '{data:>{data_width}.2f} %'.format(
data=data, data=data,
data_width=data_width-2) data_width=data_width-2,
)
except ValueError: except ValueError:
# Assuming non-numeric data # Assuming non-numeric data
data_str = '{data:>{data_width}}'.format( data_str = '{data:>{data_width}}'.format(
data=data, data=data,
data_width=data_width) data_width=data_width,
)
status = '{label}{s_color}{data_str}{CLEAR}'.format( status = '{label}{s_color}{data_str}{CLEAR}'.format(
label=label, label=label,
s_color=get_status_color(data), s_color=get_status_color(data),
@ -715,19 +762,19 @@ def get_formatted_status(label, data):
return status 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.""" """Get color based on status, returns str."""
color = COLORS['CLEAR'] color = COLORS['CLEAR']
p_recovered = -1 p_recovered = -1
try: try:
p_recovered = float(s) p_recovered = float(status)
except ValueError: except ValueError:
# Status is either in lists below or will default to red # Status is either in lists below or will default to red
pass 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'] color = COLORS['CLEAR']
elif s in ('Skipped', 'Unknown'): elif status in ('Skipped', 'Unknown'):
color = COLORS['YELLOW'] color = COLORS['YELLOW']
elif p_recovered >= t_success: elif p_recovered >= t_success:
color = COLORS['GREEN'] color = COLORS['GREEN']
@ -742,9 +789,9 @@ def is_writable_dir(dir_obj):
"""Check if we have read-write-execute permissions, returns bool.""" """Check if we have read-write-execute permissions, returns bool."""
is_ok = True is_ok = True
path_st_mode = os.stat(dir_obj.path).st_mode 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_IRUSR
is_ok == is_ok and path_st_mode & stat.S_IWUSR 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_IXUSR
return is_ok return is_ok
@ -754,6 +801,7 @@ def is_writable_filesystem(dir_obj):
def menu_ddrescue(source_path, dest_path, run_mode): def menu_ddrescue(source_path, dest_path, run_mode):
# pylint: disable=too-many-branches
"""ddrescue menu.""" """ddrescue menu."""
source = None source = None
dest = None dest = None
@ -797,9 +845,8 @@ def menu_ddrescue(source_path, dest_path, run_mode):
raise GenericAbort() raise GenericAbort()
# Main menu # Main menu
clear_screen() state.build_outer_panes()
build_outer_panes(state) state.fix_tmux_panes(forced=True)
fix_tmux_panes(state, forced=True)
menu_main(state) menu_main(state)
# Done # Done
@ -808,6 +855,7 @@ def menu_ddrescue(source_path, dest_path, run_mode):
def menu_main(state): def menu_main(state):
# pylint: disable=too-many-branches,too-many-statements
"""Main menu is used to set ddrescue settings.""" """Main menu is used to set ddrescue settings."""
checkmark = '*' checkmark = '*'
if 'DISPLAY' in global_vars['Env']: if 'DISPLAY' in global_vars['Env']:
@ -818,16 +866,15 @@ def menu_main(state):
# Build menu # Build menu
main_options = [ main_options = [
{'Base Name': 'Auto continue (if recovery % over threshold)', {'Base Name': 'Auto continue (if recovery % over threshold)',
'Enabled': True}, 'Enabled': True},
{'Base Name': 'Retry (mark non-rescued sectors "non-tried")', {'Base Name': 'Retry (mark non-rescued sectors "non-tried")',
'Enabled': False}, 'Enabled': False},
{'Base Name': 'Reverse direction', 'Enabled': False}, {'Base Name': 'Reverse direction', 'Enabled': False},
] ]
actions = [ actions = [
{'Name': 'Start', 'Letter': 'S'}, {'Name': 'Start', 'Letter': 'S'},
{'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format( {'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format(**COLORS),
**COLORS), 'Letter': 'C'},
'Letter': 'C'},
{'Name': 'Quit', 'Letter': 'Q', 'CRLF': True}, {'Name': 'Quit', 'Letter': 'Q', 'CRLF': True},
] ]
@ -858,13 +905,13 @@ def menu_main(state):
elif selection == 'S': elif selection == 'S':
# Set settings for pass # Set settings for pass
pass_settings = [] pass_settings = []
for k, v in state.settings.items(): for option, option_data in state.settings.items():
if not v['Enabled']: if not option_data['Enabled']:
continue continue
if 'Value' in v: if 'Value' in option_data:
pass_settings.append('{}={}'.format(k, v['Value'])) pass_settings.append('{}={}'.format(option, option_data['Value']))
else: else:
pass_settings.append(k) pass_settings.append(option)
for opt in main_options: for opt in main_options:
if 'Auto' in opt['Base Name']: if 'Auto' in opt['Base Name']:
auto_run = opt['Enabled'] auto_run = opt['Enabled']
@ -887,7 +934,7 @@ def menu_main(state):
state.current_pass_min() < AUTO_PASS_1_THRESHOLD): state.current_pass_min() < AUTO_PASS_1_THRESHOLD):
auto_run = False auto_run = False
elif (state.current_pass == 1 and 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 auto_run = False
else: else:
auto_run = False auto_run = False
@ -916,13 +963,15 @@ def menu_settings(state):
# Build menu # Build menu
settings = [] settings = []
for k, v in sorted(state.settings.items()): for option, option_data in sorted(state.settings.items()):
if not v.get('Hidden', False): if not option_data.get('Hidden', False):
settings.append({'Base Name': k, 'Flag': k}) settings.append({'Base Name': option, 'Flag': option})
actions = [{'Name': 'Main Menu', 'Letter': 'M'}] actions = [{'Name': 'Main Menu', 'Letter': 'M'}]
# Show menu # Show menu
while True: while True:
# pylint: disable=invalid-name
# TODO: Clean up and/or replace with new menu-select function
for s in settings: for s in settings:
s['Name'] = '{}{}{}'.format( s['Name'] = '{}{}{}'.format(
s['Base Name'], s['Base Name'],
@ -959,25 +1008,27 @@ def menu_settings(state):
def read_map_file(map_path): def read_map_file(map_path):
"""Read map file with ddrescuelog and return data as dict.""" """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: try:
result = run_program(['ddrescuelog', '-t', map_path]) result = run_program(cmd, encoding='utf-8', errors='ignore')
except CalledProcessError: except CalledProcessError:
# (Grossly) assuming map_data hasn't been saved yet, return empty dict # (Grossly) assuming map_data hasn't been saved yet, return empty dict
return map_data return map_data
# Parse output # Parse output
for line in result.stdout.decode().splitlines(): for line in result.stdout.splitlines():
m = re.match( line = line.strip()
r'^\s*(?P<key>\S+):.*\(\s*(?P<value>\d+\.?\d*)%.*', line.strip()) _r = REGEX_DDRESCUE_LOG.search(line)
if m: if _r:
try: map_data[_r.group('key')] = convert_to_bytes('{size} {unit}B'.format(
map_data[m.group('key')] = float(m.group('value')) **_r.groupdict()))
except ValueError: map_data['pass completed'] = 'current status: finished' in line
raise GenericError('Failed to read map data')
m = re.match(r'.*current status:\s+(?P<status>.*)', line.strip())
if m:
map_data['pass completed'] = bool(m.group('status') == 'finished')
# Check if 100% done # Check if 100% done
try: try:
@ -991,6 +1042,7 @@ def read_map_file(map_path):
def run_ddrescue(state, pass_settings): def run_ddrescue(state, pass_settings):
# pylint: disable=too-many-branches,too-many-statements
"""Run ddrescue pass.""" """Run ddrescue pass."""
return_code = -1 return_code = -1
aborted = False aborted = False
@ -1005,8 +1057,8 @@ def run_ddrescue(state, pass_settings):
# Create SMART monitor pane # Create SMART monitor pane
state.smart_out = '{}/smart_{}.out'.format( state.smart_out = '{}/smart_{}.out'.format(
global_vars['TmpDir'], state.smart_source.name) global_vars['TmpDir'], state.smart_source.name)
with open(state.smart_out, 'w') as f: with open(state.smart_out, 'w') as _f:
f.write('Initializing...') _f.write('Initializing...')
state.panes['SMART'] = tmux_split_window( state.panes['SMART'] = tmux_split_window(
behind=True, lines=12, vertical=True, watch=state.smart_out) behind=True, lines=12, vertical=True, watch=state.smart_out)
@ -1016,19 +1068,19 @@ def run_ddrescue(state, pass_settings):
command=['sudo', 'journalctl', '-f']) command=['sudo', 'journalctl', '-f'])
# Fix layout # Fix layout
fix_tmux_panes(state, forced=True) state.fix_tmux_panes(forced=True)
# Run pass for each block-pair # Run pass for each block-pair
for bp in state.block_pairs: for b_pair in state.block_pairs:
if bp.pass_done[state.current_pass]: if b_pair.pass_done[state.current_pass]:
# Skip to next block-pair # Skip to next block-pair
continue continue
update_sidepane(state) update_sidepane(state)
# Set ddrescue cmd # Set ddrescue cmd
cmd = [ cmd = [
'ddrescue', *pass_settings, 'sudo', 'ddrescue', *pass_settings,
bp.source_path, bp.dest_path, bp.map_path] b_pair.source_path, b_pair.dest_path, b_pair.map_path]
if state.mode == 'clone': if state.mode == 'clone':
cmd.append('--force') cmd.append('--force')
if state.current_pass == 0: if state.current_pass == 0:
@ -1043,36 +1095,36 @@ def run_ddrescue(state, pass_settings):
# Start ddrescue # Start ddrescue
try: try:
clear_screen() clear_screen()
print_info('Current dev: {}'.format(bp.source_path)) print_info('Current dev: {}'.format(b_pair.source_path))
ddrescue_proc = popen_program(cmd) ddrescue_proc = popen_program(cmd)
i = 0 i = 0
while True: while True:
# Update SMART display (every 30 seconds) # Update SMART display (every 30 seconds)
if i % 30 == 0: if i % 30 == 0:
state.smart_source.get_smart_details() 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( report = state.smart_source.generate_attribute_report(
timestamp=True) timestamp=True)
for line in report: for line in report:
f.write('{}\n'.format(line)) _f.write('{}\n'.format(line))
i += 1 i += 1
# Update progress # Update progress
bp.update_progress(state.current_pass) b_pair.update_progress(state.current_pass)
update_sidepane(state) update_sidepane(state)
# Fix panes # Fix panes
fix_tmux_panes(state) state.fix_tmux_panes()
# Check if ddrescue has finished # Check if ddrescue has finished
try: try:
ddrescue_proc.wait(timeout=1) ddrescue_proc.wait(timeout=1)
sleep(2) sleep(2)
bp.update_progress(state.current_pass) b_pair.update_progress(state.current_pass)
update_sidepane(state) update_sidepane(state)
break break
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
# Catch to update smart/bp/sidepane # Catch to update smart/b_pair/sidepane
pass pass
except KeyboardInterrupt: except KeyboardInterrupt:
@ -1081,7 +1133,7 @@ def run_ddrescue(state, pass_settings):
ddrescue_proc.wait(timeout=10) ddrescue_proc.wait(timeout=10)
# Update progress/sidepane again # Update progress/sidepane again
bp.update_progress(state.current_pass) b_pair.update_progress(state.current_pass)
update_sidepane(state) update_sidepane(state)
# Was ddrescue aborted? # Was ddrescue aborted?
@ -1103,7 +1155,7 @@ def run_ddrescue(state, pass_settings):
break break
else: else:
# Mark pass finished # Mark pass finished
bp.finish_pass(state.current_pass) b_pair.finish_pass(state.current_pass)
update_sidepane(state) update_sidepane(state)
# Done # Done
@ -1119,6 +1171,8 @@ def run_ddrescue(state, pass_settings):
def select_parts(source_device): 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.""" """Select partition(s) or whole device, returns list of DevObj()s."""
selected_parts = [] selected_parts = []
children = source_device.details.get('children', []) children = source_device.details.get('children', [])
@ -1180,24 +1234,26 @@ def select_parts(source_device):
raise GenericAbort() raise GenericAbort()
# Build list of selected parts # Build list of selected parts
for d in dev_options: for _d in dev_options:
if d['Selected']: if _d['Selected']:
d['Dev'].model = source_device.model _d['Dev'].model = source_device.model
d['Dev'].model_size = source_device.model_size _d['Dev'].model_size = source_device.model_size
d['Dev'].update_filename_prefix() _d['Dev'].update_filename_prefix()
selected_parts.append(d['Dev']) selected_parts.append(_d['Dev'])
return selected_parts return selected_parts
def select_path(skip_device=None): 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.""" """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 selected_path = None
# Build menu # Build menu
path_options = [ path_options = [
{'Name': 'Current directory: {}'.format(wd), 'Path': wd}, {'Name': 'Current directory: {}'.format(work_dir), 'Path': work_dir},
{'Name': 'Local device', 'Path': None}, {'Name': 'Local device', 'Path': None},
{'Name': 'Enter manually', 'Path': None}] {'Name': 'Enter manually', 'Path': None}]
actions = [{'Name': 'Quit', 'Letter': 'Q'}] actions = [{'Name': 'Quit', 'Letter': 'Q'}]
@ -1212,9 +1268,9 @@ def select_path(skip_device=None):
raise GenericAbort() raise GenericAbort()
elif selection.isnumeric(): elif selection.isnumeric():
index = int(selection) - 1 index = int(selection) - 1
if path_options[index]['Path'] == wd: if path_options[index]['Path'] == work_dir:
# Current directory # Current directory
selected_path = DirObj(wd) selected_path = DirObj(work_dir)
elif path_options[index]['Name'] == 'Local device': elif path_options[index]['Name'] == 'Local device':
# Local device # Local device
@ -1230,15 +1286,15 @@ def select_path(skip_device=None):
# Select volume # Select volume
vol_options = [] vol_options = []
for k, v in sorted(report.items()): for _k, _v in sorted(report.items()):
disabled = v['show_data']['data'] == 'Failed to mount' disabled = _v['show_data']['data'] == 'Failed to mount'
if disabled: if disabled:
name = '{name} (Failed to mount)'.format(**v) name = '{name} (Failed to mount)'.format(**_v)
else: else:
name = '{name} (mounted on "{mount_point}")'.format(**v) name = '{name} (mounted on "{mount_point}")'.format(**_v)
vol_options.append({ vol_options.append({
'Name': name, 'Name': name,
'Path': v['mount_point'], 'Path': _v['mount_point'],
'Disabled': disabled}) 'Disabled': disabled})
selection = menu_select( selection = menu_select(
title='Please select a volume', title='Please select a volume',
@ -1313,15 +1369,17 @@ def select_device(description='device', skip_device=None):
action_entries=actions, action_entries=actions,
disabled_label='ALREADY SELECTED') disabled_label='ALREADY SELECTED')
if selection == 'Q':
raise GenericAbort()
if selection.isnumeric(): if selection.isnumeric():
return dev_options[int(selection)-1]['Dev'] return dev_options[int(selection)-1]['Dev']
elif selection == 'Q':
raise GenericAbort()
def setup_loopback_device(source_path): def setup_loopback_device(source_path):
"""Setup loopback device for source_path, returns dev_path as str.""" """Setup loopback device for source_path, returns dev_path as str."""
cmd = ( cmd = (
'sudo',
'losetup', 'losetup',
'--find', '--find',
'--partscan', '--partscan',
@ -1355,6 +1413,7 @@ def show_selection_details(state):
def show_usage(script_name): def show_usage(script_name):
"""Show usage."""
print_info('Usage:') print_info('Usage:')
print_standard(USAGE.format(script_name=script_name)) print_standard(USAGE.format(script_name=script_name))
pause() pause()
@ -1378,14 +1437,14 @@ def update_sidepane(state):
output.append('─────────────────────') output.append('─────────────────────')
# Source(s) progress # Source(s) progress
for bp in state.block_pairs: for b_pair in state.block_pairs:
if state.source.is_image(): if state.source.is_image():
output.append('{BLUE}Image File{CLEAR}'.format(**COLORS)) output.append('{BLUE}Image File{CLEAR}'.format(**COLORS))
else: else:
output.append('{BLUE}{source}{CLEAR}'.format( output.append('{BLUE}{source}{CLEAR}'.format(
source=bp.source_path, source=b_pair.source_path,
**COLORS)) **COLORS))
output.extend(bp.status) output.extend(b_pair.status)
output.append(' ') output.append(' ')
# EToC # EToC
@ -1404,11 +1463,9 @@ def update_sidepane(state):
# Add line-endings # Add line-endings
output = ['{}\n'.format(line) for line in output] output = ['{}\n'.format(line) for line in output]
with open(state.progress_out, 'w') as f: with open(state.progress_out, 'w') as _f:
f.writelines(output) _f.writelines(output)
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -36,6 +36,7 @@ class CpuObj():
self.tests = OrderedDict() self.tests = OrderedDict()
self.get_details() self.get_details()
self.name = self.lscpu.get('Model name', 'Unknown CPU') self.name = self.lscpu.get('Model name', 'Unknown CPU')
self.description = self.name
def get_details(self): def get_details(self):
"""Get CPU details from lscpu.""" """Get CPU details from lscpu."""
@ -57,6 +58,13 @@ class CpuObj():
report.append('{BLUE}Device{CLEAR}'.format(**COLORS)) report.append('{BLUE}Device{CLEAR}'.format(**COLORS))
report.append(' {}'.format(self.name)) 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 # Tests
for test in self.tests.values(): for test in self.tests.values():
report.extend(test.report) report.extend(test.report)
@ -186,8 +194,8 @@ class DiskObj():
disk_ok = False disk_ok = False
# Disable override if necessary # Disable override if necessary
self.override_disabled |= ATTRIBUTES[attr_type][k].get( if ATTRIBUTES[attr_type][k].get('Critical', False):
'Critical', False) self.override_disabled = True
# SMART overall assessment # SMART overall assessment
## NOTE: Only fail drives if the overall value exists and reports failed ## NOTE: Only fail drives if the overall value exists and reports failed
@ -220,11 +228,12 @@ class DiskObj():
# Done # Done
return test_running 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.""" """Disable test by name and update status."""
if name in self.tests: if name in self.tests:
self.tests[name].update_status(status) self.tests[name].update_status(status)
self.tests[name].disabled = True self.tests[name].disabled = True
self.tests[name].failed = test_failed
def generate_attribute_report( def generate_attribute_report(
self, description=False, timestamp=False): self, description=False, timestamp=False):
@ -307,6 +316,11 @@ class DiskObj():
attr_type=self.attr_type, **COLORS)) attr_type=self.attr_type, **COLORS))
report.extend(sorted(self.nvme_smart_notes.keys())) 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 # Tests
for test in self.tests.values(): for test in self.tests.values():
report.extend(test.report) report.extend(test.report)
@ -410,6 +424,26 @@ class DiskObj():
'self_test', {}).get( 'self_test', {}).get(
k, {}) 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): def safety_check(self, silent=False):
"""Run safety checks and disable tests if necessary.""" """Run safety checks and disable tests if necessary."""
test_running = False test_running = False
@ -454,7 +488,6 @@ class DiskObj():
disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?') disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?')
print_standard(' ') print_standard(' ')
# Disable tests if necessary (statuses won't be overwritten) # Disable tests if necessary (statuses won't be overwritten)
if test_running: if test_running:
if not silent: if not silent:
@ -463,7 +496,7 @@ class DiskObj():
for t in ['badblocks', 'I/O Benchmark']: for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied') self.disable_test(t, 'Denied')
elif not disk_ok: 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']: for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied') self.disable_test(t, 'Denied')
@ -471,6 +504,7 @@ class DiskObj():
class State(): class State():
"""Object to track device objects and overall state.""" """Object to track device objects and overall state."""
def __init__(self): def __init__(self):
self.args = None
self.cpu = None self.cpu = None
self.disks = [] self.disks = []
self.panes = {} self.panes = {}
@ -498,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): def init(self):
"""Remove test objects, set log, and add devices.""" """Remove test objects, set log, and add devices."""
self.disks = [] self.disks = []
@ -505,14 +616,18 @@ class State():
v['Objects'] = [] v['Objects'] = []
# Update LogDir # 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['LogDir'] = '{}/Logs/{}_{}'.format(
global_vars['Env']['HOME'], global_vars['Env']['HOME'],
get_ticket_number(), get_ticket_number(),
time.strftime('%Y-%m-%d_%H%M_%z')) time.strftime('%Y-%m-%d_%H%M_%z'))
os.makedirs(global_vars['LogDir'], exist_ok=True) os.makedirs(global_vars['LogDir'], exist_ok=True)
global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format( global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
global_vars['LogDir']) global_vars['LogDir'])
self.progress_out = '{}/progress.out'.format(global_vars['LogDir']) self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
# Add CPU # Add CPU
@ -541,7 +656,13 @@ class State():
# Start tmux thread # Start tmux thread
self.tmux_layout = TMUX_LAYOUT.copy() 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(): class TestObj():
@ -576,28 +697,6 @@ class TestObj():
# Functions # 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): def build_status_string(label, status, info_label=False):
"""Build status string with appropriate colors.""" """Build status string with appropriate colors."""
status_color = COLORS['CLEAR'] status_color = COLORS['CLEAR']
@ -614,64 +713,6 @@ def build_status_string(label, status, info_label=False):
**COLORS) **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): def generate_horizontal_graph(rates, oneline=False):
"""Generate horizontal graph from rates, returns list.""" """Generate horizontal graph from rates, returns list."""
graph = ['', '', '', ''] graph = ['', '', '', '']
@ -731,6 +772,44 @@ def get_graph_step(rate, scale=16):
return step 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): def get_read_rate(s):
"""Get read rate in bytes/s from dd progress output.""" """Get read rate in bytes/s from dd progress output."""
real_rate = None real_rate = None
@ -743,6 +822,7 @@ def get_read_rate(s):
def menu_diags(state, args): def menu_diags(state, args):
"""Main menu to select and run HW tests.""" """Main menu to select and run HW tests."""
args = [a.lower() for a in args] args = [a.lower() for a in args]
state.args = args
checkmark = '*' checkmark = '*'
if 'DISPLAY' in global_vars['Env']: if 'DISPLAY' in global_vars['Env']:
checkmark = '' checkmark = ''
@ -788,7 +868,7 @@ def menu_diags(state, args):
# If so, verify no other tests are enabled and set quick_mode # If so, verify no other tests are enabled and set quick_mode
state.quick_mode = True state.quick_mode = True
for opt in main_options[3:4] + main_options[5:]: 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: else:
state.quick_mode = False state.quick_mode = False
@ -884,10 +964,7 @@ def run_badblocks_test(state, test):
update_progress_pane(state) update_progress_pane(state)
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.description)
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
# Create monitor pane # Create monitor pane
test.badblocks_out = '{}/badblocks_{}.out'.format( test.badblocks_out = '{}/badblocks_{}.out'.format(
@ -970,10 +1047,11 @@ def run_hw_tests(state):
"""Run enabled hardware tests.""" """Run enabled hardware tests."""
print_standard('Scanning devices...') print_standard('Scanning devices...')
state.init() state.init()
tests_enabled = False
# Build Panes # Build Panes
update_progress_pane(state) update_progress_pane(state)
build_outer_panes(state) state.build_outer_panes()
# Show selected tests and create TestObj()s # Show selected tests and create TestObj()s
print_info('Selected Tests:') print_info('Selected Tests:')
@ -985,6 +1063,8 @@ def run_hw_tests(state):
COLORS['CLEAR'], COLORS['CLEAR'],
QUICK_LABEL if state.quick_mode and 'NVMe' in k else '')) QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
if v['Enabled']: if v['Enabled']:
tests_enabled = True
# Create TestObj and track under both CpuObj/DiskObj and State # Create TestObj and track under both CpuObj/DiskObj and State
if k in TESTS_CPU: if k in TESTS_CPU:
test_obj = TestObj( test_obj = TestObj(
@ -998,10 +1078,16 @@ def run_hw_tests(state):
v['Objects'].append(test_obj) v['Objects'].append(test_obj)
print_standard('') print_standard('')
# Bail if no tests selected
if not tests_enabled:
tmux_kill_pane(*state.panes.values())
return
# Run disk safety checks (if necessary) # Run disk safety checks (if necessary)
_disk_tests_enabled = False _disk_tests_enabled = False
for k in TESTS_DISK: 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: if _disk_tests_enabled:
for disk in state.disks: for disk in state.disks:
try: try:
@ -1039,7 +1125,7 @@ def run_hw_tests(state):
# Rebuild panes # Rebuild panes
update_progress_pane(state) update_progress_pane(state)
build_outer_panes(state) state.build_outer_panes()
# Mark unfinished tests as aborted # Mark unfinished tests as aborted
for k, v in state.tests.items(): for k, v in state.tests.items():
@ -1051,8 +1137,22 @@ def run_hw_tests(state):
# Update side pane # Update side pane
update_progress_pane(state) update_progress_pane(state)
# Done # Show results
show_results(state) 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) sleep(1)
if state.quick_mode: if state.quick_mode:
pause('Press Enter to exit... ') pause('Press Enter to exit... ')
@ -1079,10 +1179,7 @@ def run_io_benchmark(state, test):
update_progress_pane(state) update_progress_pane(state)
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.description)
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
state.tmux_layout['Current'] = {'y': 15, 'Check': True} state.tmux_layout['Current'] = {'y': 15, 'Check': True}
# Create monitor pane # Create monitor pane
@ -1241,9 +1338,7 @@ def run_mprime_test(state, test):
test.thermal_abort = False test.thermal_abort = False
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.name)
state.panes['Top'],
text='{}\n{}'.format(TOP_PANE_TEXT, dev.name))
# Start live sensor monitor # Start live sensor monitor
test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir']) test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir'])
@ -1406,7 +1501,7 @@ def run_mprime_test(state, test):
# Add temps to report # Add temps to report
test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS)) test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS))
for line in generate_sensor_report( 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)) test.report.append(' {}'.format(line))
# Add abort message(s) # Add abort message(s)
@ -1456,10 +1551,7 @@ def run_nvme_smart_tests(state, test, update_mode=False):
update_progress_pane(state) update_progress_pane(state)
# Update tmux layout # Update tmux layout
tmux_update_pane( state.set_top_pane_text(dev.description)
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
# SMART short self-test # SMART short self-test
if dev.smart_attributes and not (state.quick_mode or update_mode): if dev.smart_attributes and not (state.quick_mode or update_mode):
@ -1604,14 +1696,13 @@ def show_report(report, log_report=False):
def show_results(state): def show_results(state):
"""Show results for all tests.""" """Show results for all tests."""
clear_screen() clear_screen()
tmux_update_pane( state.set_top_pane_text('Results')
state.panes['Top'],
text='{}\nResults'.format(TOP_PANE_TEXT))
# CPU tests # CPU tests
_enabled = False _enabled = False
for k in TESTS_CPU: for k in TESTS_CPU:
_enabled |= state.tests[k]['Enabled'] if state.tests[k]['Enabled']:
_enabled = True
if _enabled: if _enabled:
print_success('CPU:'.format(k)) print_success('CPU:'.format(k))
show_report(state.cpu.generate_cpu_report(), log_report=True) show_report(state.cpu.generate_cpu_report(), log_report=True)
@ -1620,7 +1711,8 @@ def show_results(state):
# Disk tests # Disk tests
_enabled = False _enabled = False
for k in TESTS_DISK: for k in TESTS_DISK:
_enabled |= state.tests[k]['Enabled'] if state.tests[k]['Enabled']:
_enabled = True
if _enabled: if _enabled:
print_success('Disk{}:'.format( print_success('Disk{}:'.format(
'' if len(state.disks) == 1 else 's')) '' if len(state.disks) == 1 else 's'))
@ -1634,17 +1726,6 @@ def show_results(state):
# Update progress # Update progress
update_progress_pane(state) 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): def update_main_options(state, selection, main_options):
"""Update menu and state based on selection.""" """Update menu and state based on selection."""

View file

@ -95,7 +95,7 @@ def get_installed_antivirus():
out = out.stdout.decode().strip() out = out.stdout.decode().strip()
state = out.split('=')[1] state = out.split('=')[1]
state = hex(int(state)) state = hex(int(state))
if str(state)[3:5] != '10': if str(state)[3:5] not in ['10', '11']:
programs.append('[Disabled] {}'.format(prod)) programs.append('[Disabled] {}'.format(prod))
else: else:
programs.append(prod) programs.append(prod)
@ -446,16 +446,19 @@ def show_os_name():
def show_temp_files_size(): def show_temp_files_size():
"""Show total size of temp files identified by BleachBit.""" """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: with open(r'{LogDir}\Tools\BleachBit.log'.format(**global_vars), 'r') as f:
for line in f.readlines(): 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'.*: ', '', line.strip())
size = re.sub(r'(\w)iB$', r' \1b', size) size = re.sub(r'(\w)iB$', r' \1b', size)
if size is None: total += convert_to_bytes(size)
print_warning(size, timestamp=False) size_str = human_readable_size(total, decimals=1)
if size_str is None:
print_warning('UNKNOWN', timestamp=False)
else: else:
print_standard(size, timestamp=False) print_standard(size_str, timestamp=False)
def show_user_data_summary(indent=8, width=32): def show_user_data_summary(indent=8, width=32):

View file

@ -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 json
import re import re
@ -9,7 +11,7 @@ from settings.sensors import *
# Error Classes # Error Classes
class ThermalLimitReachedError(Exception): class ThermalLimitReachedError(Exception):
pass '''Thermal limit reached error.'''
def clear_temps(sensor_data): def clear_temps(sensor_data):
@ -20,28 +22,30 @@ def clear_temps(sensor_data):
_data['Temps'] = [] _data['Temps'] = []
def fix_sensor_str(s): def fix_sensor_str(_s):
"""Cleanup string and return str.""" """Cleanup string and return str."""
s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', s, re.IGNORECASE) _s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', _s, re.IGNORECASE)
s = s.title() _s = _s.title()
s = s.replace('Coretemp', 'CoreTemp') _s = _s.replace('Coretemp', 'CPUTemp')
s = s.replace('Acpi', 'ACPI') _s = _s.replace('Acpi', 'ACPI')
s = s.replace('ACPItz', 'ACPI TZ') _s = _s.replace('ACPItz', 'ACPI TZ')
s = s.replace('Isa ', 'ISA ') _s = _s.replace('Isa ', 'ISA ')
s = s.replace('Id ', 'ID ') _s = _s.replace('Pci ', 'PCI ')
s = re.sub(r'(\D+)(\d+)', r'\1 \2', s, re.IGNORECASE) _s = _s.replace('Id ', 'ID ')
s = s.replace(' ', ' ') _s = re.sub(r'(\D+)(\d+)', r'\1 \2', _s, re.IGNORECASE)
return s _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( def generate_sensor_report(
sensor_data, *temp_labels, sensor_data, *temp_labels,
colors=True, core_only=False): colors=True, cpu_only=False):
"""Generate report based on temp_labels, returns list if str.""" """Generate report based on temp_labels, returns list if str."""
report = [] report = []
for _section, _adapters in sorted(sensor_data.items()): for _section, _adapters in sorted(sensor_data.items()):
# CoreTemps then Other temps # CPU temps then Other temps
if core_only and 'Core' not in _section: if cpu_only and 'CPU' not in _section:
continue continue
for _adapter, _sources in sorted(_adapters.items()): for _adapter, _sources in sorted(_adapters.items()):
# Adapter # Adapter
@ -56,7 +60,7 @@ def generate_sensor_report(
': ' if _label != 'Current' else '', ': ' if _label != 'Current' else '',
get_temp_str(_data.get(_label, '???'), colors=colors)) get_temp_str(_data.get(_label, '???'), colors=colors))
report.append(_line) report.append(_line)
if not core_only: if not cpu_only:
report.append(' ') report.append(' ')
# Handle empty reports (i.e. no sensors detected) # Handle empty reports (i.e. no sensors detected)
@ -91,17 +95,17 @@ def get_colored_temp_str(temp):
else: else:
color = COLORS['CLEAR'] color = COLORS['CLEAR']
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format( return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
color = color, color=color,
prefix = '-' if temp < 0 else '', prefix='-' if temp < 0 else '',
temp = temp, temp=temp,
**COLORS) **COLORS)
def get_raw_sensor_data(): def get_raw_sensor_data():
"""Read sensor data and return dict.""" """Read sensor data and return dict."""
data = {} json_data = {}
cmd = ['sensors', '-j'] cmd = ['sensors', '-j']
# Get raw data # Get raw data
try: try:
result = run_program(cmd) result = run_program(cmd)
@ -122,8 +126,8 @@ def get_raw_sensor_data():
try: try:
json_data = json.loads('\n'.join(raw_data)) json_data = json.loads('\n'.join(raw_data))
except json.JSONDecodeError: except json.JSONDecodeError:
# Still broken, just set to empty dict # Still broken, just return the empty dict
json_data = {} pass
# Done # Done
return json_data return json_data
@ -132,10 +136,10 @@ def get_raw_sensor_data():
def get_sensor_data(): def get_sensor_data():
"""Parse raw sensor data and return new dict.""" """Parse raw sensor data and return new dict."""
json_data = get_raw_sensor_data() json_data = get_raw_sensor_data()
sensor_data = {'CoreTemps': {}, 'Other': {}} sensor_data = {'CPUTemps': {}, 'Other': {}}
for _adapter, _sources in json_data.items(): for _adapter, _sources in json_data.items():
if 'coretemp' in _adapter: if is_cpu_adapter(_adapter):
_section = 'CoreTemps' _section = 'CPUTemps'
else: else:
_section = 'Other' _section = 'Other'
sensor_data[_section][_adapter] = {} sensor_data[_section][_adapter] = {}
@ -157,8 +161,8 @@ def get_sensor_data():
} }
# Remove empty sections # Remove empty sections
for k, v in sensor_data.items(): for _k, _v in sensor_data.items():
v = {k2: v2 for k2, v2 in v.items() if v2} _v = {_k2: _v2 for _k2, _v2 in _v.items() if _v2}
# Done # Done
return sensor_data return sensor_data
@ -178,14 +182,20 @@ def get_temp_str(temp, colors=True):
temp) 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): def monitor_sensors(monitor_pane, monitor_file):
"""Continually update sensor data and report to screen.""" """Continually update sensor data and report to screen."""
sensor_data = get_sensor_data() sensor_data = get_sensor_data()
while True: while True:
update_sensor_data(sensor_data) 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') report = generate_sensor_report(sensor_data, 'Current', 'Max')
f.write('\n'.join(report)) _f.write('\n'.join(report))
sleep(1) sleep(1)
if monitor_pane and not tmux_poll_pane(monitor_pane): if monitor_pane and not tmux_poll_pane(monitor_pane):
break break
@ -196,7 +206,7 @@ def save_average_temp(sensor_data, temp_label, seconds=10):
clear_temps(sensor_data) clear_temps(sensor_data)
# Get temps # Get temps
for i in range(seconds): for _i in range(seconds): # pylint: disable=unused-variable
update_sensor_data(sensor_data) update_sensor_data(sensor_data)
sleep(1) sleep(1)
@ -219,24 +229,15 @@ def update_sensor_data(sensor_data, thermal_limit=None):
_data['Current'] = _temp _data['Current'] = _temp
_data['Max'] = max(_temp, _data['Max']) _data['Max'] = max(_temp, _data['Max'])
_data['Temps'].append(_temp) _data['Temps'].append(_temp)
except Exception: except Exception: # pylint: disable=broad-except
# Dumb workound for Dell sensors with changing source names # Dumb workound for Dell sensors with changing source names
pass pass
# Check if thermal limit reached # 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: if max(_data['Current'], _data['Max']) >= thermal_limit:
raise ThermalLimitReachedError('CoreTemps reached limit') raise ThermalLimitReachedError('CPU temps reached limit')
def join_columns(column1, column2, width=55):
return '{:<{}}{}'.format(
column1,
55+len(column1)-len(REGEX_COLORS.sub('', column1)),
column2)
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -1,7 +1,10 @@
# Wizard Kit: Functions - Setup # Wizard Kit: Functions - Setup
from functions.browsers import *
from functions.json import *
from functions.update import * from functions.update import *
from settings.setup import * from settings.setup import *
from settings.sources import *
# Configuration # Configuration
@ -63,9 +66,13 @@ def config_explorer_system():
write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True) write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True)
def config_explorer_user(): def config_explorer_user(setup_mode='All'):
"""Configure Windows Explorer for current user.""" """Configure Windows Explorer for current user per setup_mode."""
write_registry_settings(SETTINGS_EXPLORER_USER, all_users=False) 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(): def config_windows_updates():
@ -75,7 +82,7 @@ def config_windows_updates():
def update_clock(): def update_clock():
"""Set Timezone and sync 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(['net', 'stop', 'w32ime'], check=False)
run_program( run_program(
['w32tm', '/config', '/syncfromflags:manual', ['w32tm', '/config', '/syncfromflags:manual',
@ -107,6 +114,39 @@ def write_registry_settings(settings, all_users=False):
# Installations # 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(): def install_adobe_reader():
"""Install Adobe Reader.""" """Install Adobe Reader."""
cmd = [ cmd = [
@ -159,29 +199,115 @@ def install_firefox_extensions():
run_program(cmd) 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.""" """Run Ninite installer(s), returns list of Popen objects."""
popen_objects = [] popen_objects = []
if global_vars['OS']['Version'] in ('8', '8.1', '10'): if browsers_only:
# Modern selection # This option is deprecated
popen_objects.append( installer_path = r'{BaseDir}\Installers\Extras\Web Browsers'.format(
popen_program(r'{BaseDir}\Installers\Extras\Bundles\Modern.exe'.format( **global_vars)
**global_vars))) scan_for_browsers(silent=True)
else: for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
# Legacy selection if is_installed(browser):
if mse: cmd = r'{}\{}.exe'.format(installer_path, browser)
cmd = r'{BaseDir}\Installers\Extras\Security'.format(**global_vars) popen_objects.append(popen_program(cmd))
cmd += r'\Microsoft Security Essentials.exe'
popen_objects.append(popen_program(cmd)) # Bail
popen_objects.append( return popen_objects
popen_program(r'{BaseDir}\Installers\Extras\Bundles\Legacy.exe'.format(
**global_vars))) # 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 # LibreOffice
if libreoffice: if libreoffice:
cmd = r'{BaseDir}\Installers\Extras\Office'.format(**global_vars) cmd = r'{}\Installers\Extras\Office\{}'.format(
cmd += r'\LibreOffice.exe' global_vars['BaseDir'],
popen_objects.append(popen_program(cmd)) 'LibreOffice.exe',
)
popen_objects.append(popen_program([cmd]))
# Done # Done
return popen_objects return popen_objects
@ -208,6 +334,10 @@ def open_device_manager():
popen_program(['mmc', 'devmgmt.msc']) popen_program(['mmc', 'devmgmt.msc'])
def open_speedtest():
popen_program(['start', '', 'https://fast.com'], shell=True)
def open_windows_activation(): def open_windows_activation():
popen_program(['slui']) popen_program(['slui'])

View file

@ -6,6 +6,35 @@ from functions.common import *
from settings.sw_diags 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(): def check_connection():
"""Check if the system is online and optionally abort the script.""" """Check if the system is online and optionally abort the script."""
while True: while True:
@ -19,6 +48,37 @@ def check_connection():
abort() 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): def check_secure_boot_status(show_alert=False):
"""Checks UEFI Secure Boot status via PowerShell.""" """Checks UEFI Secure Boot status via PowerShell."""
boot_mode = get_boot_mode() boot_mode = get_boot_mode()
@ -81,33 +141,6 @@ def get_boot_mode():
return type_str 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(): def run_autoruns():
"""Run AutoRuns in the background with VirusTotal checks enabled.""" """Run AutoRuns in the background with VirusTotal checks enabled."""
extract_item('Autoruns', filter='autoruns*', silent=True) extract_item('Autoruns', filter='autoruns*', silent=True)
@ -197,8 +230,10 @@ def run_rkill():
shutil.move(item.path, dest) 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.""" """Show Windows alert box with message."""
if not title:
title = '{} Warning'.format(KIT_NAME_FULL)
message_box = ctypes.windll.user32.MessageBoxW message_box = ctypes.windll.user32.MessageBoxW
message_box(None, message, title, 0x00001030) message_box(None, message, title, 0x00001030)

View file

@ -60,16 +60,16 @@ def confirm_selections(args):
def copy_source(source, items, overwrite=False): def copy_source(source, items, overwrite=False):
"""Copy source items to /mnt/UFD.""" """Copy source items to /mnt/UFD."""
is_iso = source.name.lower().endswith('.iso') is_image = source.is_file()
# Mount source if necessary # Mount source if necessary
if is_iso: if is_image:
mount(source, '/mnt/Source') mount(source, '/mnt/Source')
# Copy items # Copy items
for i_source, i_dest in items: for i_source, i_dest in items:
i_source = '{}{}'.format( i_source = '{}{}'.format(
'/mnt/Source' if is_iso else source, '/mnt/Source' if is_image else source,
i_source, i_source,
) )
i_dest = '/mnt/UFD{}'.format(i_dest) i_dest = '/mnt/UFD{}'.format(i_dest)
@ -80,7 +80,7 @@ def copy_source(source, items, overwrite=False):
pass pass
# Unmount source if necessary # Unmount source if necessary
if is_iso: if is_image:
unmount('/mnt/Source') unmount('/mnt/Source')
@ -199,6 +199,8 @@ def is_valid_path(path_obj, path_type):
valid_path = path_obj.is_dir() valid_path = path_obj.is_dir()
elif path_type == 'KIT': elif path_type == 'KIT':
valid_path = path_obj.is_dir() and path_obj.joinpath('.bin').exists() 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': elif path_type == 'ISO':
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso' valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
elif path_type == 'UFD': elif path_type == 'UFD':
@ -207,10 +209,16 @@ def is_valid_path(path_obj, path_type):
return valid_path return valid_path
def mount(mount_source, mount_point): def mount(mount_source, mount_point, read_write=False):
"""Mount mount_source on mount_point.""" """Mount mount_source on mount_point."""
os.makedirs(mount_point, exist_ok=True) 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) run_program(cmd)

View file

@ -615,6 +615,22 @@ def update_adobe_reader_dc():
dest, 'Adobe Reader DC.exe', SOURCE_URLS['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(): def update_macs_fan_control():
# Prep # Prep
dest = r'{}\Installers'.format( dest = r'{}\Installers'.format(

View file

@ -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

View file

@ -49,7 +49,7 @@ if __name__ == '__main__':
global_vars=global_vars) global_vars=global_vars)
# Done # Done
sleep(10) sleep(1)
pause('Press Enter to exit...') pause('Press Enter to exit...')
exit_script(1) exit_script(1)

View file

@ -25,7 +25,6 @@ if __name__ == '__main__':
'UnsupportedOSError': 'Unsupported OS', 'UnsupportedOSError': 'Unsupported OS',
}} }}
answer_extensions = ask('Install Extensions?') answer_extensions = ask('Install Extensions?')
answer_adobe_reader = ask('Install Adobe Reader?')
answer_vcr = ask('Install Visual C++ Runtimes?') answer_vcr = ask('Install Visual C++ Runtimes?')
answer_ninite = ask('Install Ninite Bundle?') answer_ninite = ask('Install Ninite Bundle?')
if answer_ninite and global_vars['OS']['Version'] in ['7']: if answer_ninite and global_vars['OS']['Version'] in ['7']:
@ -35,9 +34,6 @@ if __name__ == '__main__':
answer_mse = False answer_mse = False
print_info('Installing Programs') 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: if answer_vcr:
install_vcredists() install_vcredists()
if answer_ninite: if answer_ninite:

View file

@ -16,9 +16,6 @@ if __name__ == '__main__':
# Prep # Prep
clear_screen() clear_screen()
# Connect
connect_to_network()
# Mount # Mount
if is_connected(): if is_connected():
mount_backup_shares(read_write=True) mount_backup_shares(read_write=True)

View file

@ -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

View file

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

View file

@ -5,7 +5,9 @@ import re
from collections import OrderedDict from collections import OrderedDict
# General # General
MAP_DIR = '/Backups/ddrescue-tui'
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs'] RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
RECOMMENDED_MAP_FSTYPES = ['cifs', 'ext2', 'ext3', 'ext4', 'vfat', 'xfs']
USAGE = """ {script_name} clone [source [destination]] USAGE = """ {script_name} clone [source [destination]]
{script_name} image [source [destination]] {script_name} image [source [destination]]
(e.g. {script_name} clone /dev/sda /dev/sdb) (e.g. {script_name} clone /dev/sda /dev/sdb)
@ -36,6 +38,12 @@ DDRESCUE_SETTINGS = {
'-vvvv': {'Enabled': True, 'Hidden': True, }, '-vvvv': {'Enabled': True, 'Hidden': True, },
} }
ETOC_REFRESH_RATE = 30 # in seconds ETOC_REFRESH_RATE = 30 # in seconds
REGEX_DDRESCUE_LOG = re.compile(
r'^\s*(?P<key>\S+):\s+'
r'(?P<size>\d+)\s+'
r'(?P<unit>[PTGMKB])i?B?',
re.IGNORECASE,
)
REGEX_REMAINING_TIME = re.compile( REGEX_REMAINING_TIME = re.compile(
r'remaining time:' r'remaining time:'
r'\s*((?P<days>\d+)d)?' r'\s*((?P<days>\d+)d)?'

View file

@ -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 = { LAUNCHERS = {
r'(Root)': { 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': { 'System Diagnostics': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
'L_ITEM': 'system_diagnostics.py', 'L_ITEM': 'system_diagnostics.py',
'L_ELEV': 'True', 'L_ELEV': 'True',
}, },
'User Checklist': { 'System Setup': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
'L_ITEM': 'user_checklist.py', 'L_ITEM': 'system_setup.py',
'L_ELEV': 'True',
}, },
}, },
r'Data Recovery': { r'Data Recovery': {
@ -55,6 +40,7 @@ LAUNCHERS = {
}, },
}, },
r'Data Transfers': { r'Data Transfers': {
# pylint: disable=bad-continuation
'FastCopy (as ADMIN)': { 'FastCopy (as ADMIN)': {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'FastCopy', 'L_PATH': 'FastCopy',
@ -257,7 +243,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'erunt', 'L_PATH': 'erunt',
'L_ITEM': 'ERUNT.EXE', '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', 'L_ELEV': 'True',
'Extra Code': [ 'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs', r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@ -287,13 +273,13 @@ LAUNCHERS = {
r'Drivers': { r'Drivers': {
'Intel RST (Current Release)': { 'Intel RST (Current Release)': {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': '_Drivers\Intel RST', 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': 'SetupRST_17.2.exe', 'L_ITEM': 'SetupRST_17.2.exe',
'L_7ZIP': 'SetupRST_17.2.exe', 'L_7ZIP': 'SetupRST_17.2.exe',
}, },
'Intel RST (Previous Releases)': { 'Intel RST (Previous Releases)': {
'L_TYPE': 'Folder', 'L_TYPE': 'Folder',
'L_PATH': '_Drivers\Intel RST', 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': '.', 'L_ITEM': '.',
'L_NCMD': 'True', 'L_NCMD': 'True',
}, },
@ -309,7 +295,7 @@ LAUNCHERS = {
}, },
'Snappy Driver Installer Origin': { 'Snappy Driver Installer Origin': {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': '_Drivers\SDIO', 'L_PATH': r'_Drivers\SDIO',
'L_ITEM': 'SDIO.exe', 'L_ITEM': 'SDIO.exe',
}, },
}, },
@ -435,6 +421,12 @@ LAUNCHERS = {
}, },
}, },
r'Misc': { r'Misc': {
'Activate Windows': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'activate.py',
'L_ELEV': 'True',
},
'Cleanup CBS Temp Files': { 'Cleanup CBS Temp Files': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
@ -452,6 +444,20 @@ LAUNCHERS = {
'L_PATH': 'ConEmu', 'L_PATH': 'ConEmu',
'L_ITEM': 'ConEmu.exe', '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': { 'Enter SafeMode': {
'L_TYPE': 'PyScript', 'L_TYPE': 'PyScript',
'L_PATH': 'Scripts', 'L_PATH': 'Scripts',
@ -491,7 +497,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'XMPlay', 'L_PATH': 'XMPlay',
'L_ITEM': 'xmplay.exe', 'L_ITEM': 'xmplay.exe',
'L_ARGS': '"%bin%\XMPlay\music.7z"', 'L_ARGS': r'"%bin%\XMPlay\music.7z"',
}, },
}, },
r'Repairs': { r'Repairs': {
@ -551,7 +557,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable', 'L_TYPE': 'Executable',
'L_PATH': 'RKill', 'L_PATH': 'RKill',
'L_ITEM': 'RKill.exe', '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', 'L_ELEV': 'True',
'Extra Code': [ 'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs', r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@ -594,5 +600,3 @@ LAUNCHERS = {
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -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 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 # General
HKU = winreg.HKEY_USERS
HKCR = winreg.HKEY_CLASSES_ROOT
HKCU = winreg.HKEY_CURRENT_USER
HKLM = winreg.HKEY_LOCAL_MACHINE
OTHER_RESULTS = { OTHER_RESULTS = {
'Error': { 'Error': {
'CalledProcessError': 'Unknown Error', 'CalledProcessError': 'Unknown Error',
@ -92,6 +98,15 @@ SETTINGS_EXPLORER_SYSTEM = {
}, },
} }
SETTINGS_EXPLORER_USER = { 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 # Disable features
r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': { r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': {
'DWORD Items': { 'DWORD Items': {
@ -104,21 +119,41 @@ SETTINGS_EXPLORER_USER = {
}, },
# File Explorer # File Explorer
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': { r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
'Invalid modes': ['Cur'],
'DWORD Items': { 'DWORD Items': {
# Change default Explorer view to "Computer" # Change default Explorer view to "Computer"
'LaunchTo': 1, '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 # Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': { r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
'Invalid modes': ['Cur'],
'DWORD Items': {'PeopleBand': 0}, 'DWORD Items': {'PeopleBand': 0},
}, },
# Hide Search button / box # Hide Search button / box
r'Software\Microsoft\Windows\CurrentVersion\Search': { r'Software\Microsoft\Windows\CurrentVersion\Search': {
'Invalid modes': ['Cur'],
'DWORD Items': {'SearchboxTaskbarMode': 0}, 'DWORD Items': {'SearchboxTaskbarMode': 0},
}, },
} }
# LibreOffice
LIBREOFFICE_XCU_DATA = '''<?xml version="1.0" encoding="UTF-8"?>
<oor:items xmlns:oor="http://openoffice.org/2001/registry" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.presentation.PresentationDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>Impress MS PowerPoint 2007 XML</value></prop></item>
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.sheet.SpreadsheetDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>Calc MS Excel 2007 XML</value></prop></item>
<item oor:path="/org.openoffice.Setup/Office/Factories/org.openoffice.Setup:Factory['com.sun.star.text.TextDocument']"><prop oor:name="ooSetupFactoryDefaultFilter" oor:op="fuse"><value>MS Word 2007 XML</value></prop></item>
<item oor:path="/org.openoffice.Office.Common/Save/Document"><prop oor:name="WarnAlienFormat" oor:op="fuse"><value>false</value></prop></item>
</oor:items>
'''
# Visual C++ Runtimes # Visual C++ Runtimes
VCR_REDISTS = [ VCR_REDISTS = [
{'Name': 'Visual C++ 2010 x32...', {'Name': 'Visual C++ 2010 x32...',
@ -157,5 +192,3 @@ SETTINGS_WINDOWS_UPDATES = {
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -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 = { SOURCE_URLS = {
'Adobe Reader DC': 'http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1901020098/AcroRdrDC1901020098_en_US.exe', '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', 'ERUNT': 'http://www.aumha.org/downloads/erunt.zip',
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.935.x86.en-US.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', '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', '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', 'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.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', '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', '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', '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', 'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip', 'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip',
'NirCmd64': 'https://www.nirsoft.net/utils/nircmd-x64.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', 'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent',
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe', 'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip', 'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip',
'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-i686-bin.zip', 'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-i686-bin.zip',
'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-x86_64-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', 'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip',
'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.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', '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', '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 = { NINITE_SOURCES = {
'Bundles': { 'Bundles': {
'Legacy.exe': '.net4.7.2-7zip-chrome-firefox-vlc', 'base.exe': '.net4.7.2-7zip-vlc',
'Modern.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-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': { 'Audio-Video': {
'AIMP.exe': 'aimp', 'AIMP.exe': 'aimp',
@ -216,5 +227,3 @@ WINDOWS_UPDATE_SOURCES = {
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2 tw=0

View file

@ -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 = { WINDOWS_BUILDS = {
# Build, Version, Release, Codename, Marketing Name, Notes # Build, Version, Release, Codename, Marketing Name, Notes
'6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'), '6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6001': ('Vista', 'SP1', 'Longhorn', None, 'unsupported'), '6001': ('Vista', 'SP1', 'Longhorn', None, 'unsupported'),
'6002': ('Vista', 'SP2', 'Longhorn', None, 'unsupported'), '6002': ('Vista', 'SP2', 'Longhorn', None, 'unsupported'),
@ -202,15 +204,22 @@ WINDOWS_BUILDS = {
'18356': ('10', None, '19H1', None, 'preview build'), '18356': ('10', None, '19H1', None, 'preview build'),
'18358': ('10', None, '19H1', None, 'preview build'), '18358': ('10', None, '19H1', None, 'preview build'),
'18361': ('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'), '18836': ('10', None, '20H1', None, 'preview build'),
'18841': ('10', None, '20H1', None, 'preview build'), '18841': ('10', None, '20H1', None, 'preview build'),
'18845': ('10', None, '20H1', None, 'preview build'), '18845': ('10', None, '20H1', None, 'preview build'),
'18850': ('10', None, '20H1', None, 'preview build'), '18850': ('10', None, '20H1', None, 'preview build'),
'18855': ('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__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

@ -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

View file

@ -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()

View file

@ -57,6 +57,7 @@ if __name__ == '__main__':
# Installers # Installers
print_info(' 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='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='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='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) try_and_print(message='Visual C++ Runtimes...', function=update_vcredists, other_results=other_results, width=40)

View file

@ -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

View file

@ -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

View file

@ -34,5 +34,5 @@ alias srsz='sudo rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias testdisk='sudo testdisk' alias testdisk='sudo testdisk'
alias umount='sudo umount' alias umount='sudo umount'
alias unmount='sudo umount' alias unmount='sudo umount'
alias wkclone='sudo ddrescue-tui clone' alias wkclone='ddrescue-tui clone'
alias wkimage='sudo ddrescue-tui image' alias wkimage='ddrescue-tui image'

View file

@ -283,8 +283,8 @@ function update_live_env() {
fi fi
# WiFi # WiFi
cp "$ROOT_DIR/.linux_items/known_networks" "/root/known_networks" cp "$ROOT_DIR/.linux_items/known_networks" "$LIVE_DIR/airootfs/root/known_networks"
echo "add-known-networks" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh" echo "add-known-networks --user=$username" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
} }
function update_repo() { function update_repo() {