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

View file

@ -1,6 +1,6 @@
#!/bin/env python3
#
# pylint: disable=no-name-in-module,wildcard-import
# pylint: disable=no-name-in-module,wildcard-import,wrong-import-position
# vim: sts=2 sw=2 ts=2
"""Wizard Kit: UFD build tool"""
@ -54,16 +54,8 @@ if __name__ == '__main__':
confirm_selections(args)
# Prep UFD
print_info('Prep UFD')
if args['--update']:
# Remove arch folder
try_and_print(
indent=2,
message='Removing Linux...',
function=remove_arch,
)
else:
# Format and partition
if not args['--update']:
print_info('Prep UFD')
prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr'])
# Mount UFD
@ -73,8 +65,17 @@ if __name__ == '__main__':
function=mount,
mount_source=find_first_partition(ufd_dev),
mount_point='/mnt/UFD',
read_write=True,
)
# Remove Arch folder
if args['--update']:
try_and_print(
indent=2,
message='Removing Linux...',
function=remove_arch,
)
# Copy sources
print_standard(' ')
print_info('Copy Sources')

View file

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

View file

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

View file

@ -32,8 +32,8 @@ def archive_all_users():
user_path = os.path.join(users_root, user_name)
appdata_local = os.path.join(user_path, r'AppData\Local')
appdata_roaming = os.path.join(user_path, r'AppData\Roaming')
valid_user &= os.path.exists(appdata_local)
valid_user &= os.path.exists(appdata_roaming)
valid_user = valid_user and os.path.exists(appdata_local)
valid_user = valid_user and os.path.exists(appdata_roaming)
if valid_user:
user_envs.append({
'USERNAME': user_name,
@ -325,7 +325,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
if just_firefox and browser_data[browser]['base'] != 'mozilla':
continue
exe_path = browser_data[browser].get('exe_path', None)
function=run_program
if not exe_path:
if browser_data[browser]['profiles']:
print_standard(
@ -375,7 +374,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
elif browser_data[browser]['base'] == 'ie':
urls.append(IE_GALLERY)
function=popen_program
# By using check=False we're skipping any return codes so
# it should only fail if the program can't be run
@ -384,10 +382,16 @@ def install_adblock(indent=8, width=32, just_firefox=False):
# installation status.
try_and_print(message='{}...'.format(browser),
indent=indent, width=width,
cs='Done', function=function,
cs='Started', function=popen_program,
cmd=[exe_path, *urls], check=False)
def is_installed(browser_name):
"""Checks if browser is installed based on exe_path, returns bool."""
browser_name = browser_name.replace(' Chromium', '')
return bool(browser_data.get(browser_name, {}).get('exe_path', False))
def list_homepages(indent=8, width=32):
"""List current homepages for reference."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['exe_path']]
@ -419,6 +423,12 @@ def list_homepages(indent=8, width=32):
indent=' '*indent, width=width, name=name, page=page))
def profile_present(browser_name):
"""Checks if a profile was detected for browser, returns bool."""
browser_name = browser_name.replace(' Chromium', '')
return bool(browser_data.get(browser_name, {}).get('profiles', False))
def reset_browsers(indent=8, width=32):
"""Reset all detected browsers to safe defaults."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['profiles']]
@ -437,14 +447,21 @@ def reset_browsers(indent=8, width=32):
other_results=other_results, profile=profile)
def scan_for_browsers(just_firefox=False):
def scan_for_browsers(just_firefox=False, silent=False):
"""Scan system for any supported browsers."""
for name, details in sorted(SUPPORTED_BROWSERS.items()):
if just_firefox and details['base'] != 'mozilla':
continue
try_and_print(message='{}...'.format(name),
function=get_browser_details, cs='Detected',
other_results=other_results, name=name)
if silent:
try:
get_browser_details(name)
except Exception:
# Ignore errors in silent mode
pass
else:
try_and_print(message='{}...'.format(name),
function=get_browser_details, cs='Detected',
other_results=other_results, name=name)
if __name__ == '__main__':

View file

@ -1,7 +1,9 @@
# Wizard Kit: Functions - Cleanup
from functions.common import *
'''Wizard Kit: Functions - Cleanup'''
# pylint: disable=no-name-in-module,wildcard-import
# vim: sts=2 sw=2 ts=2
from functions.setup import *
from settings.cleanup import *
def cleanup_adwcleaner():
"""Move AdwCleaner folders into the ClientDir."""
@ -75,8 +77,7 @@ def cleanup_desktop():
desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env'])
for entry in os.scandir(desktop_path):
# JRT, RKill, Shortcut cleaner
if re.search(r'^(JRT|RKill|sc-cleaner)', entry.name, re.IGNORECASE):
if DESKTOP_ITEMS.search(entry.name):
dest_name = r'{}\{}'.format(dest_folder, entry.name)
dest_name = non_clobber_rename(dest_name)
shutil.move(entry.path, dest_name)
@ -130,7 +131,14 @@ def delete_registry_value(hive, key, value):
winreg.DeleteValue(k, value)
def restore_default_uac():
"""Restores default UAC settings via the registry."""
if global_vars['OS']['Version'] == '10':
write_registry_settings(UAC_DEFAULTS_WIN10, all_users=True)
else:
# Haven't checked Win8 settings, only applying minimum set
write_registry_settings(UAC_DEFAULTS_WIN7, all_users=True)
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

View file

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

View file

@ -151,12 +151,16 @@ def is_valid_wim_file(item):
def get_mounted_volumes():
"""Get mounted volumes, returns dict."""
cmd = [
'findmnt', '-J', '-b', '-i',
'-t', (
'findmnt',
'--list',
'--json',
'--bytes',
'--invert',
'--types', (
'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,'
'devtmpfs,hugetlbfs,mqueue,proc,pstore,securityfs,sysfs,tmpfs'
),
'-o', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
'--output', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
json_data = get_json_from_command(cmd)
mounted_volumes = []
for item in json_data.get('filesystems', []):
@ -195,6 +199,8 @@ def mount_volumes(
volumes.update({child['name']: child})
for grandchild in child.get('children', []):
volumes.update({grandchild['name']: grandchild})
for great_grandchild in grandchild.get('children', []):
volumes.update({great_grandchild['name']: great_grandchild})
# Get list of mounted volumes
mounted_volumes = get_mounted_volumes()
@ -212,7 +218,7 @@ def mount_volumes(
report[vol_path] = vol_data
elif 'children' in vol_data:
# Skip LVM/RAID partitions (the real volume is mounted separately)
vol_data['show_data']['data'] = vol_data.get('fstype', 'UNKNOWN')
vol_data['show_data']['data'] = vol_data.get('fstype', 'Unknown')
if vol_data.get('label', None):
vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label'])
vol_data['show_data']['info'] = True
@ -237,6 +243,7 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None
else:
fstype = vol_data.get('fstype', 'Unknown FS')
size_used = human_readable_size(
mounted_volumes[vol_path]['used'],
decimals=1,
@ -250,8 +257,9 @@ def mount_volumes(
vol_data['mount_point'] = mounted_volumes[vol_path]['target']
vol_data['show_data']['data'] = 'Mounted on {}'.format(
mounted_volumes[vol_path]['target'])
vol_data['show_data']['data'] = '{:40} ({} used, {} free)'.format(
vol_data['show_data']['data'] = '{:40} ({}, {} used, {} free)'.format(
vol_data['show_data']['data'],
fstype,
size_used,
size_avail)
@ -285,6 +293,14 @@ def mount_backup_shares(read_write=False):
def mount_network_share(server, read_write=False):
"""Mount a network share defined by server."""
uid = '1000'
# Get UID
cmd = ['id', '--user', 'tech']
result = run_program(cmd, check=False, encoding='utf-8', errors='ignore')
if result.stdout.strip().isnumeric():
uid = result.stdout.strip()
if read_write:
username = server['RW-User']
password = server['RW-Pass']
@ -300,18 +316,35 @@ def mount_network_share(server, read_write=False):
error = r'Failed to mount \\{Name}\{Share} ({IP})'.format(**server)
success = 'Mounted {Name}'.format(**server)
elif psutil.LINUX:
# Make mountpoint
cmd = [
'sudo', 'mkdir', '-p',
'/Backups/{Name}'.format(**server)]
run_program(cmd)
# Set mount options
cmd_options = [
# Assuming GID matches UID
'gid={}'.format(uid),
'uid={}'.format(uid),
]
cmd_options.append('rw' if read_write else 'ro')
cmd_options.append('username={}'.format(username))
if password:
cmd_options.append('password={}'.format(password))
else:
# Skip password check
cmd_options.append('guest')
# Set mount command
cmd = [
'sudo', 'mount',
'//{IP}/{Share}'.format(**server),
'//{IP}/{Share}'.format(**server).replace('\\', '/'),
'/Backups/{Name}'.format(**server),
'-o', '{}username={},password={}'.format(
'' if read_write else 'ro,',
username,
password)]
'-o', ','.join(cmd_options),
]
# Set result messages
warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format(
**server)
error = 'Failed to mount /Backups/{Name}'.format(**server)

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

View file

@ -36,6 +36,7 @@ class CpuObj():
self.tests = OrderedDict()
self.get_details()
self.name = self.lscpu.get('Model name', 'Unknown CPU')
self.description = self.name
def get_details(self):
"""Get CPU details from lscpu."""
@ -57,6 +58,13 @@ class CpuObj():
report.append('{BLUE}Device{CLEAR}'.format(**COLORS))
report.append(' {}'.format(self.name))
# Include RAM details
ram_details = get_ram_details()
ram_total = human_readable_size(ram_details.pop('Total', 0)).strip()
ram_dimms = ['{}x {}'.format(v, k) for k, v in sorted(ram_details.items())]
report.append('{BLUE}RAM{CLEAR}'.format(**COLORS))
report.append(' {} ({})'.format(ram_total, ', '.join(ram_dimms)))
# Tests
for test in self.tests.values():
report.extend(test.report)
@ -186,8 +194,8 @@ class DiskObj():
disk_ok = False
# Disable override if necessary
self.override_disabled |= ATTRIBUTES[attr_type][k].get(
'Critical', False)
if ATTRIBUTES[attr_type][k].get('Critical', False):
self.override_disabled = True
# SMART overall assessment
## NOTE: Only fail drives if the overall value exists and reports failed
@ -220,11 +228,12 @@ class DiskObj():
# Done
return test_running
def disable_test(self, name, status):
def disable_test(self, name, status, test_failed=False):
"""Disable test by name and update status."""
if name in self.tests:
self.tests[name].update_status(status)
self.tests[name].disabled = True
self.tests[name].failed = test_failed
def generate_attribute_report(
self, description=False, timestamp=False):
@ -307,6 +316,11 @@ class DiskObj():
attr_type=self.attr_type, **COLORS))
report.extend(sorted(self.nvme_smart_notes.keys()))
# 4K alignment check
if not self.is_4k_aligned():
report.append('{YELLOW}Warning{CLEAR}'.format(**COLORS))
report.append(' One or more partitions are not 4K aligned')
# Tests
for test in self.tests.values():
report.extend(test.report)
@ -410,6 +424,26 @@ class DiskObj():
'self_test', {}).get(
k, {})
def is_4k_aligned(self):
"""Check if partitions are 4K aligned, returns bool."""
cmd = [
'sudo',
'sfdisk',
'--json',
self.path,
]
aligned = True
# Get partition details
json_data = get_json_from_command(cmd)
# Check partitions
for part in json_data.get('partitiontable', {}).get('partitions', []):
aligned = aligned and part.get('start', -1) % 4096 == 0
# Done
return aligned
def safety_check(self, silent=False):
"""Run safety checks and disable tests if necessary."""
test_running = False
@ -454,7 +488,6 @@ class DiskObj():
disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?')
print_standard(' ')
# Disable tests if necessary (statuses won't be overwritten)
if test_running:
if not silent:
@ -463,7 +496,7 @@ class DiskObj():
for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied')
elif not disk_ok:
self.disable_test('NVMe / SMART', 'NS')
self.disable_test('NVMe / SMART', 'NS', test_failed=True)
for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied')
@ -471,6 +504,7 @@ class DiskObj():
class State():
"""Object to track device objects and overall state."""
def __init__(self):
self.args = None
self.cpu = None
self.disks = []
self.panes = {}
@ -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):
"""Remove test objects, set log, and add devices."""
self.disks = []
@ -505,14 +616,18 @@ class State():
v['Objects'] = []
# Update LogDir
if not self.quick_mode:
if self.quick_mode:
global_vars['LogDir'] = '{}/Logs/{}'.format(
global_vars['Env']['HOME'],
time.strftime('%Y-%m-%d_%H%M_%z'))
else:
global_vars['LogDir'] = '{}/Logs/{}_{}'.format(
global_vars['Env']['HOME'],
get_ticket_number(),
time.strftime('%Y-%m-%d_%H%M_%z'))
os.makedirs(global_vars['LogDir'], exist_ok=True)
global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
global_vars['LogDir'])
os.makedirs(global_vars['LogDir'], exist_ok=True)
global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
global_vars['LogDir'])
self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
# Add CPU
@ -541,7 +656,13 @@ class State():
# Start tmux thread
self.tmux_layout = TMUX_LAYOUT.copy()
start_thread(fix_tmux_panes_loop, args=[self])
start_thread(self.fix_tmux_panes_loop)
def set_top_pane_text(self, text):
"""Set top pane text using TOP_PANE_TEXT and provided text."""
tmux_update_pane(
self.panes['Top'],
text='{}\n{}'.format(TOP_PANE_TEXT, text))
class TestObj():
@ -576,28 +697,6 @@ class TestObj():
# Functions
def build_outer_panes(state):
"""Build top and side panes."""
clear_screen()
# Top
state.panes['Top'] = tmux_split_window(
behind=True, lines=2, vertical=True,
text=TOP_PANE_TEXT)
# Started
state.panes['Started'] = tmux_split_window(
lines=SIDE_PANE_WIDTH, target_pane=state.panes['Top'],
text='{BLUE}Started{CLEAR}\n{s}'.format(
s=time.strftime("%Y-%m-%d %H:%M %Z"),
**COLORS))
# Progress
state.panes['Progress'] = tmux_split_window(
lines=SIDE_PANE_WIDTH,
watch=state.progress_out)
def build_status_string(label, status, info_label=False):
"""Build status string with appropriate colors."""
status_color = COLORS['CLEAR']
@ -614,64 +713,6 @@ def build_status_string(label, status, info_label=False):
**COLORS)
def fix_tmux_panes_loop(state):
while True:
try:
fix_tmux_panes(state)
sleep(1)
except RuntimeError:
# Assuming layout definitions changes mid-run, ignoring
pass
def fix_tmux_panes(state):
"""Fix pane sizes if the window has been resized."""
needs_fixed = False
# Bail?
if not state.panes:
return
# Check layout
for k, v in state.tmux_layout.items():
if not v.get('Check'):
# Not concerned with the size of this pane
continue
# Get target
target = None
if k != 'Current':
if k not in state.panes:
# Skip missing panes
continue
else:
target = state.panes[k]
# Check pane size
x, y = tmux_get_pane_size(pane_id=target)
if v.get('x', False) and v['x'] != x:
needs_fixed = True
if v.get('y', False) and v['y'] != y:
needs_fixed = True
# Bail?
if not needs_fixed:
return
# Update layout
for k, v in state.tmux_layout.items():
# Get target
target = None
if k != 'Current':
if k not in state.panes:
# Skip missing panes
continue
else:
target = state.panes[k]
# Resize pane
tmux_resize_pane(pane_id=target, **v)
def generate_horizontal_graph(rates, oneline=False):
"""Generate horizontal graph from rates, returns list."""
graph = ['', '', '', '']
@ -731,6 +772,44 @@ def get_graph_step(rate, scale=16):
return step
def get_ram_details():
"""Get RAM details via dmidecode, returns dict."""
cmd = ['sudo', 'dmidecode', '--type', 'memory']
manufacturer = 'UNKNOWN'
ram_details = {'Total': 0}
size = 0
# Get DMI data
result = run_program(cmd, encoding='utf-8', errors='ignore')
dmi_data = result.stdout.splitlines()
# Parse data
for line in dmi_data:
line = line.strip()
if line == 'Memory Device':
# Reset vars
manufacturer = 'UNKNOWN'
size = 0
elif line.startswith('Size:'):
size = convert_to_bytes(line.replace('Size: ', ''))
elif line.startswith('Manufacturer:'):
manufacturer = line.replace('Manufacturer: ', '')
if size > 0:
# Add RAM to list if slot populated
ram_str = '{} {}'.format(
human_readable_size(size).strip(),
manufacturer,
)
ram_details['Total'] += size
if ram_str in ram_details:
ram_details[ram_str] += 1
else:
ram_details[ram_str] = 1
# Done
return ram_details
def get_read_rate(s):
"""Get read rate in bytes/s from dd progress output."""
real_rate = None
@ -743,6 +822,7 @@ def get_read_rate(s):
def menu_diags(state, args):
"""Main menu to select and run HW tests."""
args = [a.lower() for a in args]
state.args = args
checkmark = '*'
if 'DISPLAY' in global_vars['Env']:
checkmark = ''
@ -788,7 +868,7 @@ def menu_diags(state, args):
# If so, verify no other tests are enabled and set quick_mode
state.quick_mode = True
for opt in main_options[3:4] + main_options[5:]:
state.quick_mode &= not opt['Enabled']
state.quick_mode = state.quick_mode and not opt['Enabled']
else:
state.quick_mode = False
@ -884,10 +964,7 @@ def run_badblocks_test(state, test):
update_progress_pane(state)
# Update tmux layout
tmux_update_pane(
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
state.set_top_pane_text(dev.description)
# Create monitor pane
test.badblocks_out = '{}/badblocks_{}.out'.format(
@ -970,10 +1047,11 @@ def run_hw_tests(state):
"""Run enabled hardware tests."""
print_standard('Scanning devices...')
state.init()
tests_enabled = False
# Build Panes
update_progress_pane(state)
build_outer_panes(state)
state.build_outer_panes()
# Show selected tests and create TestObj()s
print_info('Selected Tests:')
@ -985,6 +1063,8 @@ def run_hw_tests(state):
COLORS['CLEAR'],
QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
if v['Enabled']:
tests_enabled = True
# Create TestObj and track under both CpuObj/DiskObj and State
if k in TESTS_CPU:
test_obj = TestObj(
@ -998,10 +1078,16 @@ def run_hw_tests(state):
v['Objects'].append(test_obj)
print_standard('')
# Bail if no tests selected
if not tests_enabled:
tmux_kill_pane(*state.panes.values())
return
# Run disk safety checks (if necessary)
_disk_tests_enabled = False
for k in TESTS_DISK:
_disk_tests_enabled |= state.tests[k]['Enabled']
if state.tests[k]['Enabled']:
_disk_tests_enabled = True
if _disk_tests_enabled:
for disk in state.disks:
try:
@ -1039,7 +1125,7 @@ def run_hw_tests(state):
# Rebuild panes
update_progress_pane(state)
build_outer_panes(state)
state.build_outer_panes()
# Mark unfinished tests as aborted
for k, v in state.tests.items():
@ -1051,8 +1137,22 @@ def run_hw_tests(state):
# Update side pane
update_progress_pane(state)
# Done
# Show results
show_results(state)
# Upload for review
if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
try_and_print(
message='Saving debug reports...',
function=save_debug_reports,
state=state, global_vars=global_vars)
try_and_print(
message='Uploading Data...',
function=upload_logdir,
global_vars=global_vars,
reason='Review')
# Done
sleep(1)
if state.quick_mode:
pause('Press Enter to exit... ')
@ -1079,10 +1179,7 @@ def run_io_benchmark(state, test):
update_progress_pane(state)
# Update tmux layout
tmux_update_pane(
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
state.set_top_pane_text(dev.description)
state.tmux_layout['Current'] = {'y': 15, 'Check': True}
# Create monitor pane
@ -1241,9 +1338,7 @@ def run_mprime_test(state, test):
test.thermal_abort = False
# Update tmux layout
tmux_update_pane(
state.panes['Top'],
text='{}\n{}'.format(TOP_PANE_TEXT, dev.name))
state.set_top_pane_text(dev.name)
# Start live sensor monitor
test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir'])
@ -1406,7 +1501,7 @@ def run_mprime_test(state, test):
# Add temps to report
test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS))
for line in generate_sensor_report(
test.sensor_data, 'Idle', 'Max', 'Cooldown', core_only=True):
test.sensor_data, 'Idle', 'Max', 'Cooldown', cpu_only=True):
test.report.append(' {}'.format(line))
# Add abort message(s)
@ -1456,10 +1551,7 @@ def run_nvme_smart_tests(state, test, update_mode=False):
update_progress_pane(state)
# Update tmux layout
tmux_update_pane(
state.panes['Top'],
text='{}\n{}'.format(
TOP_PANE_TEXT, dev.description))
state.set_top_pane_text(dev.description)
# SMART short self-test
if dev.smart_attributes and not (state.quick_mode or update_mode):
@ -1604,14 +1696,13 @@ def show_report(report, log_report=False):
def show_results(state):
"""Show results for all tests."""
clear_screen()
tmux_update_pane(
state.panes['Top'],
text='{}\nResults'.format(TOP_PANE_TEXT))
state.set_top_pane_text('Results')
# CPU tests
_enabled = False
for k in TESTS_CPU:
_enabled |= state.tests[k]['Enabled']
if state.tests[k]['Enabled']:
_enabled = True
if _enabled:
print_success('CPU:'.format(k))
show_report(state.cpu.generate_cpu_report(), log_report=True)
@ -1620,7 +1711,8 @@ def show_results(state):
# Disk tests
_enabled = False
for k in TESTS_DISK:
_enabled |= state.tests[k]['Enabled']
if state.tests[k]['Enabled']:
_enabled = True
if _enabled:
print_success('Disk{}:'.format(
'' if len(state.disks) == 1 else 's'))
@ -1634,17 +1726,6 @@ def show_results(state):
# Update progress
update_progress_pane(state)
# Ask for review
if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
try_and_print(
message='Saving debug reports...',
function=save_debug_reports,
state=state, global_vars=global_vars)
try_and_print(
message='Uploading Data...',
function=upload_logdir,
global_vars=global_vars)
def update_main_options(state, selection, main_options):
"""Update menu and state based on selection."""

View file

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

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

View file

@ -1,7 +1,10 @@
# Wizard Kit: Functions - Setup
from functions.browsers import *
from functions.json import *
from functions.update import *
from settings.setup import *
from settings.sources import *
# Configuration
@ -63,9 +66,13 @@ def config_explorer_system():
write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True)
def config_explorer_user():
"""Configure Windows Explorer for current user."""
write_registry_settings(SETTINGS_EXPLORER_USER, all_users=False)
def config_explorer_user(setup_mode='All'):
"""Configure Windows Explorer for current user per setup_mode."""
settings_explorer_user = {
k: v for k, v in SETTINGS_EXPLORER_USER.items()
if setup_mode not in v.get('Invalid modes', [])
}
write_registry_settings(settings_explorer_user, all_users=False)
def config_windows_updates():
@ -75,7 +82,7 @@ def config_windows_updates():
def update_clock():
"""Set Timezone and sync clock."""
run_program(['tzutil' ,'/s', WINDOWS_TIME_ZONE], check=False)
run_program(['tzutil', '/s', WINDOWS_TIME_ZONE], check=False)
run_program(['net', 'stop', 'w32ime'], check=False)
run_program(
['w32tm', '/config', '/syncfromflags:manual',
@ -107,6 +114,39 @@ def write_registry_settings(settings, all_users=False):
# Installations
def find_current_software():
"""Find currently installed software, returns list."""
ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
installers = []
# Browsers
scan_for_browsers(silent=True)
for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
if is_installed(browser):
installers.append(
r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
# TODO: Add more sections
return installers
def find_missing_software():
"""Find missing software based on dirs/files present, returns list."""
ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
installers = []
# Browsers
scan_for_browsers(silent=True)
for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
if profile_present(browser):
installers.append(
r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
# TODO: Add more sections
return installers
def install_adobe_reader():
"""Install Adobe Reader."""
cmd = [
@ -159,29 +199,115 @@ def install_firefox_extensions():
run_program(cmd)
def install_ninite_bundle(mse=False, libreoffice=False):
def install_libreoffice(
quickstart=True, register_mso_types=True,
use_mso_formats=False, vcredist=False):
"""Install LibreOffice using specified settings."""
cmd = [
'msiexec', '/passive', '/norestart',
'/i', r'{}\Installers\Extras\Office\LibreOffice.msi'.format(
global_vars['BaseDir']),
'REBOOTYESNO=No',
'ISCHECKFORPRODUCTUPDATES=0',
'QUICKSTART={}'.format(1 if quickstart else 0),
'UI_LANGS=en_US',
'VC_REDIST={}'.format(1 if vcredist else 0),
]
if register_mso_types:
cmd.append('REGISTER_ALL_MSO_TYPES=1')
else:
cmd.append('REGISTER_NO_MSO_TYPES=1')
xcu_dir = r'{APPDATA}\LibreOffice\4\user'.format(**global_vars['Env'])
xcu_file = r'{}\registrymodifications.xcu'.format(xcu_dir)
# Set default save format
if use_mso_formats and not os.path.exists(xcu_file):
os.makedirs(xcu_dir, exist_ok=True)
with open(xcu_file, 'w', encoding='utf-8', newline='\n') as f:
f.write(LIBREOFFICE_XCU_DATA)
# Install LibreOffice
run_program(cmd, check=True)
def install_ninite_bundle(
# pylint: disable=too-many-arguments,too-many-branches
base=True,
browsers_only=False,
libreoffice=False,
missing=False,
mse=False,
standard=True,
):
"""Run Ninite installer(s), returns list of Popen objects."""
popen_objects = []
if global_vars['OS']['Version'] in ('8', '8.1', '10'):
# Modern selection
popen_objects.append(
popen_program(r'{BaseDir}\Installers\Extras\Bundles\Modern.exe'.format(
**global_vars)))
else:
# Legacy selection
if mse:
cmd = r'{BaseDir}\Installers\Extras\Security'.format(**global_vars)
cmd += r'\Microsoft Security Essentials.exe'
popen_objects.append(popen_program(cmd))
popen_objects.append(
popen_program(r'{BaseDir}\Installers\Extras\Bundles\Legacy.exe'.format(
**global_vars)))
if browsers_only:
# This option is deprecated
installer_path = r'{BaseDir}\Installers\Extras\Web Browsers'.format(
**global_vars)
scan_for_browsers(silent=True)
for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
if is_installed(browser):
cmd = r'{}\{}.exe'.format(installer_path, browser)
popen_objects.append(popen_program(cmd))
# Bail
return popen_objects
# Main selections
main_selections = []
if base:
main_selections.append('base')
if standard:
if global_vars['OS']['Version'] in ('8', '8.1', '10'):
main_selections.append('standard')
else:
main_selections.append('standard7')
if main_selections:
# Only run if base and/or standard are enabled
cmd = r'{}\Installers\Extras\Bundles\{}.exe'.format(
global_vars['BaseDir'],
'-'.join(main_selections),
)
popen_objects.append(popen_program([cmd]))
# Extra selections
extra_selections = {}
for cmd in find_current_software():
extra_selections[cmd] = True
if missing:
for cmd in find_missing_software():
extra_selections[cmd] = True
# Remove overlapping selections
regex = []
for n_name, n_group in NINITE_REGEX.items():
if n_name in main_selections:
regex.extend(n_group)
regex = '({})'.format('|'.join(regex))
extra_selections = {
cmd: True for cmd in extra_selections
if not re.search(regex, cmd, re.IGNORECASE)
}
# Start extra selections
for cmd in extra_selections:
popen_objects.append(popen_program([cmd]))
# Microsoft Security Essentials
if mse:
cmd = r'{}\Installers\Extras\Security\{}'.format(
global_vars['BaseDir'],
'Microsoft Security Essentials.exe',
)
popen_objects.append(popen_program([cmd]))
# LibreOffice
if libreoffice:
cmd = r'{BaseDir}\Installers\Extras\Office'.format(**global_vars)
cmd += r'\LibreOffice.exe'
popen_objects.append(popen_program(cmd))
cmd = r'{}\Installers\Extras\Office\{}'.format(
global_vars['BaseDir'],
'LibreOffice.exe',
)
popen_objects.append(popen_program([cmd]))
# Done
return popen_objects
@ -208,6 +334,10 @@ def open_device_manager():
popen_program(['mmc', 'devmgmt.msc'])
def open_speedtest():
popen_program(['start', '', 'https://fast.com'], shell=True)
def open_windows_activation():
popen_program(['slui'])

View file

@ -6,6 +6,35 @@ from functions.common import *
from settings.sw_diags import *
def check_4k_alignment(show_alert=False):
"""Check that all partitions are 4K aligned."""
aligned = True
cmd = ['WMIC', 'partition', 'get', 'StartingOffset']
offsets = []
# Get offsets
result = run_program(cmd, encoding='utf-8', errors='ignore', check=False)
offsets = result.stdout.splitlines()
# Check offsets
for off in offsets:
off = off.strip()
if not off.isnumeric():
# Skip
continue
try:
aligned = aligned and int(off) % 4096 == 0
except ValueError:
# Ignore, this check is low priority
pass
# Show alert
if show_alert:
show_alert_box('One or more partitions are not 4K aligned')
raise Not4KAlignedError
def check_connection():
"""Check if the system is online and optionally abort the script."""
while True:
@ -19,6 +48,37 @@ def check_connection():
abort()
def check_os_support_status():
"""Check if current OS is supported."""
msg = ''
outdated = False
unsupported = False
# Check OS version/notes
os_info = global_vars['OS'].copy()
if os_info['Notes'] == 'unsupported':
msg = 'The installed version of Windows is no longer supported'
unsupported = True
elif os_info['Notes'] == 'preview build':
msg = 'Preview builds are not officially supported'
unsupported = True
elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
msg = 'The installed version of Windows is outdated'
outdated = True
if 'Preview' not in msg:
msg += '\n\nPlease consider upgrading before continuing setup.'
# Show alert
if outdated or unsupported:
show_alert_box(msg)
# Raise exception if necessary
if outdated:
raise WindowsOutdatedError
if unsupported:
raise WindowsUnsupportedError
def check_secure_boot_status(show_alert=False):
"""Checks UEFI Secure Boot status via PowerShell."""
boot_mode = get_boot_mode()
@ -81,33 +141,6 @@ def get_boot_mode():
return type_str
def os_is_unsupported(show_alert=False):
"""Checks if the current OS is unsupported, returns bool."""
msg = ''
unsupported = False
# Check OS version/notes
os_info = global_vars['OS'].copy()
if os_info['Notes'] == 'unsupported':
msg = 'The installed version of Windows is no longer supported'
unsupported = True
elif os_info['Notes'] == 'preview build':
msg = 'Preview builds are not officially supported'
unsupported = True
elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
msg = 'The installed version of Windows is outdated'
unsupported = True
if 'Preview' not in msg:
msg += '\n\nPlease consider upgrading before continuing setup.'
# Show alert
if unsupported and show_alert:
show_alert_box(msg)
# Done
return unsupported
def run_autoruns():
"""Run AutoRuns in the background with VirusTotal checks enabled."""
extract_item('Autoruns', filter='autoruns*', silent=True)
@ -197,8 +230,10 @@ def run_rkill():
shutil.move(item.path, dest)
def show_alert_box(message, title='Wizard Kit Warning'):
def show_alert_box(message, title=None):
"""Show Windows alert box with message."""
if not title:
title = '{} Warning'.format(KIT_NAME_FULL)
message_box = ctypes.windll.user32.MessageBoxW
message_box(None, message, title, 0x00001030)

View file

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

View file

@ -615,6 +615,22 @@ def update_adobe_reader_dc():
dest, 'Adobe Reader DC.exe', SOURCE_URLS['Adobe Reader DC'])
def update_libreoffice():
# Prep
dest = r'{}\Installers\Extras\Office'.format(
global_vars['BaseDir'])
# Remove existing installer
try:
os.remove(r'{}\LibreOffice.msi'.format(dest))
except FileNotFoundError:
pass
# Download
download_generic(
dest, 'LibreOffice.msi', SOURCE_URLS['LibreOffice'])
def update_macs_fan_control():
# Prep
dest = r'{}\Installers'.format(

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)
# Done
sleep(10)
sleep(1)
pause('Press Enter to exit...')
exit_script(1)

View file

@ -25,7 +25,6 @@ if __name__ == '__main__':
'UnsupportedOSError': 'Unsupported OS',
}}
answer_extensions = ask('Install Extensions?')
answer_adobe_reader = ask('Install Adobe Reader?')
answer_vcr = ask('Install Visual C++ Runtimes?')
answer_ninite = ask('Install Ninite Bundle?')
if answer_ninite and global_vars['OS']['Version'] in ['7']:
@ -35,9 +34,6 @@ if __name__ == '__main__':
answer_mse = False
print_info('Installing Programs')
if answer_adobe_reader:
try_and_print(message='Adobe Reader DC...',
function=install_adobe_reader, other_results=other_results)
if answer_vcr:
install_vcredists()
if answer_ninite:

View file

@ -16,9 +16,6 @@ if __name__ == '__main__':
# Prep
clear_screen()
# Connect
connect_to_network()
# Mount
if is_connected():
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
# General
MAP_DIR = '/Backups/ddrescue-tui'
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
RECOMMENDED_MAP_FSTYPES = ['cifs', 'ext2', 'ext3', 'ext4', 'vfat', 'xfs']
USAGE = """ {script_name} clone [source [destination]]
{script_name} image [source [destination]]
(e.g. {script_name} clone /dev/sda /dev/sdb)
@ -36,6 +38,12 @@ DDRESCUE_SETTINGS = {
'-vvvv': {'Enabled': True, 'Hidden': True, },
}
ETOC_REFRESH_RATE = 30 # in seconds
REGEX_DDRESCUE_LOG = re.compile(
r'^\s*(?P<key>\S+):\s+'
r'(?P<size>\d+)\s+'
r'(?P<unit>[PTGMKB])i?B?',
re.IGNORECASE,
)
REGEX_REMAINING_TIME = re.compile(
r'remaining time:'
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 = {
r'(Root)': {
'Activate Windows': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'activate.py',
'L_ELEV': 'True',
},
'New System Setup': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'new_system_setup.py',
'L_ELEV': 'True',
},
'System Checklist': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'system_checklist.py',
'L_ELEV': 'True',
},
'System Diagnostics': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'system_diagnostics.py',
'L_ELEV': 'True',
},
'User Checklist': {
'System Setup': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'user_checklist.py',
'L_ITEM': 'system_setup.py',
'L_ELEV': 'True',
},
},
r'Data Recovery': {
@ -55,6 +40,7 @@ LAUNCHERS = {
},
},
r'Data Transfers': {
# pylint: disable=bad-continuation
'FastCopy (as ADMIN)': {
'L_TYPE': 'Executable',
'L_PATH': 'FastCopy',
@ -257,7 +243,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'erunt',
'L_ITEM': 'ERUNT.EXE',
'L_ARGS': '%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
'L_ARGS': r'%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
'L_ELEV': 'True',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@ -287,13 +273,13 @@ LAUNCHERS = {
r'Drivers': {
'Intel RST (Current Release)': {
'L_TYPE': 'Executable',
'L_PATH': '_Drivers\Intel RST',
'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': 'SetupRST_17.2.exe',
'L_7ZIP': 'SetupRST_17.2.exe',
},
'Intel RST (Previous Releases)': {
'L_TYPE': 'Folder',
'L_PATH': '_Drivers\Intel RST',
'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': '.',
'L_NCMD': 'True',
},
@ -309,7 +295,7 @@ LAUNCHERS = {
},
'Snappy Driver Installer Origin': {
'L_TYPE': 'Executable',
'L_PATH': '_Drivers\SDIO',
'L_PATH': r'_Drivers\SDIO',
'L_ITEM': 'SDIO.exe',
},
},
@ -435,6 +421,12 @@ LAUNCHERS = {
},
},
r'Misc': {
'Activate Windows': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'activate.py',
'L_ELEV': 'True',
},
'Cleanup CBS Temp Files': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
@ -452,6 +444,20 @@ LAUNCHERS = {
'L_PATH': 'ConEmu',
'L_ITEM': 'ConEmu.exe',
},
'Disable Windows Updates': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'windows_updates.py',
'L_ARGS': '--disable',
'L_ELEV': 'True',
},
'Enable Windows Updates': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'windows_updates.py',
'L_ARGS': '--enable',
'L_ELEV': 'True',
},
'Enter SafeMode': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
@ -491,7 +497,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'XMPlay',
'L_ITEM': 'xmplay.exe',
'L_ARGS': '"%bin%\XMPlay\music.7z"',
'L_ARGS': r'"%bin%\XMPlay\music.7z"',
},
},
r'Repairs': {
@ -551,7 +557,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'RKill',
'L_ITEM': 'RKill.exe',
'L_ARGS': '-s -l %log_dir%\Tools\RKill.log',
'L_ARGS': r'-s -l %log_dir%\Tools\RKill.log',
'L_ELEV': 'True',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@ -594,5 +600,3 @@ LAUNCHERS = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2

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 winreg
try:
import winreg
HKU = winreg.HKEY_USERS
HKCR = winreg.HKEY_CLASSES_ROOT
HKCU = winreg.HKEY_CURRENT_USER
HKLM = winreg.HKEY_LOCAL_MACHINE
except ImportError:
if os.name != 'posix':
raise
# General
HKU = winreg.HKEY_USERS
HKCR = winreg.HKEY_CLASSES_ROOT
HKCU = winreg.HKEY_CURRENT_USER
HKLM = winreg.HKEY_LOCAL_MACHINE
OTHER_RESULTS = {
'Error': {
'CalledProcessError': 'Unknown Error',
@ -92,6 +98,15 @@ SETTINGS_EXPLORER_SYSTEM = {
},
}
SETTINGS_EXPLORER_USER = {
# Desktop theme
r'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize': {
'Invalid modes': ['Cur'],
'DWORD Items': {
# <= v1809 default
'AppsUseLightTheme': 1,
'SystemUsesLightTheme': 0,
},
},
# Disable features
r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': {
'DWORD Items': {
@ -104,21 +119,41 @@ SETTINGS_EXPLORER_USER = {
},
# File Explorer
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
'Invalid modes': ['Cur'],
'DWORD Items': {
# Change default Explorer view to "Computer"
'LaunchTo': 1,
},
},
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
# Dup path so it Will be applied to all modes
'DWORD Items': {
# Launch Folder Windows in a Separate Process
'SeparateProcess': 1,
},
},
# Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
'Invalid modes': ['Cur'],
'DWORD Items': {'PeopleBand': 0},
},
# Hide Search button / box
r'Software\Microsoft\Windows\CurrentVersion\Search': {
'Invalid modes': ['Cur'],
'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
VCR_REDISTS = [
{'Name': 'Visual C++ 2010 x32...',
@ -157,5 +192,3 @@ SETTINGS_WINDOWS_UPDATES = {
if __name__ == '__main__':
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 = {
'Adobe Reader DC': 'http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1901020098/AcroRdrDC1901020098_en_US.exe',
@ -15,7 +17,7 @@ SOURCE_URLS = {
'ERUNT': 'http://www.aumha.org/downloads/erunt.zip',
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.935.x86.en-US.zip',
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.935.x64.en-US.zip',
'FastCopy': 'http://ftp.vector.co.jp/71/31/2323/FastCopy363_installer.exe',
'FastCopy': 'https://fastcopy.jp/archive/FastCopy380_installer.exe',
'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/1709472/ublock_origin-1.18.6-an+fx.xpi',
'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
@ -23,6 +25,7 @@ SOURCE_URLS = {
'Intel SSD Toolbox': r'https://downloadmirror.intel.com/28593/eng/Intel%20SSD%20Toolbox%20-%20v3.5.9.exe',
'IOBit_Uninstaller': r'https://portableapps.com/redirect/?a=IObitUninstallerPortable&s=s&d=pa&f=IObitUninstallerPortable_7.5.0.7.paf.exe',
'KVRT': 'http://devbuilds.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe',
'LibreOffice': 'https://download.documentfoundation.org/libreoffice/stable/6.2.4/win/x86_64/LibreOffice_6.2.4_Win_x64.msi',
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip',
'NirCmd64': 'https://www.nirsoft.net/utils/nircmd-x64.zip',
@ -37,8 +40,8 @@ SOURCE_URLS = {
'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent',
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip',
'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-i686-bin.zip',
'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-x86_64-bin.zip',
'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-i686-bin.zip',
'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-x86_64-bin.zip',
'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip',
'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.zip',
'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962',
@ -66,10 +69,18 @@ VCREDIST_SOURCES = {
'64': 'https://aka.ms/vs/15/release/vc_redist.x64.exe',
},
}
NINITE_REGEX = {
'base': ['7-Zip', 'VLC'],
'standard': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
'standard7': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
}
NINITE_SOURCES = {
'Bundles': {
'Legacy.exe': '.net4.7.2-7zip-chrome-firefox-vlc',
'Modern.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-vlc',
'base.exe': '.net4.7.2-7zip-vlc',
'base-standard.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-sumatrapdf-vlc',
'base-standard7.exe': '.net4.7.2-7zip-chrome-firefox-sumatrapdf-vlc',
'standard.exe': 'chrome-classicstart-firefox-sumatrapdf',
'standard7.exe': 'chrome-firefox-sumatrapdf',
},
'Audio-Video': {
'AIMP.exe': 'aimp',
@ -216,5 +227,3 @@ WINDOWS_UPDATE_SOURCES = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
# vim: sts=2 sw=2 ts=2 tw=0

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

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
print_info(' Installers')
try_and_print(message='Adobe Reader DC...', function=update_adobe_reader_dc, other_results=other_results, width=40)
try_and_print(message='LibreOffice...', function=update_libreoffice, other_results=other_results, width=40)
try_and_print(message='Macs Fan Control...', function=update_macs_fan_control, other_results=other_results, width=40)
try_and_print(message='MS Office...', function=update_office, other_results=other_results, width=40)
try_and_print(message='Visual C++ Runtimes...', function=update_vcredists, other_results=other_results, width=40)

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 umount='sudo umount'
alias unmount='sudo umount'
alias wkclone='sudo ddrescue-tui clone'
alias wkimage='sudo ddrescue-tui image'
alias wkclone='ddrescue-tui clone'
alias wkimage='ddrescue-tui image'

View file

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