From f8773e173616ae10eaa484bc50dbe994df3bb688 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Sun, 5 May 2019 12:53:07 -0600
Subject: [PATCH 01/63] Add known networks when building the Linux ISO
* Instead of at login
---
.bin/Scripts/{setup-wifi => add-known-networks} | 0
.linux_items/include/airootfs/etc/skel/.update_network | 5 ++---
Build Linux | 1 +
3 files changed, 3 insertions(+), 3 deletions(-)
rename .bin/Scripts/{setup-wifi => add-known-networks} (100%)
diff --git a/.bin/Scripts/setup-wifi b/.bin/Scripts/add-known-networks
similarity index 100%
rename from .bin/Scripts/setup-wifi
rename to .bin/Scripts/add-known-networks
diff --git a/.linux_items/include/airootfs/etc/skel/.update_network b/.linux_items/include/airootfs/etc/skel/.update_network
index 0b66717f..fb3ec990 100755
--- a/.linux_items/include/airootfs/etc/skel/.update_network
+++ b/.linux_items/include/airootfs/etc/skel/.update_network
@@ -2,9 +2,8 @@
#
## Setup network and update hostname
-# Add saved networks to NetworkManager
-sudo setup-wifi
-sudo systemctl restart NetworkManager
+# Wait for WiFi
+sleep 1s
# Set hostname
IP="$(ip a show scope global \
diff --git a/Build Linux b/Build Linux
index effd9155..980c2e77 100755
--- a/Build Linux
+++ b/Build Linux
@@ -284,6 +284,7 @@ function update_live_env() {
# WiFi
cp "$ROOT_DIR/.linux_items/known_networks" "/root/known_networks"
+ echo "add-known-networks" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
}
function update_repo() {
From b554cfebe0a23b978bc01147a970cdb33ad4be05 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 8 May 2019 19:38:48 -0600
Subject: [PATCH 02/63] Update tmux config to work with v2.9
---
.linux_items/include/airootfs/etc/skel/.tmux.conf | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.linux_items/include/airootfs/etc/skel/.tmux.conf b/.linux_items/include/airootfs/etc/skel/.tmux.conf
index a3712835..c82d4600 100644
--- a/.linux_items/include/airootfs/etc/skel/.tmux.conf
+++ b/.linux_items/include/airootfs/etc/skel/.tmux.conf
@@ -1,5 +1,5 @@
set -g status off
-set -g pane-active-border-fg white
+set -g pane-active-border-style fg=white
# Window names
set -g set-titles on
From d51538aac3ebafe68f7d47386af42393e308237e Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 16:52:17 -0600
Subject: [PATCH 03/63] Fixed handling of known_networks
---
.bin/Scripts/add-known-networks | 11 ++++++++++-
Build Linux | 4 ++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/.bin/Scripts/add-known-networks b/.bin/Scripts/add-known-networks
index 6047c438..76316055 100644
--- a/.bin/Scripts/add-known-networks
+++ b/.bin/Scripts/add-known-networks
@@ -4,6 +4,7 @@
import os
import re
+import sys
import uuid
KNOWN_NETWORKS = '/root/known_networks'
@@ -34,13 +35,21 @@ method=auto
'''
def get_user_name():
- """Get real user name, returns str."""
+ """Get user name, returns str."""
user = None
+
+ # Get running user
if 'SUDO_USER' in os.environ:
user = os.environ.get('SUDO_USER')
else:
user = os.environ.get('USER')
+ # Check if user manually specified
+ for a in sys.argv:
+ a = a.strip().lower()
+ if a.startswith('--user='):
+ user = a.replace('--user=', '')
+
return user
if __name__ == '__main__':
diff --git a/Build Linux b/Build Linux
index 980c2e77..f9acd897 100755
--- a/Build Linux
+++ b/Build Linux
@@ -283,8 +283,8 @@ function update_live_env() {
fi
# WiFi
- cp "$ROOT_DIR/.linux_items/known_networks" "/root/known_networks"
- echo "add-known-networks" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
+ cp "$ROOT_DIR/.linux_items/known_networks" "$LIVE_DIR/airootfs/root/known_networks"
+ echo "add-known-networks --user=$username" >> "$LIVE_DIR/airootfs/root/customize_airootfs.sh"
}
function update_repo() {
From 617bb1484a2494ddf28d95b8c49dd08de0750937 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 16:54:40 -0600
Subject: [PATCH 04/63] Name connections by SSID
---
.bin/Scripts/add-known-networks | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/add-known-networks b/.bin/Scripts/add-known-networks
index 76316055..bdfc7a1c 100644
--- a/.bin/Scripts/add-known-networks
+++ b/.bin/Scripts/add-known-networks
@@ -63,7 +63,7 @@ if __name__ == '__main__':
for ssid, password in known_networks.items():
out_path = '{}/{}.nmconnection'.format(
'/etc/NetworkManager/system-connections',
- password,
+ ssid,
)
if not os.path.exists(out_path):
with open(out_path, 'w') as f:
From 798876eb10fc5ce62a0b8ea069f41eb08fc620c0 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 17:06:11 -0600
Subject: [PATCH 05/63] Updated UFD sections
---
.bin/Scripts/build-ufd | 21 +++++++++++----------
.bin/Scripts/functions/ufd.py | 20 ++++++++++++++------
2 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/.bin/Scripts/build-ufd b/.bin/Scripts/build-ufd
index bb0813a8..bd878a12 100755
--- a/.bin/Scripts/build-ufd
+++ b/.bin/Scripts/build-ufd
@@ -54,16 +54,8 @@ if __name__ == '__main__':
confirm_selections(args)
# Prep UFD
- print_info('Prep UFD')
- if args['--update']:
- # Remove arch folder
- try_and_print(
- indent=2,
- message='Removing Linux...',
- function=remove_arch,
- )
- else:
- # Format and partition
+ if not args['--update']:
+ print_info('Prep UFD')
prep_device(ufd_dev, UFD_LABEL, use_mbr=args['--use-mbr'])
# Mount UFD
@@ -73,8 +65,17 @@ if __name__ == '__main__':
function=mount,
mount_source=find_first_partition(ufd_dev),
mount_point='/mnt/UFD',
+ read_write=True,
)
+ # Remove Arch folder
+ if args['--update']:
+ try_and_print(
+ indent=2,
+ message='Removing Linux...',
+ function=remove_arch,
+ )
+
# Copy sources
print_standard(' ')
print_info('Copy Sources')
diff --git a/.bin/Scripts/functions/ufd.py b/.bin/Scripts/functions/ufd.py
index 598985d3..32f08201 100644
--- a/.bin/Scripts/functions/ufd.py
+++ b/.bin/Scripts/functions/ufd.py
@@ -60,16 +60,16 @@ def confirm_selections(args):
def copy_source(source, items, overwrite=False):
"""Copy source items to /mnt/UFD."""
- is_iso = source.name.lower().endswith('.iso')
+ is_image = source.is_file()
# Mount source if necessary
- if is_iso:
+ if is_image:
mount(source, '/mnt/Source')
# Copy items
for i_source, i_dest in items:
i_source = '{}{}'.format(
- '/mnt/Source' if is_iso else source,
+ '/mnt/Source' if is_image else source,
i_source,
)
i_dest = '/mnt/UFD{}'.format(i_dest)
@@ -80,7 +80,7 @@ def copy_source(source, items, overwrite=False):
pass
# Unmount source if necessary
- if is_iso:
+ if is_image:
unmount('/mnt/Source')
@@ -199,6 +199,8 @@ def is_valid_path(path_obj, path_type):
valid_path = path_obj.is_dir()
elif path_type == 'KIT':
valid_path = path_obj.is_dir() and path_obj.joinpath('.bin').exists()
+ elif path_type == 'IMG':
+ valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.img'
elif path_type == 'ISO':
valid_path = path_obj.is_file() and path_obj.suffix.lower() == '.iso'
elif path_type == 'UFD':
@@ -207,10 +209,16 @@ def is_valid_path(path_obj, path_type):
return valid_path
-def mount(mount_source, mount_point):
+def mount(mount_source, mount_point, read_write=False):
"""Mount mount_source on mount_point."""
os.makedirs(mount_point, exist_ok=True)
- cmd = ['mount', mount_source, mount_point]
+ cmd = [
+ 'mount',
+ mount_source,
+ mount_point,
+ '-o',
+ 'rw' if read_write else 'ro',
+ ]
run_program(cmd)
From b9276aa0d7a7c5fc440ff45a5c60515b579747b5 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 17:09:59 -0600
Subject: [PATCH 06/63] Updated build-ufd
---
.bin/Scripts/build-ufd | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/build-ufd b/.bin/Scripts/build-ufd
index bd878a12..45c3ff35 100755
--- a/.bin/Scripts/build-ufd
+++ b/.bin/Scripts/build-ufd
@@ -1,6 +1,6 @@
#!/bin/env python3
#
-# pylint: disable=no-name-in-module,wildcard-import
+# pylint: disable=no-name-in-module,wildcard-import,wrong-import-position
# vim: sts=2 sw=2 ts=2
"""Wizard Kit: UFD build tool"""
From 5ccd6282599f9109a6d47f8eab83ae7ba01c4da8 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 17:10:38 -0600
Subject: [PATCH 07/63] Updated check_disk.py
---
.bin/Scripts/check_disk.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/check_disk.py b/.bin/Scripts/check_disk.py
index 8919063e..fe18650b 100644
--- a/.bin/Scripts/check_disk.py
+++ b/.bin/Scripts/check_disk.py
@@ -38,7 +38,7 @@ if __name__ == '__main__':
if repair:
cs = 'Scheduled'
else:
- cs = 'CS'
+ cs = 'No issues'
message = 'CHKDSK ({SYSTEMDRIVE})...'.format(**global_vars['Env'])
try_and_print(message=message, function=run_chkdsk,
cs=cs, other_results=other_results, repair=repair)
From 7ccd4c605599fc74868fa385c4c2ada95d06d935 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:24:01 -0600
Subject: [PATCH 08/63] BITWISE operators =/= LOGICAL operators
---
.bin/Scripts/functions/browsers.py | 4 ++--
.bin/Scripts/functions/ddrescue.py | 13 +++++++------
.bin/Scripts/functions/hw_diags.py | 15 +++++++++------
3 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/.bin/Scripts/functions/browsers.py b/.bin/Scripts/functions/browsers.py
index dcb0ed2f..c4d110f7 100644
--- a/.bin/Scripts/functions/browsers.py
+++ b/.bin/Scripts/functions/browsers.py
@@ -32,8 +32,8 @@ def archive_all_users():
user_path = os.path.join(users_root, user_name)
appdata_local = os.path.join(user_path, r'AppData\Local')
appdata_roaming = os.path.join(user_path, r'AppData\Roaming')
- valid_user &= os.path.exists(appdata_local)
- valid_user &= os.path.exists(appdata_roaming)
+ valid_user = valid_user and os.path.exists(appdata_local)
+ valid_user = valid_user and os.path.exists(appdata_roaming)
if valid_user:
user_envs.append({
'USERNAME': user_name,
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index 545d08e0..4e6f7809 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -318,7 +318,7 @@ class RecoveryState():
"""Checks if pass is done for all block-pairs, returns bool."""
done = True
for bp in self.block_pairs:
- done &= bp.pass_done[self.current_pass]
+ done = done and bp.pass_done[self.current_pass]
return done
def current_pass_min(self):
@@ -376,7 +376,8 @@ class RecoveryState():
self.total_size = 0
for bp in self.block_pairs:
bp.self_check()
- self.resumed |= bp.resumed
+ if bp.resumed:
+ self.resumed = True
self.total_size += bp.size
def set_pass_num(self):
@@ -386,7 +387,7 @@ class RecoveryState():
# Iterate backwards through passes
pass_done = True
for bp in self.block_pairs:
- pass_done &= bp.pass_done[pass_num]
+ pass_done = pass_done and bp.pass_done[pass_num]
if pass_done:
# All block-pairs reported being done
# Set to next pass, unless we're on the last pass (2)
@@ -742,9 +743,9 @@ def is_writable_dir(dir_obj):
"""Check if we have read-write-execute permissions, returns bool."""
is_ok = True
path_st_mode = os.stat(dir_obj.path).st_mode
- is_ok == is_ok and path_st_mode & stat.S_IRUSR
- is_ok == is_ok and path_st_mode & stat.S_IWUSR
- is_ok == is_ok and path_st_mode & stat.S_IXUSR
+ is_ok = is_ok and path_st_mode & stat.S_IRUSR
+ is_ok = is_ok and path_st_mode & stat.S_IWUSR
+ is_ok = is_ok and path_st_mode & stat.S_IXUSR
return is_ok
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index bea5e01e..c085a5ed 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -186,8 +186,8 @@ class DiskObj():
disk_ok = False
# Disable override if necessary
- self.override_disabled |= ATTRIBUTES[attr_type][k].get(
- 'Critical', False)
+ if ATTRIBUTES[attr_type][k].get('Critical', False):
+ self.override_disabled = True
# SMART overall assessment
## NOTE: Only fail drives if the overall value exists and reports failed
@@ -788,7 +788,7 @@ def menu_diags(state, args):
# If so, verify no other tests are enabled and set quick_mode
state.quick_mode = True
for opt in main_options[3:4] + main_options[5:]:
- state.quick_mode &= not opt['Enabled']
+ state.quick_mode = state.quick_mode and not opt['Enabled']
else:
state.quick_mode = False
@@ -1001,7 +1001,8 @@ def run_hw_tests(state):
# Run disk safety checks (if necessary)
_disk_tests_enabled = False
for k in TESTS_DISK:
- _disk_tests_enabled |= state.tests[k]['Enabled']
+ if state.tests[k]['Enabled']:
+ _disk_tests_enabled = True
if _disk_tests_enabled:
for disk in state.disks:
try:
@@ -1611,7 +1612,8 @@ def show_results(state):
# CPU tests
_enabled = False
for k in TESTS_CPU:
- _enabled |= state.tests[k]['Enabled']
+ if state.tests[k]['Enabled']:
+ _enabled = True
if _enabled:
print_success('CPU:'.format(k))
show_report(state.cpu.generate_cpu_report(), log_report=True)
@@ -1620,7 +1622,8 @@ def show_results(state):
# Disk tests
_enabled = False
for k in TESTS_DISK:
- _enabled |= state.tests[k]['Enabled']
+ if state.tests[k]['Enabled']:
+ _enabled = True
if _enabled:
print_success('Disk{}:'.format(
'' if len(state.disks) == 1 else 's'))
From 35890d6bb31544ff379858f161ffd77a6496031b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:29:32 -0600
Subject: [PATCH 09/63] Fixed setting timezone in Windows
---
.bin/Scripts/functions/setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/functions/setup.py b/.bin/Scripts/functions/setup.py
index 05a9dca3..20c3eaf3 100644
--- a/.bin/Scripts/functions/setup.py
+++ b/.bin/Scripts/functions/setup.py
@@ -75,7 +75,7 @@ def config_windows_updates():
def update_clock():
"""Set Timezone and sync clock."""
- run_program(['tzutil' ,'/s', WINDOWS_TIME_ZONE], check=False)
+ run_program(['tzutil', '/s', WINDOWS_TIME_ZONE], check=False)
run_program(['net', 'stop', 'w32ime'], check=False)
run_program(
['w32tm', '/config', '/syncfromflags:manual',
From 214df527230a0e7c779481ec4fc597f58f755908 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:33:57 -0600
Subject: [PATCH 10/63] Expanded SW bundle sections
---
.bin/Scripts/functions/browsers.py | 31 ++++--
.bin/Scripts/functions/setup.py | 168 +++++++++++++++++++++++++----
.bin/Scripts/functions/update.py | 16 +++
.bin/Scripts/settings/sources.py | 25 +++--
.bin/Scripts/update_kit.py | 1 +
5 files changed, 205 insertions(+), 36 deletions(-)
diff --git a/.bin/Scripts/functions/browsers.py b/.bin/Scripts/functions/browsers.py
index c4d110f7..8a1f4dcb 100644
--- a/.bin/Scripts/functions/browsers.py
+++ b/.bin/Scripts/functions/browsers.py
@@ -325,7 +325,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
if just_firefox and browser_data[browser]['base'] != 'mozilla':
continue
exe_path = browser_data[browser].get('exe_path', None)
- function=run_program
if not exe_path:
if browser_data[browser]['profiles']:
print_standard(
@@ -375,7 +374,6 @@ def install_adblock(indent=8, width=32, just_firefox=False):
elif browser_data[browser]['base'] == 'ie':
urls.append(IE_GALLERY)
- function=popen_program
# By using check=False we're skipping any return codes so
# it should only fail if the program can't be run
@@ -384,10 +382,16 @@ def install_adblock(indent=8, width=32, just_firefox=False):
# installation status.
try_and_print(message='{}...'.format(browser),
indent=indent, width=width,
- cs='Done', function=function,
+ cs='Started', function=popen_program,
cmd=[exe_path, *urls], check=False)
+def is_installed(browser_name):
+ """Checks if browser is installed based on exe_path, returns bool."""
+ browser_name = browser_name.replace(' Chromium', '')
+ return bool(browser_data.get(browser_name, {}).get('exe_path', False))
+
+
def list_homepages(indent=8, width=32):
"""List current homepages for reference."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['exe_path']]
@@ -419,6 +423,12 @@ def list_homepages(indent=8, width=32):
indent=' '*indent, width=width, name=name, page=page))
+def profile_present(browser_name):
+ """Checks if a profile was detected for browser, returns bool."""
+ browser_name = browser_name.replace(' Chromium', '')
+ return bool(browser_data.get(browser_name, {}).get('profiles', False))
+
+
def reset_browsers(indent=8, width=32):
"""Reset all detected browsers to safe defaults."""
browser_list = [k for k, v in sorted(browser_data.items()) if v['profiles']]
@@ -437,14 +447,21 @@ def reset_browsers(indent=8, width=32):
other_results=other_results, profile=profile)
-def scan_for_browsers(just_firefox=False):
+def scan_for_browsers(just_firefox=False, silent=False):
"""Scan system for any supported browsers."""
for name, details in sorted(SUPPORTED_BROWSERS.items()):
if just_firefox and details['base'] != 'mozilla':
continue
- try_and_print(message='{}...'.format(name),
- function=get_browser_details, cs='Detected',
- other_results=other_results, name=name)
+ if silent:
+ try:
+ get_browser_details(name)
+ except Exception:
+ # Ignore errors in silent mode
+ pass
+ else:
+ try_and_print(message='{}...'.format(name),
+ function=get_browser_details, cs='Detected',
+ other_results=other_results, name=name)
if __name__ == '__main__':
diff --git a/.bin/Scripts/functions/setup.py b/.bin/Scripts/functions/setup.py
index 20c3eaf3..ad575f75 100644
--- a/.bin/Scripts/functions/setup.py
+++ b/.bin/Scripts/functions/setup.py
@@ -1,7 +1,10 @@
# Wizard Kit: Functions - Setup
+from functions.browsers import *
+from functions.json import *
from functions.update import *
from settings.setup import *
+from settings.sources import *
# Configuration
@@ -63,9 +66,13 @@ def config_explorer_system():
write_registry_settings(SETTINGS_EXPLORER_SYSTEM, all_users=True)
-def config_explorer_user():
- """Configure Windows Explorer for current user."""
- write_registry_settings(SETTINGS_EXPLORER_USER, all_users=False)
+def config_explorer_user(setup_mode='All'):
+ """Configure Windows Explorer for current user per setup_mode."""
+ settings_explorer_user = {
+ k: v for k, v in SETTINGS_EXPLORER_USER.items()
+ if setup_mode not in v.get('Invalid modes', [])
+ }
+ write_registry_settings(settings_explorer_user, all_users=False)
def config_windows_updates():
@@ -107,6 +114,39 @@ def write_registry_settings(settings, all_users=False):
# Installations
+def find_current_software():
+ """Find currently installed software, returns list."""
+ ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
+ installers = []
+
+ # Browsers
+ scan_for_browsers(silent=True)
+ for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
+ if is_installed(browser):
+ installers.append(
+ r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
+
+ # TODO: Add more sections
+
+ return installers
+
+def find_missing_software():
+ """Find missing software based on dirs/files present, returns list."""
+ ninite_extras_path = r'{BaseDir}\Installers\Extras'.format(**global_vars)
+ installers = []
+
+ # Browsers
+ scan_for_browsers(silent=True)
+ for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
+ if profile_present(browser):
+ installers.append(
+ r'{}\Web Browsers\{}.exe'.format(ninite_extras_path, browser))
+
+ # TODO: Add more sections
+
+ return installers
+
+
def install_adobe_reader():
"""Install Adobe Reader."""
cmd = [
@@ -159,29 +199,115 @@ def install_firefox_extensions():
run_program(cmd)
-def install_ninite_bundle(mse=False, libreoffice=False):
+def install_libreoffice(
+ quickstart=True, register_mso_types=True,
+ use_mso_formats=False, vcredist=False):
+ """Install LibreOffice using specified settings."""
+ cmd = [
+ 'msiexec', '/passive', '/norestart',
+ '/i', r'{}\Installers\Extras\Office\LibreOffice.msi'.format(
+ global_vars['BaseDir']),
+ 'REBOOTYESNO=No',
+ 'ISCHECKFORPRODUCTUPDATES=0',
+ 'QUICKSTART={}'.format(1 if quickstart else 0),
+ 'UI_LANGS=en_US',
+ 'VC_REDIST={}'.format(1 if vcredist else 0),
+ ]
+ if register_mso_types:
+ cmd.append('REGISTER_ALL_MSO_TYPES=1')
+ else:
+ cmd.append('REGISTER_NO_MSO_TYPES=1')
+ xcu_dir = r'{APPDATA}\LibreOffice\4\user'.format(**global_vars['Env'])
+ xcu_file = r'{}\registrymodifications.xcu'.format(xcu_dir)
+
+ # Set default save format
+ if use_mso_formats and not os.path.exists(xcu_file):
+ os.makedirs(xcu_dir, exist_ok=True)
+ with open(xcu_file, 'w', encoding='utf-8', newline='\n') as f:
+ f.write(LIBREOFFICE_XCU_DATA)
+
+ # Install LibreOffice
+ run_program(cmd, check=True)
+
+def install_ninite_bundle(
+ # pylint: disable=too-many-arguments,too-many-branches
+ base=True,
+ browsers_only=False,
+ libreoffice=False,
+ missing=False,
+ mse=False,
+ standard=True,
+ ):
"""Run Ninite installer(s), returns list of Popen objects."""
popen_objects = []
- if global_vars['OS']['Version'] in ('8', '8.1', '10'):
- # Modern selection
- popen_objects.append(
- popen_program(r'{BaseDir}\Installers\Extras\Bundles\Modern.exe'.format(
- **global_vars)))
- else:
- # Legacy selection
- if mse:
- cmd = r'{BaseDir}\Installers\Extras\Security'.format(**global_vars)
- cmd += r'\Microsoft Security Essentials.exe'
- popen_objects.append(popen_program(cmd))
- popen_objects.append(
- popen_program(r'{BaseDir}\Installers\Extras\Bundles\Legacy.exe'.format(
- **global_vars)))
+ if browsers_only:
+ # This option is deprecated
+ installer_path = r'{BaseDir}\Installers\Extras\Web Browsers'.format(
+ **global_vars)
+ scan_for_browsers(silent=True)
+ for browser in ('Google Chrome', 'Mozilla Firefox', 'Opera Chromium'):
+ if is_installed(browser):
+ cmd = r'{}\{}.exe'.format(installer_path, browser)
+ popen_objects.append(popen_program(cmd))
+
+ # Bail
+ return popen_objects
+
+ # Main selections
+ main_selections = []
+ if base:
+ main_selections.append('base')
+ if standard:
+ if global_vars['OS']['Version'] in ('8', '8.1', '10'):
+ main_selections.append('standard')
+ else:
+ main_selections.append('standard7')
+ if main_selections:
+ # Only run if base and/or standard are enabled
+ cmd = r'{}\Installers\Extras\Bundles\{}.exe'.format(
+ global_vars['BaseDir'],
+ '-'.join(main_selections),
+ )
+ popen_objects.append(popen_program([cmd]))
+
+ # Extra selections
+ extra_selections = {}
+ for cmd in find_current_software():
+ extra_selections[cmd] = True
+ if missing:
+ for cmd in find_missing_software():
+ extra_selections[cmd] = True
+
+ # Remove overlapping selections
+ regex = []
+ for n_name, n_group in NINITE_REGEX.items():
+ if n_name in main_selections:
+ regex.extend(n_group)
+ regex = '({})'.format('|'.join(regex))
+ extra_selections = {
+ cmd: True for cmd in extra_selections
+ if not re.search(regex, cmd, re.IGNORECASE)
+ }
+
+ # Start extra selections
+ for cmd in extra_selections:
+ popen_objects.append(popen_program([cmd]))
+
+ # Microsoft Security Essentials
+ if mse:
+ cmd = r'{}\Installers\Extras\Security\{}'.format(
+ global_vars['BaseDir'],
+ 'Microsoft Security Essentials.exe',
+ )
+ popen_objects.append(popen_program([cmd]))
# LibreOffice
if libreoffice:
- cmd = r'{BaseDir}\Installers\Extras\Office'.format(**global_vars)
- cmd += r'\LibreOffice.exe'
- popen_objects.append(popen_program(cmd))
+ cmd = r'{}\Installers\Extras\Office\{}'.format(
+ global_vars['BaseDir'],
+ 'LibreOffice.exe',
+ )
+ popen_objects.append(popen_program([cmd]))
# Done
return popen_objects
diff --git a/.bin/Scripts/functions/update.py b/.bin/Scripts/functions/update.py
index 40bc3a27..38f881d5 100755
--- a/.bin/Scripts/functions/update.py
+++ b/.bin/Scripts/functions/update.py
@@ -615,6 +615,22 @@ def update_adobe_reader_dc():
dest, 'Adobe Reader DC.exe', SOURCE_URLS['Adobe Reader DC'])
+def update_libreoffice():
+ # Prep
+ dest = r'{}\Installers\Extras\Office'.format(
+ global_vars['BaseDir'])
+
+ # Remove existing installer
+ try:
+ os.remove(r'{}\LibreOffice.msi'.format(dest))
+ except FileNotFoundError:
+ pass
+
+ # Download
+ download_generic(
+ dest, 'LibreOffice.msi', SOURCE_URLS['LibreOffice'])
+
+
def update_macs_fan_control():
# Prep
dest = r'{}\Installers'.format(
diff --git a/.bin/Scripts/settings/sources.py b/.bin/Scripts/settings/sources.py
index e6cf5c3a..2abf079f 100644
--- a/.bin/Scripts/settings/sources.py
+++ b/.bin/Scripts/settings/sources.py
@@ -1,4 +1,6 @@
-# Wizard Kit: Settings - Sources
+'''Wizard Kit: Settings - Sources'''
+# pylint: disable=line-too-long
+# vim: sts=2 sw=2 ts=2 tw=0
SOURCE_URLS = {
'Adobe Reader DC': 'http://ardownload.adobe.com/pub/adobe/reader/win/AcrobatDC/1901020098/AcroRdrDC1901020098_en_US.exe',
@@ -15,7 +17,7 @@ SOURCE_URLS = {
'ERUNT': 'http://www.aumha.org/downloads/erunt.zip',
'Everything32': 'https://www.voidtools.com/Everything-1.4.1.935.x86.en-US.zip',
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.935.x64.en-US.zip',
- 'FastCopy': 'http://ftp.vector.co.jp/71/31/2323/FastCopy363_installer.exe',
+ 'FastCopy': 'https://fastcopy.jp/archive/FastCopy380_installer.exe',
'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/1709472/ublock_origin-1.18.6-an+fx.xpi',
'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
@@ -23,6 +25,7 @@ SOURCE_URLS = {
'Intel SSD Toolbox': r'https://downloadmirror.intel.com/28593/eng/Intel%20SSD%20Toolbox%20-%20v3.5.9.exe',
'IOBit_Uninstaller': r'https://portableapps.com/redirect/?a=IObitUninstallerPortable&s=s&d=pa&f=IObitUninstallerPortable_7.5.0.7.paf.exe',
'KVRT': 'http://devbuilds.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe',
+ 'LibreOffice': 'https://download.documentfoundation.org/libreoffice/stable/6.2.4/win/x86_64/LibreOffice_6.2.4_Win_x64.msi',
'Macs Fan Control': 'https://www.crystalidea.com/downloads/macsfancontrol_setup.exe',
'NirCmd32': 'https://www.nirsoft.net/utils/nircmd.zip',
'NirCmd64': 'https://www.nirsoft.net/utils/nircmd-x64.zip',
@@ -37,8 +40,8 @@ SOURCE_URLS = {
'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent',
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
'TestDisk': 'https://www.cgsecurity.org/testdisk-7.1-WIP.win.zip',
- 'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-i686-bin.zip',
- 'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.0-windows-x86_64-bin.zip',
+ 'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-i686-bin.zip',
+ 'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-x86_64-bin.zip',
'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip',
'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.zip',
'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962',
@@ -66,10 +69,18 @@ VCREDIST_SOURCES = {
'64': 'https://aka.ms/vs/15/release/vc_redist.x64.exe',
},
}
+NINITE_REGEX = {
+ 'base': ['7-Zip', 'VLC'],
+ 'standard': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
+ 'standard7': ['Google Chrome', 'Mozilla Firefox', 'SumatraPDF'],
+ }
NINITE_SOURCES = {
'Bundles': {
- 'Legacy.exe': '.net4.7.2-7zip-chrome-firefox-vlc',
- 'Modern.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-vlc',
+ 'base.exe': '.net4.7.2-7zip-vlc',
+ 'base-standard.exe': '.net4.7.2-7zip-chrome-classicstart-firefox-sumatrapdf-vlc',
+ 'base-standard7.exe': '.net4.7.2-7zip-chrome-firefox-sumatrapdf-vlc',
+ 'standard.exe': 'chrome-classicstart-firefox-sumatrapdf',
+ 'standard7.exe': 'chrome-firefox-sumatrapdf',
},
'Audio-Video': {
'AIMP.exe': 'aimp',
@@ -216,5 +227,3 @@ WINDOWS_UPDATE_SOURCES = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2 tw=0
diff --git a/.bin/Scripts/update_kit.py b/.bin/Scripts/update_kit.py
index 133fe744..77f527aa 100644
--- a/.bin/Scripts/update_kit.py
+++ b/.bin/Scripts/update_kit.py
@@ -57,6 +57,7 @@ if __name__ == '__main__':
# Installers
print_info(' Installers')
try_and_print(message='Adobe Reader DC...', function=update_adobe_reader_dc, other_results=other_results, width=40)
+ try_and_print(message='LibreOffice...', function=update_libreoffice, other_results=other_results, width=40)
try_and_print(message='Macs Fan Control...', function=update_macs_fan_control, other_results=other_results, width=40)
try_and_print(message='MS Office...', function=update_office, other_results=other_results, width=40)
try_and_print(message='Visual C++ Runtimes...', function=update_vcredists, other_results=other_results, width=40)
From 7816602685b1c36c4ee3aae9fedef47867f87571 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:40:42 -0600
Subject: [PATCH 11/63] 4K Alignment checks
---
.bin/Scripts/functions/common.py | 7 +++++--
.bin/Scripts/functions/hw_diags.py | 26 +++++++++++++++++++++++++-
.bin/Scripts/functions/sw_diags.py | 29 +++++++++++++++++++++++++++++
3 files changed, 59 insertions(+), 3 deletions(-)
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 28834660..9db75814 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -64,10 +64,13 @@ class GenericRepair(Exception):
class MultipleInstallationsError(Exception):
pass
-class NotInstalledError(Exception):
+class NoProfilesError(Exception):
pass
-class NoProfilesError(Exception):
+class Not4KAlignedError(Exception):
+ pass
+
+class NotInstalledError(Exception):
pass
class OSInstalledLegacyError(Exception):
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index c085a5ed..36be4a30 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -307,6 +307,11 @@ class DiskObj():
attr_type=self.attr_type, **COLORS))
report.extend(sorted(self.nvme_smart_notes.keys()))
+ # 4K alignment check
+ if not self.is_4k_aligned():
+ report.append('{YELLOW}Warning{CLEAR}'.format(**COLORS))
+ report.append(' One or more partitions are not 4K aligned')
+
# Tests
for test in self.tests.values():
report.extend(test.report)
@@ -410,6 +415,26 @@ class DiskObj():
'self_test', {}).get(
k, {})
+ def is_4k_aligned(self):
+ """Check if partitions are 4K aligned, returns bool."""
+ cmd = [
+ 'sudo',
+ 'sfdisk',
+ '--json',
+ self.path,
+ ]
+ aligned = True
+
+ # Get partition details
+ json_data = get_json_from_command(cmd)
+
+ # Check partitions
+ for part in json_data.get('partitiontable', {}).get('partitions', []):
+ aligned = aligned and part.get('start', -1) % 4096 == 0
+
+ # Done
+ return aligned
+
def safety_check(self, silent=False):
"""Run safety checks and disable tests if necessary."""
test_running = False
@@ -454,7 +479,6 @@ class DiskObj():
disk_ok = OVERRIDES_FORCED or ask('Run tests on this device anyway?')
print_standard(' ')
-
# Disable tests if necessary (statuses won't be overwritten)
if test_running:
if not silent:
diff --git a/.bin/Scripts/functions/sw_diags.py b/.bin/Scripts/functions/sw_diags.py
index 1b965766..b122528a 100644
--- a/.bin/Scripts/functions/sw_diags.py
+++ b/.bin/Scripts/functions/sw_diags.py
@@ -6,6 +6,35 @@ from functions.common import *
from settings.sw_diags import *
+def check_4k_alignment(show_alert=False):
+ """Check that all partitions are 4K aligned."""
+ aligned = True
+ cmd = ['WMIC', 'partition', 'get', 'StartingOffset']
+ offsets = []
+
+ # Get offsets
+ result = run_program(cmd, encoding='utf-8', errors='ignore', check=False)
+ offsets = result.stdout.splitlines()
+
+ # Check offsets
+ for off in offsets:
+ off = off.strip()
+ if not off.isnumeric():
+ # Skip
+ continue
+
+ try:
+ aligned = aligned and int(off) % 4096 == 0
+ except ValueError:
+ # Ignore, this check is low priority
+ pass
+
+ # Show alert
+ if show_alert:
+ show_alert_box('One or more partitions are not 4K aligned')
+ raise Not4KAlignedError
+
+
def check_connection():
"""Check if the system is online and optionally abort the script."""
while True:
From 2d3ccac369e214d787a11d4b71f45157c6e98c08 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:46:35 -0600
Subject: [PATCH 12/63] Updated convert_to_bytes and human_readable_size
---
.bin/Scripts/functions/common.py | 52 ++++++++++++++++++--------------
1 file changed, 30 insertions(+), 22 deletions(-)
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 9db75814..99b7b662 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -167,18 +167,22 @@ def clear_screen():
def convert_to_bytes(size):
"""Convert human-readable size str to bytes and return an int."""
size = str(size)
- tmp = re.search(r'(\d+\.?\d*)\s+([KMGT]B)', size.upper())
+ tmp = re.search(r'(\d+\.?\d*)\s+([PTGMKB])B?', size.upper())
if tmp:
size = float(tmp.group(1))
units = tmp.group(2)
- if units == 'TB':
- size *= 1099511627776
- elif units == 'GB':
- size *= 1073741824
- elif units == 'MB':
- size *= 1048576
- elif units == 'KB':
- size *= 1024
+ if units == 'P':
+ size *= 1024 ** 5
+ if units == 'T':
+ size *= 1024 ** 4
+ elif units == 'G':
+ size *= 1024 ** 3
+ elif units == 'M':
+ size *= 1024 ** 2
+ elif units == 'K':
+ size *= 1024 ** 1
+ elif units == 'B':
+ size *= 1024 ** 0
size = int(size)
else:
return -1
@@ -297,20 +301,24 @@ def human_readable_size(size, decimals=0):
return '{size:>{width}} b'.format(size='???', width=width)
# Convert to sensible units
- if size >= 1099511627776:
- size /= 1099511627776
- units = 'Tb'
- elif size >= 1073741824:
- size /= 1073741824
- units = 'Gb'
- elif size >= 1048576:
- size /= 1048576
- units = 'Mb'
- elif size >= 1024:
- size /= 1024
- units = 'Kb'
+ if size >= 1024 ** 5:
+ size /= 1024 ** 5
+ units = 'PB'
+ elif size >= 1024 ** 4:
+ size /= 1024 ** 4
+ units = 'TB'
+ elif size >= 1024 ** 3:
+ size /= 1024 ** 3
+ units = 'GB'
+ elif size >= 1024 ** 2:
+ size /= 1024 ** 2
+ units = 'MB'
+ elif size >= 1024 ** 1:
+ size /= 1024 ** 1
+ units = 'KB'
else:
- units = ' b'
+ size /= 1024 ** 0
+ units = ' B'
# Return
return '{size:>{width}.{decimals}f} {units}'.format(
From b83f2b0c5ff6a8f2e18bf5d6cde1c52662dbdc26 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:48:03 -0600
Subject: [PATCH 13/63] Updated pause()
---
.bin/Scripts/functions/common.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 99b7b662..2828bcb4 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -433,6 +433,8 @@ def non_clobber_rename(full_path):
def pause(prompt='Press Enter to continue... '):
"""Simple pause implementation."""
+ if prompt[-1] != ' ':
+ prompt += ' '
input(prompt)
From 576cb29281ed3e900f8a84b7aa3515d101a02e10 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:57:07 -0600
Subject: [PATCH 14/63] Updated settings.setup.py
---
.bin/Scripts/settings/setup.py | 30 ++++++++++++++++++++++--------
1 file changed, 22 insertions(+), 8 deletions(-)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index 232e3a0d..dd875992 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -1,13 +1,19 @@
-# Wizard Kit: Settings - Setup
+'''Wizard Kit: Settings - Setup'''
+# pylint: disable=bad-continuation,line-too-long
+# vim: sts=2 sw=2 ts=2
import os
-import winreg
+try:
+ import winreg
+ HKU = winreg.HKEY_USERS
+ HKCR = winreg.HKEY_CLASSES_ROOT
+ HKCU = winreg.HKEY_CURRENT_USER
+ HKLM = winreg.HKEY_LOCAL_MACHINE
+except ImportError:
+ if os.name != 'posix':
+ raise
# General
-HKU = winreg.HKEY_USERS
-HKCR = winreg.HKEY_CLASSES_ROOT
-HKCU = winreg.HKEY_CURRENT_USER
-HKLM = winreg.HKEY_LOCAL_MACHINE
OTHER_RESULTS = {
'Error': {
'CalledProcessError': 'Unknown Error',
@@ -92,6 +98,15 @@ SETTINGS_EXPLORER_SYSTEM = {
},
}
SETTINGS_EXPLORER_USER = {
+ # Desktop theme
+ r'Software\Microsoft\Windows\CurrentVersion\Themes\Personalize': {
+ 'Invalid modes': ['Cur'],
+ 'DWORD Items': {
+ # <= v1809 default
+ 'AppsUseLightTheme': 1,
+ 'SystemUsesLightTheme': 0,
+ },
+ },
# Disable features
r'Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager': {
'DWORD Items': {
@@ -104,6 +119,7 @@ SETTINGS_EXPLORER_USER = {
},
# File Explorer
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
+ 'Invalid modes': ['Cur'],
'DWORD Items': {
# Change default Explorer view to "Computer"
'LaunchTo': 1,
@@ -157,5 +173,3 @@ SETTINGS_WINDOWS_UPDATES = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
From 6a821de0b56c5b4fc8b1a0c10c56ba2a6a993273 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 18:58:24 -0600
Subject: [PATCH 15/63] Added LibreOffice settings
---
.bin/Scripts/settings/setup.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index dd875992..215f56e2 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -135,6 +135,16 @@ SETTINGS_EXPLORER_USER = {
},
}
+# LibreOffice
+LIBREOFFICE_XCU_DATA = '''
+
+- Impress MS PowerPoint 2007 XML
+- Calc MS Excel 2007 XML
+- MS Word 2007 XML
+- false
+
+'''
+
# Visual C++ Runtimes
VCR_REDISTS = [
{'Name': 'Visual C++ 2010 x32...',
From 434bb977655e145253f775b7cac90a99e9ba530a Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:01:21 -0600
Subject: [PATCH 16/63] Updated sw_diags.py
---
.bin/Scripts/functions/sw_diags.py | 62 ++++++++++++++++--------------
1 file changed, 34 insertions(+), 28 deletions(-)
diff --git a/.bin/Scripts/functions/sw_diags.py b/.bin/Scripts/functions/sw_diags.py
index b122528a..1c5b943f 100644
--- a/.bin/Scripts/functions/sw_diags.py
+++ b/.bin/Scripts/functions/sw_diags.py
@@ -48,6 +48,37 @@ def check_connection():
abort()
+def check_os_support_status():
+ """Check if current OS is supported."""
+ msg = ''
+ outdated = False
+ unsupported = False
+
+ # Check OS version/notes
+ os_info = global_vars['OS'].copy()
+ if os_info['Notes'] == 'unsupported':
+ msg = 'The installed version of Windows is no longer supported'
+ unsupported = True
+ elif os_info['Notes'] == 'preview build':
+ msg = 'Preview builds are not officially supported'
+ unsupported = True
+ elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
+ msg = 'The installed version of Windows is outdated'
+ outdated = True
+ if 'Preview' not in msg:
+ msg += '\n\nPlease consider upgrading before continuing setup.'
+
+ # Show alert
+ if outdated or unsupported:
+ show_alert_box(msg)
+
+ # Raise exception if necessary
+ if outdated:
+ raise WindowsOutdatedError
+ if unsupported:
+ raise WindowsUnsupportedError
+
+
def check_secure_boot_status(show_alert=False):
"""Checks UEFI Secure Boot status via PowerShell."""
boot_mode = get_boot_mode()
@@ -110,33 +141,6 @@ def get_boot_mode():
return type_str
-def os_is_unsupported(show_alert=False):
- """Checks if the current OS is unsupported, returns bool."""
- msg = ''
- unsupported = False
-
- # Check OS version/notes
- os_info = global_vars['OS'].copy()
- if os_info['Notes'] == 'unsupported':
- msg = 'The installed version of Windows is no longer supported'
- unsupported = True
- elif os_info['Notes'] == 'preview build':
- msg = 'Preview builds are not officially supported'
- unsupported = True
- elif os_info['Version'] == '10' and os_info['Notes'] == 'outdated':
- msg = 'The installed version of Windows is outdated'
- unsupported = True
- if 'Preview' not in msg:
- msg += '\n\nPlease consider upgrading before continuing setup.'
-
- # Show alert
- if unsupported and show_alert:
- show_alert_box(msg)
-
- # Done
- return unsupported
-
-
def run_autoruns():
"""Run AutoRuns in the background with VirusTotal checks enabled."""
extract_item('Autoruns', filter='autoruns*', silent=True)
@@ -226,8 +230,10 @@ def run_rkill():
shutil.move(item.path, dest)
-def show_alert_box(message, title='Wizard Kit Warning'):
+def show_alert_box(message, title=None):
"""Show Windows alert box with message."""
+ if not title:
+ title = '{} Warning'.format(KIT_NAME_FULL)
message_box = ctypes.windll.user32.MessageBoxW
message_box(None, message, title, 0x00001030)
From 7a67e683085c36f403d7ae6adea7acd3e68624fe Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:07:37 -0600
Subject: [PATCH 17/63] Updated install_sw_bundle
---
.bin/Scripts/install_sw_bundle.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.bin/Scripts/install_sw_bundle.py b/.bin/Scripts/install_sw_bundle.py
index b536fd64..2979362e 100644
--- a/.bin/Scripts/install_sw_bundle.py
+++ b/.bin/Scripts/install_sw_bundle.py
@@ -25,7 +25,6 @@ if __name__ == '__main__':
'UnsupportedOSError': 'Unsupported OS',
}}
answer_extensions = ask('Install Extensions?')
- answer_adobe_reader = ask('Install Adobe Reader?')
answer_vcr = ask('Install Visual C++ Runtimes?')
answer_ninite = ask('Install Ninite Bundle?')
if answer_ninite and global_vars['OS']['Version'] in ['7']:
@@ -35,9 +34,6 @@ if __name__ == '__main__':
answer_mse = False
print_info('Installing Programs')
- if answer_adobe_reader:
- try_and_print(message='Adobe Reader DC...',
- function=install_adobe_reader, other_results=other_results)
if answer_vcr:
install_vcredists()
if answer_ninite:
From c4ad9055d4a0b4f44a2c66e05d5d27f3e4ac79ce Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:14:36 -0600
Subject: [PATCH 18/63] Launch Explorer windows in separate process
---
.bin/Scripts/settings/setup.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index 215f56e2..c11073ec 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -125,6 +125,13 @@ SETTINGS_EXPLORER_USER = {
'LaunchTo': 1,
},
},
+ r'SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced': {
+ # Dup path so it Will be applied to all modes
+ 'DWORD Items': {
+ # Launch Folder Windows in a Separate Process
+ 'SeparateProcess': 1,
+ },
+ },
# Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
'DWORD Items': {'PeopleBand': 0},
From 248e321438619899a65a999b7e17dfdac8b1530b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 19:16:25 -0600
Subject: [PATCH 19/63] Updated windows_builds
---
.bin/Scripts/settings/windows_builds.py | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/.bin/Scripts/settings/windows_builds.py b/.bin/Scripts/settings/windows_builds.py
index db538bf2..f7481294 100644
--- a/.bin/Scripts/settings/windows_builds.py
+++ b/.bin/Scripts/settings/windows_builds.py
@@ -1,8 +1,10 @@
-# Wizard Kit: Settings - Windows Builds
+'''Wizard Kit: Settings - Windows Builds'''
+# pylint: disable=bad-continuation,bad-whitespace
+# vim: sts=2 sw=2 ts=2
+## NOTE: Data from here: https://en.wikipedia.org/wiki/Windows_10_version_history
WINDOWS_BUILDS = {
# Build, Version, Release, Codename, Marketing Name, Notes
- '6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6000': ('Vista', 'RTM', 'Longhorn', None, 'unsupported'),
'6001': ('Vista', 'SP1', 'Longhorn', None, 'unsupported'),
'6002': ('Vista', 'SP2', 'Longhorn', None, 'unsupported'),
@@ -202,15 +204,22 @@ WINDOWS_BUILDS = {
'18356': ('10', None, '19H1', None, 'preview build'),
'18358': ('10', None, '19H1', None, 'preview build'),
'18361': ('10', None, '19H1', None, 'preview build'),
+ '18362': ('10', 'v1903', '19H1', 'May 2019 Update', None),
'18836': ('10', None, '20H1', None, 'preview build'),
'18841': ('10', None, '20H1', None, 'preview build'),
'18845': ('10', None, '20H1', None, 'preview build'),
'18850': ('10', None, '20H1', None, 'preview build'),
'18855': ('10', None, '20H1', None, 'preview build'),
+ '18860': ('10', None, '20H1', None, 'preview build'),
+ '18865': ('10', None, '20H1', None, 'preview build'),
+ '18875': ('10', None, '20H1', None, 'preview build'),
+ '18885': ('10', None, '20H1', None, 'preview build'),
+ '18890': ('10', None, '20H1', None, 'preview build'),
+ '18894': ('10', None, '20H1', None, 'preview build'),
+ '18895': ('10', None, '20H1', None, 'preview build'),
+ '18898': ('10', None, '20H1', None, 'preview build'),
}
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
From 52e4415b438a8a82c9c61c0aa83d3f0386c7cb8b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:00:11 -0600
Subject: [PATCH 20/63] Updated ddrescue sections
---
.bin/Scripts/functions/ddrescue.py | 534 ++++++++++++++++-------------
.bin/Scripts/settings/ddrescue.py | 8 +
2 files changed, 303 insertions(+), 239 deletions(-)
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index 4e6f7809..463adf4d 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -1,25 +1,25 @@
-# Wizard Kit: Functions - ddrescue-tui
+# pylint: disable=no-name-in-module,too-many-lines,wildcard-import
+# vim: sts=2 sw=2 ts=2
+'''Wizard Kit: Functions - ddrescue-tui'''
import datetime
import pathlib
-import psutil
-import pytz
import re
-import signal
import stat
import time
+from operator import itemgetter
-from collections import OrderedDict
+import pytz
from functions.data import *
from functions.hw_diags import *
from functions.json import *
from functions.tmux import *
-from operator import itemgetter
from settings.ddrescue import *
# Clases
class BaseObj():
+ # pylint: disable=missing-docstring
"""Base object used by DevObj, DirObj, and ImageObj."""
def __init__(self, path):
self.type = 'base'
@@ -44,6 +44,7 @@ class BaseObj():
class BlockPair():
+ # pylint: disable=too-many-instance-attributes
"""Object to track data and methods together for source and dest."""
def __init__(self, mode, source, dest):
self.mode = mode
@@ -60,9 +61,10 @@ class BlockPair():
if self.mode == 'clone':
# Cloning
self.dest_path = dest.path
- self.map_path = '{pwd}/Clone_{prefix}.map'.format(
- pwd=os.path.realpath(global_vars['Env']['PWD']),
- prefix=source.prefix)
+ self.map_path = '{cwd}/Clone_{prefix}.map'.format(
+ cwd=os.path.realpath(os.getcwd()),
+ prefix=source.prefix,
+ )
else:
# Imaging
self.dest_path = '{path}/{prefix}.dd'.format(
@@ -105,19 +107,19 @@ class BlockPair():
def load_map_data(self):
"""Load data from map file and set progress."""
map_data = read_map_file(self.map_path)
- self.rescued_percent = map_data['rescued']
- self.rescued = (self.rescued_percent * self.size) / 100
+ self.rescued = map_data.get('rescued', 0)
+ self.rescued_percent = (self.rescued / self.size) * 100
if map_data['full recovery']:
self.pass_done = [True, True, True]
self.rescued = self.size
self.status = ['Skipped', 'Skipped', 'Skipped']
- elif map_data['non-tried'] > 0:
+ elif map_data.get('non-tried', 0) > 0:
# Initial pass incomplete
pass
- elif map_data['non-trimmed'] > 0:
+ elif map_data.get('non-trimmed', 0) > 0:
self.pass_done = [True, False, False]
self.status = ['Skipped', 'Pending', 'Pending']
- elif map_data['non-scraped'] > 0:
+ elif map_data.get('non-scraped', 0) > 0:
self.pass_done = [True, True, False]
self.status = ['Skipped', 'Skipped', 'Pending']
else:
@@ -145,14 +147,15 @@ class BlockPair():
"""Update progress using map file."""
if os.path.exists(self.map_path):
map_data = read_map_file(self.map_path)
- self.rescued_percent = map_data.get('rescued', 0)
- self.rescued = (self.rescued_percent * self.size) / 100
+ self.rescued = map_data.get('rescued', 0)
+ self.rescued_percent = (self.rescued / self.size) * 100
self.status[pass_num] = get_formatted_status(
label='Pass {}'.format(pass_num+1),
data=(self.rescued/self.size)*100)
class DevObj(BaseObj):
+ # pylint: disable=too-many-instance-attributes
"""Block device object."""
def self_check(self):
"""Verify that self.path points to a block device."""
@@ -186,6 +189,7 @@ class DevObj(BaseObj):
self.update_filename_prefix()
def update_filename_prefix(self):
+ # pylint: disable=attribute-defined-outside-init
"""Set filename prefix based on details."""
self.prefix = '{m_size}_{model}'.format(
m_size=self.model_size,
@@ -205,6 +209,7 @@ class DevObj(BaseObj):
class DirObj(BaseObj):
+ """Directory object."""
def self_check(self):
"""Verify that self.path points to a directory."""
if not pathlib.Path(self.path).is_dir():
@@ -222,6 +227,7 @@ class DirObj(BaseObj):
class ImageObj(BaseObj):
+ """Image file object."""
def self_check(self):
"""Verify that self.path points to a file."""
if not pathlib.Path(self.path).is_file():
@@ -243,10 +249,11 @@ class ImageObj(BaseObj):
self.report = get_device_report(self.loop_dev)
self.report = self.report.replace(
self.loop_dev[self.loop_dev.rfind('/')+1:], '(Img)')
- run_program(['losetup', '--detach', self.loop_dev], check=False)
+ run_program(['sudo', 'losetup', '--detach', self.loop_dev], check=False)
class RecoveryState():
+ # pylint: disable=too-many-instance-attributes
"""Object to track BlockPair objects and overall state."""
def __init__(self, mode, source, dest):
self.mode = mode.lower()
@@ -270,6 +277,7 @@ class RecoveryState():
if mode not in ('clone', 'image'):
raise GenericError('Unsupported mode')
self.get_smart_source()
+ self.set_working_dir()
def add_block_pair(self, source, dest):
"""Run safety checks and append new BlockPair to internal list."""
@@ -314,20 +322,134 @@ class RecoveryState():
# Safety checks passed
self.block_pairs.append(BlockPair(self.mode, source, dest))
+ def build_outer_panes(self):
+ """Build top and side panes."""
+ clear_screen()
+
+ # Top
+ self.panes['Source'] = tmux_split_window(
+ behind=True, vertical=True, lines=2,
+ text='{BLUE}Source{CLEAR}'.format(**COLORS))
+
+ # Started
+ self.panes['Started'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH, target_pane=self.panes['Source'],
+ text='{BLUE}Started{CLEAR}\n{s}'.format(
+ s=time.strftime("%Y-%m-%d %H:%M %Z"),
+ **COLORS))
+
+ # Destination
+ self.panes['Destination'] = tmux_split_window(
+ percent=50, target_pane=self.panes['Source'],
+ text='{BLUE}Destination{CLEAR}'.format(**COLORS))
+
+ # Progress
+ update_sidepane(self)
+ self.panes['Progress'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH, watch=self.progress_out)
+
def current_pass_done(self):
"""Checks if pass is done for all block-pairs, returns bool."""
done = True
- for bp in self.block_pairs:
- done = done and bp.pass_done[self.current_pass]
+ for b_pair in self.block_pairs:
+ done = done and b_pair.pass_done[self.current_pass]
return done
def current_pass_min(self):
"""Gets minimum pass rescued percentage, returns float."""
min_percent = 100
- for bp in self.block_pairs:
- min_percent = min(min_percent, bp.rescued_percent)
+ for b_pair in self.block_pairs:
+ min_percent = min(min_percent, b_pair.rescued_percent)
return min_percent
+ def fix_tmux_panes(self, forced=False):
+ # pylint: disable=too-many-branches,too-many-locals
+ """Fix pane sizes if the winodw has been resized."""
+ needs_fixed = False
+
+ # Check layout
+ for pane, pane_data in TMUX_LAYOUT.items():
+ if not pane_data.get('Check'):
+ # Not concerned with the size of this pane
+ continue
+ # Get target
+ target = None
+ if pane != 'Current':
+ if pane not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[pane]
+
+ # Check pane size
+ size_x, size_y = tmux_get_pane_size(pane_id=target)
+ if pane_data.get('x', False) and pane_data['x'] != size_x:
+ needs_fixed = True
+ if pane_data.get('y', False) and pane_data['y'] != size_y:
+ needs_fixed = True
+
+ # Bail?
+ if not needs_fixed and not forced:
+ return
+
+ # Remove Destination pane (temporarily)
+ tmux_kill_pane(self.panes['Destination'])
+
+ # Update layout
+ for pane, pane_data in TMUX_LAYOUT.items():
+ # Get target
+ target = None
+ if pane != 'Current':
+ if pane not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[pane]
+
+ # Resize pane
+ tmux_resize_pane(pane_id=target, **pane_data)
+
+ # Calc Source/Destination pane sizes
+ width, height = tmux_get_pane_size()
+ width = int(width / 2) - 1
+
+ # Update Source string
+ source_str = self.source.name
+ if len(source_str) > width:
+ source_str = '{}...'.format(source_str[:width-3])
+
+ # Update Destination string
+ dest_str = self.dest.name
+ if len(dest_str) > width:
+ if self.mode == 'clone':
+ dest_str = '{}...'.format(dest_str[:width-3])
+ else:
+ dest_str = '...{}'.format(dest_str[-width+3:])
+
+ # Rebuild Source/Destination panes
+ tmux_update_pane(
+ pane_id=self.panes['Source'],
+ text='{BLUE}Source{CLEAR}\n{s}'.format(
+ s=source_str, **COLORS))
+ self.panes['Destination'] = tmux_split_window(
+ percent=50, target_pane=self.panes['Source'],
+ text='{BLUE}Destination{CLEAR}\n{s}'.format(
+ s=dest_str, **COLORS))
+
+ if 'SMART' in self.panes:
+ # Calc SMART/ddrescue/Journal panes sizes
+ ratio = [12, 22, 4]
+ width, height = tmux_get_pane_size(pane_id=self.panes['Progress'])
+ height -= 2
+ total = sum(ratio)
+ p_ratio = [int((x/total) * height) for x in ratio]
+ p_ratio[1] = height - p_ratio[0] - p_ratio[2]
+
+ # Resize SMART/Journal panes
+ tmux_resize_pane(self.panes['SMART'], y=ratio[0])
+ tmux_resize_pane(y=ratio[1])
+ tmux_resize_pane(self.panes['Journal'], y=ratio[2])
+
def get_smart_source(self):
"""Get source for SMART dispay."""
disk_path = self.source.path
@@ -339,18 +461,15 @@ class RecoveryState():
def retry_all_passes(self):
"""Mark all passes as pending for all block-pairs."""
self.finished = False
- for bp in self.block_pairs:
- bp.pass_done = [False, False, False]
- bp.status = ['Pending', 'Pending', 'Pending']
- bp.fix_status_strings()
+ for b_pair in self.block_pairs:
+ b_pair.pass_done = [False, False, False]
+ b_pair.status = ['Pending', 'Pending', 'Pending']
+ b_pair.fix_status_strings()
self.set_pass_num()
def self_checks(self):
"""Run self-checks and update state values."""
cmd = ['findmnt', '--json', '--target', os.getcwd()]
- map_allowed_fstypes = RECOMMENDED_FSTYPES.copy()
- map_allowed_fstypes.extend(['cifs', 'ext2', 'vfat'])
- map_allowed_fstypes.sort()
json_data = get_json_from_command(cmd)
# Abort if json_data is empty
@@ -361,24 +480,24 @@ class RecoveryState():
# Avoid saving map to non-persistent filesystem
fstype = json_data.get(
'filesystems', [{}])[0].get(
- 'fstype', 'unknown')
- if fstype not in map_allowed_fstypes:
+ 'fstype', 'unknown')
+ if fstype not in RECOMMENDED_MAP_FSTYPES:
print_error(
"Map isn't being saved to a recommended filesystem ({})".format(
fstype.upper()))
print_info('Recommended types are: {}'.format(
- ' / '.join(map_allowed_fstypes).upper()))
+ ' / '.join(RECOMMENDED_MAP_FSTYPES).upper()))
print_standard(' ')
if not ask('Proceed anyways? (Strongly discouraged)'):
raise GenericAbort()
# Run BlockPair self checks and get total size
self.total_size = 0
- for bp in self.block_pairs:
- bp.self_check()
- if bp.resumed:
+ for b_pair in self.block_pairs:
+ b_pair.self_check()
+ if b_pair.resumed:
self.resumed = True
- self.total_size += bp.size
+ self.total_size += b_pair.size
def set_pass_num(self):
"""Set current pass based on all block-pair's progress."""
@@ -386,8 +505,8 @@ class RecoveryState():
for pass_num in (2, 1, 0):
# Iterate backwards through passes
pass_done = True
- for bp in self.block_pairs:
- pass_done = pass_done and bp.pass_done[pass_num]
+ for b_pair in self.block_pairs:
+ pass_done = pass_done and b_pair.pass_done[pass_num]
if pass_done:
# All block-pairs reported being done
# Set to next pass, unless we're on the last pass (2)
@@ -405,6 +524,34 @@ class RecoveryState():
elif self.current_pass == 2:
self.current_pass_str = '3 "Scraping bad areas"'
+ def set_working_dir(self):
+ # pylint: disable=no-self-use
+ """Set working dir to MAP_DIR if possible.
+
+ NOTE: This is to help ensure the map file
+ is saved to non-volatile storage."""
+ map_dir = '{}/{}'.format(MAP_DIR, global_vars['Date-Time'])
+
+ # Mount backup shares
+ mount_backup_shares(read_write=True)
+
+ # Get MAP_DIR filesystem type
+ # NOTE: If the backup share fails to mount then this will
+ # likely be the type of /
+ cmd = [
+ 'findmnt',
+ '--noheadings',
+ '--target', MAP_DIR,
+ '--output', 'FSTYPE',
+ ]
+ result = run_program(cmd, check=False, encoding='utf-8', errors='ingnore')
+ map_dir_type = result.stdout.strip().lower()
+
+ # Change working dir if map_dir_type is acceptable
+ if map_dir_type in RECOMMENDED_MAP_FSTYPES:
+ os.makedirs(map_dir, exist_ok=True)
+ os.chdir(map_dir)
+
def update_etoc(self):
"""Search ddrescue output for the current EToC, returns str."""
now = datetime.datetime.now(tz=self.timezone)
@@ -414,7 +561,7 @@ class RecoveryState():
# Just set to N/A (NOTE: this overrules the refresh rate below)
self.etoc = 'N/A'
return
- elif 'In Progress' not in self.status:
+ if 'In Progress' not in self.status:
# Don't update when EToC is hidden
return
if now.second % ETOC_REFRESH_RATE != 0:
@@ -428,13 +575,14 @@ class RecoveryState():
# Capture main tmux pane
try:
text = tmux_capture_pane()
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Ignore
pass
# Search for EToC delta
matches = re.findall(r'remaining time:.*$', text, re.MULTILINE)
if matches:
+ # pylint: disable=invalid-name
r = REGEX_REMAINING_TIME.search(matches[-1])
if r.group('na'):
self.etoc = 'N/A'
@@ -451,7 +599,7 @@ class RecoveryState():
minutes=int(minutes),
seconds=int(seconds),
)
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Ignore and leave as raw string
pass
@@ -461,15 +609,16 @@ class RecoveryState():
now = datetime.datetime.now(tz=self.timezone)
_etoc = now + etoc_delta
self.etoc = _etoc.strftime('%Y-%m-%d %H:%M %Z')
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Ignore and leave as current string
pass
def update_progress(self):
+ # pylint: disable=attribute-defined-outside-init
"""Update overall progress using block_pairs."""
self.rescued = 0
- for bp in self.block_pairs:
- self.rescued += bp.rescued
+ for b_pair in self.block_pairs:
+ self.rescued += b_pair.rescued
self.rescued_percent = (self.rescued / self.total_size) * 100
self.status_percent = get_formatted_status(
label='Recovered:', data=self.rescued_percent)
@@ -478,26 +627,6 @@ class RecoveryState():
# Functions
-def build_outer_panes(state):
- """Build top and side panes."""
- state.panes['Source'] = tmux_split_window(
- behind=True, vertical=True, lines=2,
- text='{BLUE}Source{CLEAR}'.format(**COLORS))
- state.panes['Started'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH, target_pane=state.panes['Source'],
- text='{BLUE}Started{CLEAR}\n{s}'.format(
- s=time.strftime("%Y-%m-%d %H:%M %Z"),
- **COLORS))
- state.panes['Destination'] = tmux_split_window(
- percent=50, target_pane=state.panes['Source'],
- text='{BLUE}Destination{CLEAR}'.format(**COLORS))
-
- # Side pane
- update_sidepane(state)
- state.panes['Progress'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH, watch=state.progress_out)
-
-
def create_path_obj(path):
"""Create Dev, Dir, or Image obj based on path given."""
obj = None
@@ -515,101 +644,16 @@ def create_path_obj(path):
def double_confirm_clone():
"""Display warning and get 2nd confirmation, returns bool."""
print_standard('\nSAFETY CHECK')
- print_warning('All data will be DELETED from the '
- 'destination device and partition(s) listed above.')
- print_warning('This is irreversible and will lead '
- 'to {CLEAR}{RED}DATA LOSS.'.format(**COLORS))
+ print_warning(
+ 'All data will be DELETED from the '
+ 'destination device and partition(s) listed above.'
+ )
+ print_warning(
+ 'This is irreversible and will lead to {CLEAR}{RED}DATA LOSS.'.format(
+ **COLORS))
return ask('Asking again to confirm, is this correct?')
-def fix_tmux_panes(state, forced=False):
- """Fix pane sizes if the winodw has been resized."""
- needs_fixed = False
-
- # Check layout
- for k, v in TMUX_LAYOUT.items():
- if not v.get('Check'):
- # Not concerned with the size of this pane
- continue
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Check pane size
- x, y = tmux_get_pane_size(pane_id=target)
- if v.get('x', False) and v['x'] != x:
- needs_fixed = True
- if v.get('y', False) and v['y'] != y:
- needs_fixed = True
-
- # Bail?
- if not needs_fixed and not forced:
- return
-
- # Remove Destination pane (temporarily)
- tmux_kill_pane(state.panes['Destination'])
-
- # Update layout
- for k, v in TMUX_LAYOUT.items():
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Resize pane
- tmux_resize_pane(pane_id=target, **v)
-
- # Calc Source/Destination pane sizes
- width, height = tmux_get_pane_size()
- width = int(width / 2) - 1
-
- # Update Source string
- source_str = state.source.name
- if len(source_str) > width:
- source_str = '{}...'.format(source_str[:width-3])
-
- # Update Destination string
- dest_str = state.dest.name
- if len(dest_str) > width:
- if state.mode == 'clone':
- dest_str = '{}...'.format(dest_str[:width-3])
- else:
- dest_str = '...{}'.format(dest_str[-width+3:])
-
- # Rebuild Source/Destination panes
- tmux_update_pane(
- pane_id=state.panes['Source'],
- text='{BLUE}Source{CLEAR}\n{s}'.format(
- s=source_str, **COLORS))
- state.panes['Destination'] = tmux_split_window(
- percent=50, target_pane=state.panes['Source'],
- text='{BLUE}Destination{CLEAR}\n{s}'.format(
- s=dest_str, **COLORS))
-
- if 'SMART' in state.panes:
- # Calc SMART/ddrescue/Journal panes sizes
- ratio = [12, 22, 4]
- width, height = tmux_get_pane_size(pane_id=state.panes['Progress'])
- height -= 2
- total = sum(ratio)
- p_ratio = [int((x/total) * height) for x in ratio]
- p_ratio[1] = height - p_ratio[0] - p_ratio[2]
-
- # Resize SMART/Journal panes
- tmux_resize_pane(state.panes['SMART'], y=ratio[0])
- tmux_resize_pane(y=ratio[1])
- tmux_resize_pane(state.panes['Journal'], y=ratio[2])
-
-
def get_device_details(dev_path):
"""Get device details via lsblk, returns JSON dict."""
cmd = ['lsblk', '--json', '--output-all', '--paths', dev_path]
@@ -678,22 +722,22 @@ def get_dir_report(dir_path):
output.append('{BLUE}{label:<{width}}{line}{CLEAR}'.format(
label='PATH',
width=width,
- line=line.replace('\n',''),
+ line=line.replace('\n', ''),
**COLORS))
else:
output.append('{path:<{width}}{line}'.format(
path=dir_path,
width=width,
- line=line.replace('\n','')))
+ line=line.replace('\n', '')))
# Done
return '\n'.join(output)
-def get_size_in_bytes(s):
+def get_size_in_bytes(size):
"""Convert size string from lsblk string to bytes, returns int."""
- s = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', s, re.IGNORECASE)
- return convert_to_bytes(s)
+ size = re.sub(r'(\d+\.?\d*)\s*([KMGTB])B?', r'\1 \2B', size, re.IGNORECASE)
+ return convert_to_bytes(size)
def get_formatted_status(label, data):
@@ -701,13 +745,15 @@ def get_formatted_status(label, data):
data_width = SIDE_PANE_WIDTH - len(label)
try:
data_str = '{data:>{data_width}.2f} %'.format(
- data=data,
- data_width=data_width-2)
+ data=data,
+ data_width=data_width-2,
+ )
except ValueError:
# Assuming non-numeric data
data_str = '{data:>{data_width}}'.format(
- data=data,
- data_width=data_width)
+ data=data,
+ data_width=data_width,
+ )
status = '{label}{s_color}{data_str}{CLEAR}'.format(
label=label,
s_color=get_status_color(data),
@@ -716,19 +762,19 @@ def get_formatted_status(label, data):
return status
-def get_status_color(s, t_success=99, t_warn=90):
+def get_status_color(status, t_success=99, t_warn=90):
"""Get color based on status, returns str."""
color = COLORS['CLEAR']
p_recovered = -1
try:
- p_recovered = float(s)
+ p_recovered = float(status)
except ValueError:
# Status is either in lists below or will default to red
pass
- if s in ('Pending',) or str(s)[-2:] in (' b', 'Kb', 'Mb', 'Gb', 'Tb'):
+ if status == 'Pending' or str(status)[-2:] in (' b', 'Kb', 'Mb', 'Gb', 'Tb'):
color = COLORS['CLEAR']
- elif s in ('Skipped', 'Unknown'):
+ elif status in ('Skipped', 'Unknown'):
color = COLORS['YELLOW']
elif p_recovered >= t_success:
color = COLORS['GREEN']
@@ -755,6 +801,7 @@ def is_writable_filesystem(dir_obj):
def menu_ddrescue(source_path, dest_path, run_mode):
+ # pylint: disable=too-many-branches
"""ddrescue menu."""
source = None
dest = None
@@ -798,9 +845,8 @@ def menu_ddrescue(source_path, dest_path, run_mode):
raise GenericAbort()
# Main menu
- clear_screen()
- build_outer_panes(state)
- fix_tmux_panes(state, forced=True)
+ state.build_outer_panes()
+ state.fix_tmux_panes(forced=True)
menu_main(state)
# Done
@@ -809,6 +855,7 @@ def menu_ddrescue(source_path, dest_path, run_mode):
def menu_main(state):
+ # pylint: disable=too-many-branches,too-many-statements
"""Main menu is used to set ddrescue settings."""
checkmark = '*'
if 'DISPLAY' in global_vars['Env']:
@@ -819,16 +866,15 @@ def menu_main(state):
# Build menu
main_options = [
{'Base Name': 'Auto continue (if recovery % over threshold)',
- 'Enabled': True},
+ 'Enabled': True},
{'Base Name': 'Retry (mark non-rescued sectors "non-tried")',
- 'Enabled': False},
+ 'Enabled': False},
{'Base Name': 'Reverse direction', 'Enabled': False},
]
actions = [
{'Name': 'Start', 'Letter': 'S'},
- {'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format(
- **COLORS),
- 'Letter': 'C'},
+ {'Name': 'Change settings {YELLOW}(experts only){CLEAR}'.format(**COLORS),
+ 'Letter': 'C'},
{'Name': 'Quit', 'Letter': 'Q', 'CRLF': True},
]
@@ -859,13 +905,13 @@ def menu_main(state):
elif selection == 'S':
# Set settings for pass
pass_settings = []
- for k, v in state.settings.items():
- if not v['Enabled']:
+ for option, option_data in state.settings.items():
+ if not option_data['Enabled']:
continue
- if 'Value' in v:
- pass_settings.append('{}={}'.format(k, v['Value']))
+ if 'Value' in option_data:
+ pass_settings.append('{}={}'.format(option, option_data['Value']))
else:
- pass_settings.append(k)
+ pass_settings.append(option)
for opt in main_options:
if 'Auto' in opt['Base Name']:
auto_run = opt['Enabled']
@@ -888,7 +934,7 @@ def menu_main(state):
state.current_pass_min() < AUTO_PASS_1_THRESHOLD):
auto_run = False
elif (state.current_pass == 1 and
- state.current_pass_min() < AUTO_PASS_2_THRESHOLD):
+ state.current_pass_min() < AUTO_PASS_2_THRESHOLD):
auto_run = False
else:
auto_run = False
@@ -917,13 +963,15 @@ def menu_settings(state):
# Build menu
settings = []
- for k, v in sorted(state.settings.items()):
- if not v.get('Hidden', False):
- settings.append({'Base Name': k, 'Flag': k})
+ for option, option_data in sorted(state.settings.items()):
+ if not option_data.get('Hidden', False):
+ settings.append({'Base Name': option, 'Flag': option})
actions = [{'Name': 'Main Menu', 'Letter': 'M'}]
# Show menu
while True:
+ # pylint: disable=invalid-name
+ # TODO: Clean up and/or replace with new menu-select function
for s in settings:
s['Name'] = '{}{}{}'.format(
s['Base Name'],
@@ -960,25 +1008,27 @@ def menu_settings(state):
def read_map_file(map_path):
"""Read map file with ddrescuelog and return data as dict."""
- map_data = {'full recovery': False}
+ cmd = [
+ 'ddrescuelog',
+ '--binary-prefixes',
+ '--show-status',
+ map_path,
+ ]
+ map_data = {'full recovery': False, 'pass completed': False}
try:
- result = run_program(['ddrescuelog', '-t', map_path])
+ result = run_program(cmd, encoding='utf-8', errors='ignore')
except CalledProcessError:
# (Grossly) assuming map_data hasn't been saved yet, return empty dict
return map_data
# Parse output
- for line in result.stdout.decode().splitlines():
- m = re.match(
- r'^\s*(?P\S+):.*\(\s*(?P\d+\.?\d*)%.*', line.strip())
- if m:
- try:
- map_data[m.group('key')] = float(m.group('value'))
- except ValueError:
- raise GenericError('Failed to read map data')
- m = re.match(r'.*current status:\s+(?P.*)', line.strip())
- if m:
- map_data['pass completed'] = bool(m.group('status') == 'finished')
+ for line in result.stdout.splitlines():
+ line = line.strip()
+ _r = REGEX_DDRESCUE_LOG.search(line)
+ if _r:
+ map_data[_r.group('key')] = convert_to_bytes('{size} {unit}B'.format(
+ **_r.groupdict()))
+ map_data['pass completed'] = 'current status: finished' in line
# Check if 100% done
try:
@@ -992,6 +1042,7 @@ def read_map_file(map_path):
def run_ddrescue(state, pass_settings):
+ # pylint: disable=too-many-branches,too-many-statements
"""Run ddrescue pass."""
return_code = -1
aborted = False
@@ -1006,8 +1057,8 @@ def run_ddrescue(state, pass_settings):
# Create SMART monitor pane
state.smart_out = '{}/smart_{}.out'.format(
global_vars['TmpDir'], state.smart_source.name)
- with open(state.smart_out, 'w') as f:
- f.write('Initializing...')
+ with open(state.smart_out, 'w') as _f:
+ _f.write('Initializing...')
state.panes['SMART'] = tmux_split_window(
behind=True, lines=12, vertical=True, watch=state.smart_out)
@@ -1017,19 +1068,19 @@ def run_ddrescue(state, pass_settings):
command=['sudo', 'journalctl', '-f'])
# Fix layout
- fix_tmux_panes(state, forced=True)
+ state.fix_tmux_panes(forced=True)
# Run pass for each block-pair
- for bp in state.block_pairs:
- if bp.pass_done[state.current_pass]:
+ for b_pair in state.block_pairs:
+ if b_pair.pass_done[state.current_pass]:
# Skip to next block-pair
continue
update_sidepane(state)
# Set ddrescue cmd
cmd = [
- 'ddrescue', *pass_settings,
- bp.source_path, bp.dest_path, bp.map_path]
+ 'sudo', 'ddrescue', *pass_settings,
+ b_pair.source_path, b_pair.dest_path, b_pair.map_path]
if state.mode == 'clone':
cmd.append('--force')
if state.current_pass == 0:
@@ -1044,36 +1095,36 @@ def run_ddrescue(state, pass_settings):
# Start ddrescue
try:
clear_screen()
- print_info('Current dev: {}'.format(bp.source_path))
+ print_info('Current dev: {}'.format(b_pair.source_path))
ddrescue_proc = popen_program(cmd)
i = 0
while True:
# Update SMART display (every 30 seconds)
if i % 30 == 0:
state.smart_source.get_smart_details()
- with open(state.smart_out, 'w') as f:
+ with open(state.smart_out, 'w') as _f:
report = state.smart_source.generate_attribute_report(
- timestamp=True)
+ timestamp=True)
for line in report:
- f.write('{}\n'.format(line))
+ _f.write('{}\n'.format(line))
i += 1
# Update progress
- bp.update_progress(state.current_pass)
+ b_pair.update_progress(state.current_pass)
update_sidepane(state)
# Fix panes
- fix_tmux_panes(state)
+ state.fix_tmux_panes()
# Check if ddrescue has finished
try:
ddrescue_proc.wait(timeout=1)
sleep(2)
- bp.update_progress(state.current_pass)
+ b_pair.update_progress(state.current_pass)
update_sidepane(state)
break
except subprocess.TimeoutExpired:
- # Catch to update smart/bp/sidepane
+ # Catch to update smart/b_pair/sidepane
pass
except KeyboardInterrupt:
@@ -1082,7 +1133,7 @@ def run_ddrescue(state, pass_settings):
ddrescue_proc.wait(timeout=10)
# Update progress/sidepane again
- bp.update_progress(state.current_pass)
+ b_pair.update_progress(state.current_pass)
update_sidepane(state)
# Was ddrescue aborted?
@@ -1104,7 +1155,7 @@ def run_ddrescue(state, pass_settings):
break
else:
# Mark pass finished
- bp.finish_pass(state.current_pass)
+ b_pair.finish_pass(state.current_pass)
update_sidepane(state)
# Done
@@ -1120,6 +1171,8 @@ def run_ddrescue(state, pass_settings):
def select_parts(source_device):
+ # pylint: disable=too-many-branches
+ # TODO: Clean up and/or replace with new menu-select function
"""Select partition(s) or whole device, returns list of DevObj()s."""
selected_parts = []
children = source_device.details.get('children', [])
@@ -1181,24 +1234,26 @@ def select_parts(source_device):
raise GenericAbort()
# Build list of selected parts
- for d in dev_options:
- if d['Selected']:
- d['Dev'].model = source_device.model
- d['Dev'].model_size = source_device.model_size
- d['Dev'].update_filename_prefix()
- selected_parts.append(d['Dev'])
+ for _d in dev_options:
+ if _d['Selected']:
+ _d['Dev'].model = source_device.model
+ _d['Dev'].model_size = source_device.model_size
+ _d['Dev'].update_filename_prefix()
+ selected_parts.append(_d['Dev'])
return selected_parts
def select_path(skip_device=None):
+ # pylint: disable=too-many-branches,too-many-locals
+ # TODO: Clean up and/or replace with new menu-select function
"""Optionally mount local dev and select path, returns DirObj."""
- wd = os.path.realpath(global_vars['Env']['PWD'])
+ work_dir = os.path.realpath(global_vars['Env']['PWD'])
selected_path = None
# Build menu
path_options = [
- {'Name': 'Current directory: {}'.format(wd), 'Path': wd},
+ {'Name': 'Current directory: {}'.format(work_dir), 'Path': work_dir},
{'Name': 'Local device', 'Path': None},
{'Name': 'Enter manually', 'Path': None}]
actions = [{'Name': 'Quit', 'Letter': 'Q'}]
@@ -1213,9 +1268,9 @@ def select_path(skip_device=None):
raise GenericAbort()
elif selection.isnumeric():
index = int(selection) - 1
- if path_options[index]['Path'] == wd:
+ if path_options[index]['Path'] == work_dir:
# Current directory
- selected_path = DirObj(wd)
+ selected_path = DirObj(work_dir)
elif path_options[index]['Name'] == 'Local device':
# Local device
@@ -1231,15 +1286,15 @@ def select_path(skip_device=None):
# Select volume
vol_options = []
- for k, v in sorted(report.items()):
- disabled = v['show_data']['data'] == 'Failed to mount'
+ for _k, _v in sorted(report.items()):
+ disabled = _v['show_data']['data'] == 'Failed to mount'
if disabled:
- name = '{name} (Failed to mount)'.format(**v)
+ name = '{name} (Failed to mount)'.format(**_v)
else:
- name = '{name} (mounted on "{mount_point}")'.format(**v)
+ name = '{name} (mounted on "{mount_point}")'.format(**_v)
vol_options.append({
'Name': name,
- 'Path': v['mount_point'],
+ 'Path': _v['mount_point'],
'Disabled': disabled})
selection = menu_select(
title='Please select a volume',
@@ -1314,15 +1369,17 @@ def select_device(description='device', skip_device=None):
action_entries=actions,
disabled_label='ALREADY SELECTED')
+ if selection == 'Q':
+ raise GenericAbort()
+
if selection.isnumeric():
return dev_options[int(selection)-1]['Dev']
- elif selection == 'Q':
- raise GenericAbort()
def setup_loopback_device(source_path):
"""Setup loopback device for source_path, returns dev_path as str."""
cmd = (
+ 'sudo',
'losetup',
'--find',
'--partscan',
@@ -1356,6 +1413,7 @@ def show_selection_details(state):
def show_usage(script_name):
+ """Show usage."""
print_info('Usage:')
print_standard(USAGE.format(script_name=script_name))
pause()
@@ -1379,14 +1437,14 @@ def update_sidepane(state):
output.append('─────────────────────')
# Source(s) progress
- for bp in state.block_pairs:
+ for b_pair in state.block_pairs:
if state.source.is_image():
output.append('{BLUE}Image File{CLEAR}'.format(**COLORS))
else:
output.append('{BLUE}{source}{CLEAR}'.format(
- source=bp.source_path,
+ source=b_pair.source_path,
**COLORS))
- output.extend(bp.status)
+ output.extend(b_pair.status)
output.append(' ')
# EToC
@@ -1405,11 +1463,9 @@ def update_sidepane(state):
# Add line-endings
output = ['{}\n'.format(line) for line in output]
- with open(state.progress_out, 'w') as f:
- f.writelines(output)
+ with open(state.progress_out, 'w') as _f:
+ _f.writelines(output)
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/settings/ddrescue.py b/.bin/Scripts/settings/ddrescue.py
index 675019ca..ffe6e215 100644
--- a/.bin/Scripts/settings/ddrescue.py
+++ b/.bin/Scripts/settings/ddrescue.py
@@ -5,7 +5,9 @@ import re
from collections import OrderedDict
# General
+MAP_DIR = '/Backups/ddrescue-tui'
RECOMMENDED_FSTYPES = ['ext3', 'ext4', 'xfs']
+RECOMMENDED_MAP_FSTYPES = ['cifs', 'ext2', 'ext3', 'ext4', 'vfat', 'xfs']
USAGE = """ {script_name} clone [source [destination]]
{script_name} image [source [destination]]
(e.g. {script_name} clone /dev/sda /dev/sdb)
@@ -36,6 +38,12 @@ DDRESCUE_SETTINGS = {
'-vvvv': {'Enabled': True, 'Hidden': True, },
}
ETOC_REFRESH_RATE = 30 # in seconds
+REGEX_DDRESCUE_LOG = re.compile(
+ r'^\s*(?P\S+):\s+'
+ r'(?P\d+)\s+'
+ r'(?P[PTGMKB])i?B?',
+ re.IGNORECASE,
+ )
REGEX_REMAINING_TIME = re.compile(
r'remaining time:'
r'\s*((?P\d+)d)?'
From 28bedc0873ac973817ed40ca9c912e0a0197f015 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:01:26 -0600
Subject: [PATCH 21/63] Run ddrescue-tui as current user
---
.linux_items/include/airootfs/etc/skel/.aliases | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.linux_items/include/airootfs/etc/skel/.aliases b/.linux_items/include/airootfs/etc/skel/.aliases
index d6486258..b0068be3 100644
--- a/.linux_items/include/airootfs/etc/skel/.aliases
+++ b/.linux_items/include/airootfs/etc/skel/.aliases
@@ -34,5 +34,5 @@ alias srsz='sudo rsync -avhzPS --stats --exclude-from="$HOME/.rsync_exclusions"'
alias testdisk='sudo testdisk'
alias umount='sudo umount'
alias unmount='sudo umount'
-alias wkclone='sudo ddrescue-tui clone'
-alias wkimage='sudo ddrescue-tui image'
+alias wkclone='ddrescue-tui clone'
+alias wkimage='ddrescue-tui image'
From 606efac3fe96b05de7993506d2347605aa7c1a2b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:09:56 -0600
Subject: [PATCH 22/63] Updated mounting sections
---
.bin/Scripts/functions/data.py | 51 ++++++++++++++++++++++++++------
.bin/Scripts/mount-backup-shares | 3 --
2 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/.bin/Scripts/functions/data.py b/.bin/Scripts/functions/data.py
index fa0bb20c..c359ab6c 100644
--- a/.bin/Scripts/functions/data.py
+++ b/.bin/Scripts/functions/data.py
@@ -151,12 +151,16 @@ def is_valid_wim_file(item):
def get_mounted_volumes():
"""Get mounted volumes, returns dict."""
cmd = [
- 'findmnt', '-J', '-b', '-i',
- '-t', (
+ 'findmnt',
+ '--list',
+ '--json',
+ '--bytes',
+ '--invert',
+ '--types', (
'autofs,binfmt_misc,bpf,cgroup,cgroup2,configfs,debugfs,devpts,'
'devtmpfs,hugetlbfs,mqueue,proc,pstore,securityfs,sysfs,tmpfs'
),
- '-o', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
+ '--output', 'SOURCE,TARGET,FSTYPE,LABEL,SIZE,AVAIL,USED']
json_data = get_json_from_command(cmd)
mounted_volumes = []
for item in json_data.get('filesystems', []):
@@ -195,6 +199,8 @@ def mount_volumes(
volumes.update({child['name']: child})
for grandchild in child.get('children', []):
volumes.update({grandchild['name']: grandchild})
+ for great_grandchild in grandchild.get('children', []):
+ volumes.update({great_grandchild['name']: great_grandchild})
# Get list of mounted volumes
mounted_volumes = get_mounted_volumes()
@@ -237,6 +243,7 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None
else:
+ fstype = vol_data.get('fstype', 'UNKNOWN FS')
size_used = human_readable_size(
mounted_volumes[vol_path]['used'],
decimals=1,
@@ -250,8 +257,9 @@ def mount_volumes(
vol_data['mount_point'] = mounted_volumes[vol_path]['target']
vol_data['show_data']['data'] = 'Mounted on {}'.format(
mounted_volumes[vol_path]['target'])
- vol_data['show_data']['data'] = '{:40} ({} used, {} free)'.format(
+ vol_data['show_data']['data'] = '{:40} ({}, {} used, {} free)'.format(
vol_data['show_data']['data'],
+ fstype,
size_used,
size_avail)
@@ -285,6 +293,14 @@ def mount_backup_shares(read_write=False):
def mount_network_share(server, read_write=False):
"""Mount a network share defined by server."""
+ uid = '1000'
+
+ # Get UID
+ cmd = ['id', '--user', 'tech']
+ result = run_program(cmd, check=False, encoding='utf-8', errors='ignore')
+ if result.stdout.strip().isnumeric():
+ uid = result.stdout.strip()
+
if read_write:
username = server['RW-User']
password = server['RW-Pass']
@@ -300,18 +316,35 @@ def mount_network_share(server, read_write=False):
error = r'Failed to mount \\{Name}\{Share} ({IP})'.format(**server)
success = 'Mounted {Name}'.format(**server)
elif psutil.LINUX:
+ # Make mountpoint
cmd = [
'sudo', 'mkdir', '-p',
'/Backups/{Name}'.format(**server)]
run_program(cmd)
+
+ # Set mount options
+ cmd_options = [
+ # Assuming GID matches UID
+ 'gid={}'.format(uid),
+ 'uid={}'.format(uid),
+ ]
+ cmd_options.append('rw' if read_write else 'ro')
+ cmd_options.append('username={}'.format(username))
+ if password:
+ cmd_options.append('password={}'.format(password))
+ else:
+ # Skip password check
+ cmd_options.append('guest')
+
+ # Set mount command
cmd = [
'sudo', 'mount',
- '//{IP}/{Share}'.format(**server),
+ '//{IP}/{Share}'.format(**server).replace('\\', '/'),
'/Backups/{Name}'.format(**server),
- '-o', '{}username={},password={}'.format(
- '' if read_write else 'ro,',
- username,
- password)]
+ '-o', ','.join(cmd_options),
+ ]
+
+ # Set result messages
warning = 'Failed to mount /Backups/{Name}, {IP} unreachable.'.format(
**server)
error = 'Failed to mount /Backups/{Name}'.format(**server)
diff --git a/.bin/Scripts/mount-backup-shares b/.bin/Scripts/mount-backup-shares
index 6a1e88d7..0d8b7fd3 100755
--- a/.bin/Scripts/mount-backup-shares
+++ b/.bin/Scripts/mount-backup-shares
@@ -16,9 +16,6 @@ if __name__ == '__main__':
# Prep
clear_screen()
- # Connect
- connect_to_network()
-
# Mount
if is_connected():
mount_backup_shares(read_write=True)
From ec5591453e12543d4921ebb25e8d5fbddfe01b37 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:16:09 -0600
Subject: [PATCH 23/63] Updated data.py
---
.bin/Scripts/functions/data.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.bin/Scripts/functions/data.py b/.bin/Scripts/functions/data.py
index c359ab6c..bf091b37 100644
--- a/.bin/Scripts/functions/data.py
+++ b/.bin/Scripts/functions/data.py
@@ -218,7 +218,7 @@ def mount_volumes(
report[vol_path] = vol_data
elif 'children' in vol_data:
# Skip LVM/RAID partitions (the real volume is mounted separately)
- vol_data['show_data']['data'] = vol_data.get('fstype', 'UNKNOWN')
+ vol_data['show_data']['data'] = vol_data.get('fstype', 'Unknown')
if vol_data.get('label', None):
vol_data['show_data']['data'] += ' "{}"'.format(vol_data['label'])
vol_data['show_data']['info'] = True
@@ -243,7 +243,7 @@ def mount_volumes(
if vol_data['show_data']['data'] == 'Failed to mount':
vol_data['mount_point'] = None
else:
- fstype = vol_data.get('fstype', 'UNKNOWN FS')
+ fstype = vol_data.get('fstype', 'Unknown FS')
size_used = human_readable_size(
mounted_volumes[vol_path]['used'],
decimals=1,
From 70823d2cd80c123932fcce862fc6c3e3d3c50be6 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 4 Jun 2019 20:53:34 -0600
Subject: [PATCH 24/63] Updated HW-Diags and sensor sections
---
.bin/Scripts/debug/hw_diags.py | 9 +-
.bin/Scripts/functions/hw_diags.py | 294 +++++++++++++++++------------
.bin/Scripts/functions/sensors.py | 89 ++++-----
.bin/Scripts/hw-diags-menu | 2 +-
4 files changed, 226 insertions(+), 168 deletions(-)
diff --git a/.bin/Scripts/debug/hw_diags.py b/.bin/Scripts/debug/hw_diags.py
index 87a35990..44517fb4 100644
--- a/.bin/Scripts/debug/hw_diags.py
+++ b/.bin/Scripts/debug/hw_diags.py
@@ -149,11 +149,14 @@ def save_debug_reports(state, global_vars):
f.write('{}\n'.format(line))
-def upload_logdir(global_vars):
+def upload_logdir(global_vars, reason='Crash'):
"""Upload compressed LogDir to CRASH_SERVER."""
source = global_vars['LogDir']
source = source[source.rfind('/')+1:]
- dest = '{}.txz'.format(source)
+ dest = 'HW-Diags_{reason}_{Date-Time}.txz'.format(
+ reason=reason,
+ **global_vars,
+ )
data = None
# Compress LogDir
@@ -166,7 +169,7 @@ def upload_logdir(global_vars):
data = f.read()
# Upload data
- url = '{}/Crash_{}.txz'.format(CRASH_SERVER['Url'], source)
+ url = '{}/{}'.format(CRASH_SERVER['Url'], dest)
r = requests.put(
url,
data=data,
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index 36be4a30..db5d01ca 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -36,6 +36,7 @@ class CpuObj():
self.tests = OrderedDict()
self.get_details()
self.name = self.lscpu.get('Model name', 'Unknown CPU')
+ self.description = self.name
def get_details(self):
"""Get CPU details from lscpu."""
@@ -57,6 +58,13 @@ class CpuObj():
report.append('{BLUE}Device{CLEAR}'.format(**COLORS))
report.append(' {}'.format(self.name))
+ # Include RAM details
+ ram_details = get_ram_details()
+ ram_total = human_readable_size(ram_details.pop('Total', 0)).strip()
+ ram_dimms = ['{}x {}'.format(v, k) for k, v in sorted(ram_details.items())]
+ report.append('{BLUE}RAM{CLEAR}'.format(**COLORS))
+ report.append(' {} ({})'.format(ram_total, ', '.join(ram_dimms)))
+
# Tests
for test in self.tests.values():
report.extend(test.report)
@@ -220,11 +228,12 @@ class DiskObj():
# Done
return test_running
- def disable_test(self, name, status):
+ def disable_test(self, name, status, test_failed=False):
"""Disable test by name and update status."""
if name in self.tests:
self.tests[name].update_status(status)
self.tests[name].disabled = True
+ self.tests[name].failed = test_failed
def generate_attribute_report(
self, description=False, timestamp=False):
@@ -487,7 +496,7 @@ class DiskObj():
for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied')
elif not disk_ok:
- self.disable_test('NVMe / SMART', 'NS')
+ self.disable_test('NVMe / SMART', 'NS', test_failed=True)
for t in ['badblocks', 'I/O Benchmark']:
self.disable_test(t, 'Denied')
@@ -495,6 +504,7 @@ class DiskObj():
class State():
"""Object to track device objects and overall state."""
def __init__(self):
+ self.args = None
self.cpu = None
self.disks = []
self.panes = {}
@@ -522,6 +532,83 @@ class State():
},
})
+ def build_outer_panes(self):
+ """Build top and side panes."""
+ clear_screen()
+
+ # Top
+ self.panes['Top'] = tmux_split_window(
+ behind=True, lines=2, vertical=True,
+ text=TOP_PANE_TEXT)
+
+ # Started
+ self.panes['Started'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH, target_pane=self.panes['Top'],
+ text='{BLUE}Started{CLEAR}\n{s}'.format(
+ s=time.strftime("%Y-%m-%d %H:%M %Z"),
+ **COLORS))
+
+ # Progress
+ self.panes['Progress'] = tmux_split_window(
+ lines=SIDE_PANE_WIDTH,
+ watch=self.progress_out)
+
+ def fix_tmux_panes(self):
+ """Fix pane sizes if the window has been resized."""
+ needs_fixed = False
+
+ # Bail?
+ if not self.panes:
+ return
+
+ # Check layout
+ for k, v in self.tmux_layout.items():
+ if not v.get('Check'):
+ # Not concerned with the size of this pane
+ continue
+ # Get target
+ target = None
+ if k != 'Current':
+ if k not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[k]
+
+ # Check pane size
+ x, y = tmux_get_pane_size(pane_id=target)
+ if v.get('x', False) and v['x'] != x:
+ needs_fixed = True
+ if v.get('y', False) and v['y'] != y:
+ needs_fixed = True
+
+ # Bail?
+ if not needs_fixed:
+ return
+
+ # Update layout
+ for k, v in self.tmux_layout.items():
+ # Get target
+ target = None
+ if k != 'Current':
+ if k not in self.panes:
+ # Skip missing panes
+ continue
+ else:
+ target = self.panes[k]
+
+ # Resize pane
+ tmux_resize_pane(pane_id=target, **v)
+
+ def fix_tmux_panes_loop(self):
+ while True:
+ try:
+ self.fix_tmux_panes()
+ sleep(1)
+ except RuntimeError:
+ # Assuming layout definitions changes mid-run, ignoring
+ pass
+
def init(self):
"""Remove test objects, set log, and add devices."""
self.disks = []
@@ -529,14 +616,18 @@ class State():
v['Objects'] = []
# Update LogDir
- if not self.quick_mode:
+ if self.quick_mode:
+ global_vars['LogDir'] = '{}/Logs/{}'.format(
+ global_vars['Env']['HOME'],
+ time.strftime('%Y-%m-%d_%H%M_%z'))
+ else:
global_vars['LogDir'] = '{}/Logs/{}_{}'.format(
global_vars['Env']['HOME'],
get_ticket_number(),
time.strftime('%Y-%m-%d_%H%M_%z'))
- os.makedirs(global_vars['LogDir'], exist_ok=True)
- global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
- global_vars['LogDir'])
+ os.makedirs(global_vars['LogDir'], exist_ok=True)
+ global_vars['LogFile'] = '{}/Hardware Diagnostics.log'.format(
+ global_vars['LogDir'])
self.progress_out = '{}/progress.out'.format(global_vars['LogDir'])
# Add CPU
@@ -565,7 +656,13 @@ class State():
# Start tmux thread
self.tmux_layout = TMUX_LAYOUT.copy()
- start_thread(fix_tmux_panes_loop, args=[self])
+ start_thread(self.fix_tmux_panes_loop)
+
+ def set_top_pane_text(self, text):
+ """Set top pane text using TOP_PANE_TEXT and provided text."""
+ tmux_update_pane(
+ self.panes['Top'],
+ text='{}\n{}'.format(TOP_PANE_TEXT, text))
class TestObj():
@@ -600,28 +697,6 @@ class TestObj():
# Functions
-def build_outer_panes(state):
- """Build top and side panes."""
- clear_screen()
-
- # Top
- state.panes['Top'] = tmux_split_window(
- behind=True, lines=2, vertical=True,
- text=TOP_PANE_TEXT)
-
- # Started
- state.panes['Started'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH, target_pane=state.panes['Top'],
- text='{BLUE}Started{CLEAR}\n{s}'.format(
- s=time.strftime("%Y-%m-%d %H:%M %Z"),
- **COLORS))
-
- # Progress
- state.panes['Progress'] = tmux_split_window(
- lines=SIDE_PANE_WIDTH,
- watch=state.progress_out)
-
-
def build_status_string(label, status, info_label=False):
"""Build status string with appropriate colors."""
status_color = COLORS['CLEAR']
@@ -638,64 +713,6 @@ def build_status_string(label, status, info_label=False):
**COLORS)
-def fix_tmux_panes_loop(state):
- while True:
- try:
- fix_tmux_panes(state)
- sleep(1)
- except RuntimeError:
- # Assuming layout definitions changes mid-run, ignoring
- pass
-
-
-def fix_tmux_panes(state):
- """Fix pane sizes if the window has been resized."""
- needs_fixed = False
-
- # Bail?
- if not state.panes:
- return
-
- # Check layout
- for k, v in state.tmux_layout.items():
- if not v.get('Check'):
- # Not concerned with the size of this pane
- continue
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Check pane size
- x, y = tmux_get_pane_size(pane_id=target)
- if v.get('x', False) and v['x'] != x:
- needs_fixed = True
- if v.get('y', False) and v['y'] != y:
- needs_fixed = True
-
- # Bail?
- if not needs_fixed:
- return
-
- # Update layout
- for k, v in state.tmux_layout.items():
- # Get target
- target = None
- if k != 'Current':
- if k not in state.panes:
- # Skip missing panes
- continue
- else:
- target = state.panes[k]
-
- # Resize pane
- tmux_resize_pane(pane_id=target, **v)
-
-
def generate_horizontal_graph(rates, oneline=False):
"""Generate horizontal graph from rates, returns list."""
graph = ['', '', '', '']
@@ -755,6 +772,44 @@ def get_graph_step(rate, scale=16):
return step
+def get_ram_details():
+ """Get RAM details via dmidecode, returns dict."""
+ cmd = ['sudo', 'dmidecode', '--type', 'memory']
+ manufacturer = 'UNKNOWN'
+ ram_details = {'Total': 0}
+ size = 0
+
+ # Get DMI data
+ result = run_program(cmd, encoding='utf-8', errors='ignore')
+ dmi_data = result.stdout.splitlines()
+
+ # Parse data
+ for line in dmi_data:
+ line = line.strip()
+ if line == 'Memory Device':
+ # Reset vars
+ manufacturer = 'UNKNOWN'
+ size = 0
+ elif line.startswith('Size:'):
+ size = convert_to_bytes(line.replace('Size: ', ''))
+ elif line.startswith('Manufacturer:'):
+ manufacturer = line.replace('Manufacturer: ', '')
+ if size > 0:
+ # Add RAM to list if slot populated
+ ram_str = '{} {}'.format(
+ human_readable_size(size).strip(),
+ manufacturer,
+ )
+ ram_details['Total'] += size
+ if ram_str in ram_details:
+ ram_details[ram_str] += 1
+ else:
+ ram_details[ram_str] = 1
+
+ # Done
+ return ram_details
+
+
def get_read_rate(s):
"""Get read rate in bytes/s from dd progress output."""
real_rate = None
@@ -767,6 +822,7 @@ def get_read_rate(s):
def menu_diags(state, args):
"""Main menu to select and run HW tests."""
args = [a.lower() for a in args]
+ state.args = args
checkmark = '*'
if 'DISPLAY' in global_vars['Env']:
checkmark = '✓'
@@ -908,10 +964,7 @@ def run_badblocks_test(state, test):
update_progress_pane(state)
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(
- TOP_PANE_TEXT, dev.description))
+ state.set_top_pane_text(dev.description)
# Create monitor pane
test.badblocks_out = '{}/badblocks_{}.out'.format(
@@ -994,10 +1047,11 @@ def run_hw_tests(state):
"""Run enabled hardware tests."""
print_standard('Scanning devices...')
state.init()
+ tests_enabled = False
# Build Panes
update_progress_pane(state)
- build_outer_panes(state)
+ state.build_outer_panes()
# Show selected tests and create TestObj()s
print_info('Selected Tests:')
@@ -1009,6 +1063,8 @@ def run_hw_tests(state):
COLORS['CLEAR'],
QUICK_LABEL if state.quick_mode and 'NVMe' in k else ''))
if v['Enabled']:
+ tests_enabled = True
+
# Create TestObj and track under both CpuObj/DiskObj and State
if k in TESTS_CPU:
test_obj = TestObj(
@@ -1022,6 +1078,11 @@ def run_hw_tests(state):
v['Objects'].append(test_obj)
print_standard('')
+ # Bail if no tests selected
+ if not tests_enabled:
+ tmux_kill_pane(*state.panes.values())
+ return
+
# Run disk safety checks (if necessary)
_disk_tests_enabled = False
for k in TESTS_DISK:
@@ -1064,7 +1125,7 @@ def run_hw_tests(state):
# Rebuild panes
update_progress_pane(state)
- build_outer_panes(state)
+ state.build_outer_panes()
# Mark unfinished tests as aborted
for k, v in state.tests.items():
@@ -1076,8 +1137,22 @@ def run_hw_tests(state):
# Update side pane
update_progress_pane(state)
- # Done
+ # Show results
show_results(state)
+
+ # Upload for review
+ if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
+ try_and_print(
+ message='Saving debug reports...',
+ function=save_debug_reports,
+ state=state, global_vars=global_vars)
+ try_and_print(
+ message='Uploading Data...',
+ function=upload_logdir,
+ global_vars=global_vars,
+ reason='Review')
+
+ # Done
sleep(1)
if state.quick_mode:
pause('Press Enter to exit... ')
@@ -1104,10 +1179,7 @@ def run_io_benchmark(state, test):
update_progress_pane(state)
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(
- TOP_PANE_TEXT, dev.description))
+ state.set_top_pane_text(dev.description)
state.tmux_layout['Current'] = {'y': 15, 'Check': True}
# Create monitor pane
@@ -1266,9 +1338,7 @@ def run_mprime_test(state, test):
test.thermal_abort = False
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(TOP_PANE_TEXT, dev.name))
+ state.set_top_pane_text(dev.name)
# Start live sensor monitor
test.sensors_out = '{}/sensors.out'.format(global_vars['TmpDir'])
@@ -1431,7 +1501,7 @@ def run_mprime_test(state, test):
# Add temps to report
test.report.append('{BLUE}Temps{CLEAR}'.format(**COLORS))
for line in generate_sensor_report(
- test.sensor_data, 'Idle', 'Max', 'Cooldown', core_only=True):
+ test.sensor_data, 'Idle', 'Max', 'Cooldown', cpu_only=True):
test.report.append(' {}'.format(line))
# Add abort message(s)
@@ -1481,10 +1551,7 @@ def run_nvme_smart_tests(state, test, update_mode=False):
update_progress_pane(state)
# Update tmux layout
- tmux_update_pane(
- state.panes['Top'],
- text='{}\n{}'.format(
- TOP_PANE_TEXT, dev.description))
+ state.set_top_pane_text(dev.description)
# SMART short self-test
if dev.smart_attributes and not (state.quick_mode or update_mode):
@@ -1629,9 +1696,7 @@ def show_report(report, log_report=False):
def show_results(state):
"""Show results for all tests."""
clear_screen()
- tmux_update_pane(
- state.panes['Top'],
- text='{}\nResults'.format(TOP_PANE_TEXT))
+ state.set_top_pane_text('Results')
# CPU tests
_enabled = False
@@ -1661,17 +1726,6 @@ def show_results(state):
# Update progress
update_progress_pane(state)
- # Ask for review
- if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
- try_and_print(
- message='Saving debug reports...',
- function=save_debug_reports,
- state=state, global_vars=global_vars)
- try_and_print(
- message='Uploading Data...',
- function=upload_logdir,
- global_vars=global_vars)
-
def update_main_options(state, selection, main_options):
"""Update menu and state based on selection."""
diff --git a/.bin/Scripts/functions/sensors.py b/.bin/Scripts/functions/sensors.py
index 993306bd..49a7472c 100644
--- a/.bin/Scripts/functions/sensors.py
+++ b/.bin/Scripts/functions/sensors.py
@@ -1,4 +1,6 @@
-# Wizard Kit: Functions - Sensors
+'''Wizard Kit: Functions - Sensors'''
+# pylint: disable=no-name-in-module,wildcard-import
+# vim: sts=2 sw=2 ts=2
import json
import re
@@ -9,7 +11,7 @@ from settings.sensors import *
# Error Classes
class ThermalLimitReachedError(Exception):
- pass
+ '''Thermal limit reached error.'''
def clear_temps(sensor_data):
@@ -20,28 +22,30 @@ def clear_temps(sensor_data):
_data['Temps'] = []
-def fix_sensor_str(s):
+def fix_sensor_str(_s):
"""Cleanup string and return str."""
- s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', s, re.IGNORECASE)
- s = s.title()
- s = s.replace('Coretemp', 'CoreTemp')
- s = s.replace('Acpi', 'ACPI')
- s = s.replace('ACPItz', 'ACPI TZ')
- s = s.replace('Isa ', 'ISA ')
- s = s.replace('Id ', 'ID ')
- s = re.sub(r'(\D+)(\d+)', r'\1 \2', s, re.IGNORECASE)
- s = s.replace(' ', ' ')
- return s
+ _s = re.sub(r'^(\w+)-(\w+)-(\w+)', r'\1 (\2 \3)', _s, re.IGNORECASE)
+ _s = _s.title()
+ _s = _s.replace('Coretemp', 'CPUTemp')
+ _s = _s.replace('Acpi', 'ACPI')
+ _s = _s.replace('ACPItz', 'ACPI TZ')
+ _s = _s.replace('Isa ', 'ISA ')
+ _s = _s.replace('Pci ', 'PCI ')
+ _s = _s.replace('Id ', 'ID ')
+ _s = re.sub(r'(\D+)(\d+)', r'\1 \2', _s, re.IGNORECASE)
+ _s = re.sub(r'^K (\d+)Temp', r'AMD K\1 Temps', _s, re.IGNORECASE)
+ _s = re.sub(r'T(ctl|die)', r'CPU (T\1)', _s, re.IGNORECASE)
+ return _s
def generate_sensor_report(
sensor_data, *temp_labels,
- colors=True, core_only=False):
+ colors=True, cpu_only=False):
"""Generate report based on temp_labels, returns list if str."""
report = []
for _section, _adapters in sorted(sensor_data.items()):
- # CoreTemps then Other temps
- if core_only and 'Core' not in _section:
+ # CPU temps then Other temps
+ if cpu_only and 'CPU' not in _section:
continue
for _adapter, _sources in sorted(_adapters.items()):
# Adapter
@@ -56,7 +60,7 @@ def generate_sensor_report(
': ' if _label != 'Current' else '',
get_temp_str(_data.get(_label, '???'), colors=colors))
report.append(_line)
- if not core_only:
+ if not cpu_only:
report.append(' ')
# Handle empty reports (i.e. no sensors detected)
@@ -91,17 +95,17 @@ def get_colored_temp_str(temp):
else:
color = COLORS['CLEAR']
return '{color}{prefix}{temp:2.0f}°C{CLEAR}'.format(
- color = color,
- prefix = '-' if temp < 0 else '',
- temp = temp,
+ color=color,
+ prefix='-' if temp < 0 else '',
+ temp=temp,
**COLORS)
def get_raw_sensor_data():
"""Read sensor data and return dict."""
- data = {}
+ json_data = {}
cmd = ['sensors', '-j']
-
+
# Get raw data
try:
result = run_program(cmd)
@@ -122,8 +126,8 @@ def get_raw_sensor_data():
try:
json_data = json.loads('\n'.join(raw_data))
except json.JSONDecodeError:
- # Still broken, just set to empty dict
- json_data = {}
+ # Still broken, just return the empty dict
+ pass
# Done
return json_data
@@ -132,10 +136,10 @@ def get_raw_sensor_data():
def get_sensor_data():
"""Parse raw sensor data and return new dict."""
json_data = get_raw_sensor_data()
- sensor_data = {'CoreTemps': {}, 'Other': {}}
+ sensor_data = {'CPUTemps': {}, 'Other': {}}
for _adapter, _sources in json_data.items():
- if 'coretemp' in _adapter:
- _section = 'CoreTemps'
+ if is_cpu_adapter(_adapter):
+ _section = 'CPUTemps'
else:
_section = 'Other'
sensor_data[_section][_adapter] = {}
@@ -157,8 +161,8 @@ def get_sensor_data():
}
# Remove empty sections
- for k, v in sensor_data.items():
- v = {k2: v2 for k2, v2 in v.items() if v2}
+ for _k, _v in sensor_data.items():
+ _v = {_k2: _v2 for _k2, _v2 in _v.items() if _v2}
# Done
return sensor_data
@@ -178,14 +182,20 @@ def get_temp_str(temp, colors=True):
temp)
+def is_cpu_adapter(adapter):
+ """Checks if adapter is a known CPU adapter, returns bool."""
+ is_cpu = re.search(r'(core|k\d+)temp', adapter, re.IGNORECASE)
+ return bool(is_cpu)
+
+
def monitor_sensors(monitor_pane, monitor_file):
"""Continually update sensor data and report to screen."""
sensor_data = get_sensor_data()
while True:
update_sensor_data(sensor_data)
- with open(monitor_file, 'w') as f:
+ with open(monitor_file, 'w') as _f:
report = generate_sensor_report(sensor_data, 'Current', 'Max')
- f.write('\n'.join(report))
+ _f.write('\n'.join(report))
sleep(1)
if monitor_pane and not tmux_poll_pane(monitor_pane):
break
@@ -196,7 +206,7 @@ def save_average_temp(sensor_data, temp_label, seconds=10):
clear_temps(sensor_data)
# Get temps
- for i in range(seconds):
+ for _i in range(seconds): # pylint: disable=unused-variable
update_sensor_data(sensor_data)
sleep(1)
@@ -219,24 +229,15 @@ def update_sensor_data(sensor_data, thermal_limit=None):
_data['Current'] = _temp
_data['Max'] = max(_temp, _data['Max'])
_data['Temps'].append(_temp)
- except Exception:
+ except Exception: # pylint: disable=broad-except
# Dumb workound for Dell sensors with changing source names
pass
# Check if thermal limit reached
- if thermal_limit and _section == 'CoreTemps':
+ if thermal_limit and _section == 'CPUTemps':
if max(_data['Current'], _data['Max']) >= thermal_limit:
- raise ThermalLimitReachedError('CoreTemps reached limit')
-
-
-def join_columns(column1, column2, width=55):
- return '{:<{}}{}'.format(
- column1,
- 55+len(column1)-len(REGEX_COLORS.sub('', column1)),
- column2)
+ raise ThermalLimitReachedError('CPU temps reached limit')
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/hw-diags-menu b/.bin/Scripts/hw-diags-menu
index 1c241bf9..fc95e04a 100755
--- a/.bin/Scripts/hw-diags-menu
+++ b/.bin/Scripts/hw-diags-menu
@@ -49,7 +49,7 @@ if __name__ == '__main__':
global_vars=global_vars)
# Done
- sleep(10)
+ sleep(1)
pause('Press Enter to exit...')
exit_script(1)
From 86f17757dbd4ac3f67d1f3ba8001919e335226c1 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 17:49:10 -0600
Subject: [PATCH 25/63] Updated cleanup sections
---
.bin/Scripts/functions/cleanup.py | 22 ++++++++++++------
.bin/Scripts/settings/cleanup.py | 37 +++++++++++++++++++++++++++++++
2 files changed, 52 insertions(+), 7 deletions(-)
create mode 100644 .bin/Scripts/settings/cleanup.py
diff --git a/.bin/Scripts/functions/cleanup.py b/.bin/Scripts/functions/cleanup.py
index 5ee20be1..744ee048 100644
--- a/.bin/Scripts/functions/cleanup.py
+++ b/.bin/Scripts/functions/cleanup.py
@@ -1,7 +1,9 @@
-# Wizard Kit: Functions - Cleanup
-
-from functions.common import *
+'''Wizard Kit: Functions - Cleanup'''
+# pylint: disable=no-name-in-module,wildcard-import
+# vim: sts=2 sw=2 ts=2
+from functions.setup import *
+from settings.cleanup import *
def cleanup_adwcleaner():
"""Move AdwCleaner folders into the ClientDir."""
@@ -75,8 +77,7 @@ def cleanup_desktop():
desktop_path = r'{USERPROFILE}\Desktop'.format(**global_vars['Env'])
for entry in os.scandir(desktop_path):
- # JRT, RKill, Shortcut cleaner
- if re.search(r'^(JRT|RKill|sc-cleaner)', entry.name, re.IGNORECASE):
+ if DESKTOP_ITEMS.search(entry.name):
dest_name = r'{}\{}'.format(dest_folder, entry.name)
dest_name = non_clobber_rename(dest_name)
shutil.move(entry.path, dest_name)
@@ -130,7 +131,14 @@ def delete_registry_value(hive, key, value):
winreg.DeleteValue(k, value)
+def restore_default_uac():
+ """Restores default UAC settings via the registry."""
+ if global_vars['OS']['Version'] == '10':
+ write_registry_settings(UAC_DEFAULTS_WIN10, all_users=True)
+ else:
+ # Haven't checked Win8 settings, only applying minimum set
+ write_registry_settings(UAC_DEFAULTS_WIN7, all_users=True)
+
+
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/settings/cleanup.py b/.bin/Scripts/settings/cleanup.py
new file mode 100644
index 00000000..2188fc6b
--- /dev/null
+++ b/.bin/Scripts/settings/cleanup.py
@@ -0,0 +1,37 @@
+'''Wizard Kit: Settings - Cleanup'''
+# vim: sts=2 sw=2 ts=2
+
+import re
+
+# Regex
+DESKTOP_ITEMS = re.compile(
+ r'^(JRT|RKill|sc-cleaner)',
+ re.IGNORECASE,
+ )
+
+# Registry
+UAC_DEFAULTS_WIN7 = {
+ r'Software\Microsoft\Windows\CurrentVersion\Policies\System': {
+ 'DWORD Items': {
+ 'ConsentPromptBehaviorAdmin': 5,
+ 'EnableLUA': 1,
+ 'PromptOnSecureDesktop': 1,
+ },
+ },
+ }
+UAC_DEFAULTS_WIN10 = {
+ r'Software\Microsoft\Windows\CurrentVersion\Policies\System': {
+ 'DWORD Items': {
+ 'ConsentPromptBehaviorAdmin': 5,
+ 'ConsentPromptBehaviorUser': 3,
+ 'EnableInstallerDetection': 1,
+ 'EnableLUA': 1,
+ 'EnableVirtualization': 1,
+ 'PromptOnSecureDesktop': 1,
+ },
+ },
+ }
+
+
+if __name__ == '__main__':
+ print("This file is not meant to be called directly.")
From c537a01fbf16d6f85dada1dd30b3c6f2b09654ad Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 17:55:50 -0600
Subject: [PATCH 26/63] Updated info.py
---
.bin/Scripts/functions/info.py | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/.bin/Scripts/functions/info.py b/.bin/Scripts/functions/info.py
index 84d92663..b1959090 100644
--- a/.bin/Scripts/functions/info.py
+++ b/.bin/Scripts/functions/info.py
@@ -95,7 +95,7 @@ def get_installed_antivirus():
out = out.stdout.decode().strip()
state = out.split('=')[1]
state = hex(int(state))
- if str(state)[3:5] != '10':
+ if str(state)[3:5] not in ['10', '11']:
programs.append('[Disabled] {}'.format(prod))
else:
programs.append(prod)
@@ -446,16 +446,19 @@ def show_os_name():
def show_temp_files_size():
"""Show total size of temp files identified by BleachBit."""
- size = None
+ size_str = None
+ total = 0
with open(r'{LogDir}\Tools\BleachBit.log'.format(**global_vars), 'r') as f:
for line in f.readlines():
- if re.search(r'^disk space to be recovered:', line, re.IGNORECASE):
+ if re.search(r'^Disk space (to be |)recovered:', line, re.IGNORECASE):
size = re.sub(r'.*: ', '', line.strip())
size = re.sub(r'(\w)iB$', r' \1b', size)
- if size is None:
- print_warning(size, timestamp=False)
+ total += convert_to_bytes(size)
+ size_str = human_readable_size(total, decimals=1)
+ if size_str is None:
+ print_warning('UNKNOWN', timestamp=False)
else:
- print_standard(size, timestamp=False)
+ print_standard(size_str, timestamp=False)
def show_user_data_summary(indent=8, width=32):
From 519572b53a4dd686d1bf861c35cd68adbab1b393 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:03:36 -0600
Subject: [PATCH 27/63] Fixed grub path
---
.bin/Scripts/settings/ufd.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/settings/ufd.py b/.bin/Scripts/settings/ufd.py
index 77b7f6df..22e9a777 100644
--- a/.bin/Scripts/settings/ufd.py
+++ b/.bin/Scripts/settings/ufd.py
@@ -59,7 +59,7 @@ BOOT_ENTRIES = {
BOOT_FILES = {
# Directory: extension
'/arch/boot/syslinux': 'cfg',
- '/boot/grub/': 'cfg',
+ '/boot/grub': 'cfg',
'/EFI/boot': 'conf',
}
From f30d195cc444eb493d057b40b91e020ab2274f92 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:08:50 -0600
Subject: [PATCH 28/63] Updated Explorer registry entries
---
.bin/Scripts/settings/setup.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index c11073ec..ec3219a2 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -134,10 +134,12 @@ SETTINGS_EXPLORER_USER = {
},
# Hide People bar
r'Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People': {
+ 'Invalid modes': ['Cur'],
'DWORD Items': {'PeopleBand': 0},
},
# Hide Search button / box
r'Software\Microsoft\Windows\CurrentVersion\Search': {
+ 'Invalid modes': ['Cur'],
'DWORD Items': {'SearchboxTaskbarMode': 0},
},
}
From 3007c22c41d0c2fd1d2657938a753ffd5fb7a60f Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:40:25 -0600
Subject: [PATCH 29/63] Added Windows Update sections
---
.bin/Scripts/functions/windows_updates.py | 143 ++++++++++++++++++++++
.bin/Scripts/windows_updates.py | 46 +++++++
2 files changed, 189 insertions(+)
create mode 100644 .bin/Scripts/functions/windows_updates.py
create mode 100644 .bin/Scripts/windows_updates.py
diff --git a/.bin/Scripts/functions/windows_updates.py b/.bin/Scripts/functions/windows_updates.py
new file mode 100644
index 00000000..3618fbb2
--- /dev/null
+++ b/.bin/Scripts/functions/windows_updates.py
@@ -0,0 +1,143 @@
+# Wizard Kit: Functions - Windows updates
+
+from functions.common import *
+
+
+# Functions
+def delete_folder(folder_path):
+ """Near-useless wrapper for shutil.rmtree."""
+ shutil.rmtree(folder_path)
+
+
+def disable_service(service_name):
+ """Set service startup to disabled."""
+ run_program(['sc', 'config', service_name, 'start=', 'disabled'])
+
+ # Verify service was disabled
+ start_type = get_service_start_type(service_name)
+ if not start_type.lower().startswith('disabled'):
+ raise GenericError('Failed to disable service {}'.format(service_name))
+
+
+def disable_windows_updates():
+ """Disable windows updates and clear SoftwareDistribution folder."""
+ indent=2
+ width=52
+ update_folders = [
+ r'{WINDIR}\SoftwareDistribution'.format(**global_vars['Env']),
+ r'{SYSTEMDRIVE}\$WINDOWS.~BT'.format(**global_vars['Env']),
+ ]
+
+ for service in ('wuauserv', 'bits'):
+ # Stop service
+ result = try_and_print(
+ 'Stopping service {}...'.format(service),
+ indent=indent, width=width,
+ function=stop_service, service_name=service)
+ if not result['CS']:
+ result = try_and_print(
+ 'Stopping service {}...'.format(service),
+ indent=indent, width=width,
+ function=stop_service, service_name=service)
+ if not result['CS']:
+ raise GenericError('Service {} could not be stopped.'.format(service))
+
+ # Disable service
+ result = try_and_print(
+ 'Disabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=disable_service, service_name=service)
+ if not result['CS']:
+ result = try_and_print(
+ 'Disabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=disable_service, service_name=service)
+ if not result['CS']:
+ raise GenericError('Service {} could not be disabled.'.format(service))
+
+ # Delete update folders
+ for folder_path in update_folders:
+ if os.path.exists(folder_path):
+ result = try_and_print(
+ 'Deleting folder {}...'.format(folder_path),
+ indent=indent, width=width,
+ function=delete_folder, folder_path=folder_path)
+ if not result['CS']:
+ raise GenericError('Failed to remove folder {}'.format(folder_path))
+
+
+def enable_service(service_name, start_type='auto'):
+ """Enable service by setting start type."""
+ run_program(['sc', 'config', service_name, 'start=', start_type])
+
+
+def enable_windows_updates(silent=False):
+ """Enable windows updates"""
+ indent=2
+ width=52
+
+ for service in ('bits', 'wuauserv'):
+ # Enable service
+ start_type = 'auto'
+ if service == 'wuauserv':
+ start_type = 'demand'
+ if silent:
+ try:
+ enable_service(service, start_type=start_type)
+ except Exception:
+ # Try again
+ enable_service(service, start_type=start_type)
+ else:
+ result = try_and_print(
+ 'Enabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=enable_service, service_name=service, start_type=start_type)
+ if not result['CS']:
+ result = try_and_print(
+ 'Enabling service {}...'.format(service),
+ indent=indent, width=width,
+ function=enable_service, service_name=service, start_type=start_type)
+ if not result['CS']:
+ raise GenericError('Service {} could not be enabled.'.format(service))
+
+
+def get_service_status(service_name):
+ """Get service status using psutil, returns str."""
+ status = 'Unknown'
+ try:
+ service = psutil.win_service_get(service_name)
+ status = service.status()
+ except psutil.NoSuchProcess:
+ # Ignore and return 'Unknown' below
+ pass
+
+ return status
+
+
+def get_service_start_type(service_name):
+ """Get service startup type using psutil, returns str."""
+ start_type = 'Unknown'
+ try:
+ service = psutil.win_service_get(service_name)
+ start_type = service.start_type()
+ except psutil.NoSuchProcess:
+ # Ignore and return 'Unknown' below
+ pass
+
+ return start_type
+
+
+def stop_service(service_name):
+ """Stop service."""
+ run_program(['net', 'stop', service_name], check=False)
+
+ # Verify service was stopped
+ status = get_service_status(service_name)
+ if not status.lower().startswith('stopped'):
+ raise GenericError('Failed to stop service {}'.format(service_name))
+
+
+if __name__ == '__main__':
+ print("This file is not meant to be called directly.")
+
+# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/windows_updates.py b/.bin/Scripts/windows_updates.py
new file mode 100644
index 00000000..e29f8c48
--- /dev/null
+++ b/.bin/Scripts/windows_updates.py
@@ -0,0 +1,46 @@
+# Wizard Kit: Windows updates
+
+import os
+import sys
+
+# Init
+sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+from functions.windows_updates import *
+init_global_vars()
+os.system('title {}: Windows Updates Tool'.format(KIT_NAME_FULL))
+set_log_file('Windows Updates Tool.log')
+
+if __name__ == '__main__':
+ try:
+ clear_screen()
+ print_info('{}: Windows Updates Tool\n'.format(KIT_NAME_FULL))
+
+ # Check args
+ if '--disable' in sys.argv:
+ disable_windows_updates()
+ elif '--enable' in sys.argv:
+ enable_windows_updates()
+ else:
+ print_error('Bad mode.')
+ abort()
+
+ # Done
+ exit_script()
+ except GenericError as err:
+ # Failed to complete request, show error(s) and prompt tech
+ print_standard(' ')
+ for line in str(err).splitlines():
+ print_warning(line)
+ print_standard(' ')
+ print_error('Error(s) encountered, see above.')
+ print_standard(' ')
+ if '--disable' in sys.argv:
+ print_standard('Please reboot and try again.')
+ pause('Press Enter to exit... ')
+ exit_script(1)
+ except SystemExit as sys_exit:
+ exit_script(sys_exit.code)
+ except:
+ major_exception()
+
+# vim: sts=2 sw=2 ts=2
From b95586a590cc63949efa7ad9d34954a11f08c381 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 18:42:31 -0600
Subject: [PATCH 30/63] Moved to a unified system setup script
* Replaces:
* Install SW Bundle
* New System Setup
* User Checklist
* System Checklist
---
.bin/Scripts/functions/common.py | 6 +
.bin/Scripts/functions/setup.py | 4 +
.bin/Scripts/new_system_setup.py | 160 -------------
.bin/Scripts/settings/launchers.py | 62 ++---
.bin/Scripts/system_checklist.py | 133 -----------
.bin/Scripts/system_setup.py | 354 +++++++++++++++++++++++++++++
.bin/Scripts/user_checklist.py | 90 --------
7 files changed, 397 insertions(+), 412 deletions(-)
delete mode 100644 .bin/Scripts/new_system_setup.py
delete mode 100644 .bin/Scripts/system_checklist.py
create mode 100644 .bin/Scripts/system_setup.py
delete mode 100644 .bin/Scripts/user_checklist.py
diff --git a/.bin/Scripts/functions/common.py b/.bin/Scripts/functions/common.py
index 2828bcb4..689cc85f 100644
--- a/.bin/Scripts/functions/common.py
+++ b/.bin/Scripts/functions/common.py
@@ -91,6 +91,12 @@ class SecureBootNotAvailError(Exception):
class SecureBootUnknownError(Exception):
pass
+class WindowsOutdatedError(Exception):
+ pass
+
+class WindowsUnsupportedError(Exception):
+ pass
+
# General functions
def abort(show_prompt=True):
diff --git a/.bin/Scripts/functions/setup.py b/.bin/Scripts/functions/setup.py
index ad575f75..f9f864e9 100644
--- a/.bin/Scripts/functions/setup.py
+++ b/.bin/Scripts/functions/setup.py
@@ -334,6 +334,10 @@ def open_device_manager():
popen_program(['mmc', 'devmgmt.msc'])
+def open_speedtest():
+ popen_program(['start', '', 'https://fast.com'], shell=True)
+
+
def open_windows_activation():
popen_program(['slui'])
diff --git a/.bin/Scripts/new_system_setup.py b/.bin/Scripts/new_system_setup.py
deleted file mode 100644
index 68e508d2..00000000
--- a/.bin/Scripts/new_system_setup.py
+++ /dev/null
@@ -1,160 +0,0 @@
-# Wizard Kit: New system setup
-
-import os
-import sys
-
-# Init
-sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-from functions.activation import *
-from functions.browsers import *
-from functions.cleanup import *
-from functions.info import *
-from functions.product_keys import *
-from functions.setup import *
-from functions.sw_diags import *
-init_global_vars()
-os.system('title {}: New System Setup'.format(KIT_NAME_FULL))
-set_log_file('New System Setup.log')
-
-if __name__ == '__main__':
- other_results = {
- 'Error': {
- 'BIOSKeyNotFoundError': 'BIOS key not found',
- 'CalledProcessError': 'Unknown Error',
- 'FileNotFoundError': 'File not found',
- 'GenericError': 'Unknown Error',
- 'SecureBootDisabledError': 'Disabled',
- },
- 'Warning': {
- 'GenericRepair': 'Repaired',
- 'NoProfilesError': 'No profiles found',
- 'NotInstalledError': 'Not installed',
- 'OSInstalledLegacyError': 'OS installed Legacy',
- 'SecureBootNotAvailError': 'Not available',
- 'SecureBootUnknownError': 'Unknown',
- 'UnsupportedOSError': 'Unsupported OS',
- }}
- try:
- stay_awake()
- clear_screen()
-
- # Check installed OS
- if os_is_unsupported(show_alert=False):
- print_warning('OS version not supported by this script')
- if not ask('Continue anyway? (NOT RECOMMENDED)'):
- abort()
-
- # Install Adobe Reader?
- answer_adobe_reader = ask('Install Adobe Reader?')
-
- # Install LibreOffice?
- answer_libreoffice = ask('Install LibreOffice?')
-
- # Install MSE?
- if global_vars['OS']['Version'] == '7':
- answer_mse = ask('Install MSE?')
- else:
- answer_mse = False
-
- # Install software
- print_info('Installing Programs')
- install_vcredists()
- if answer_adobe_reader:
- try_and_print(message='Adobe Reader DC...',
- function=install_adobe_reader, other_results=other_results)
- result = try_and_print(
- message='Ninite bundle...',
- function=install_ninite_bundle, cs='Started',
- mse=answer_mse, libreoffice=answer_libreoffice,
- other_results=other_results)
- for proc in result['Out']:
- # Wait for all processes to finish
- proc.wait()
-
- # Scan for supported browsers
- print_info('Scanning for browsers')
- scan_for_browsers()
-
- # Install extensions
- print_info('Installing Extensions')
- try_and_print(message='Classic Shell skin...',
- function=install_classicstart_skin,
- other_results=other_results)
- try_and_print(message='Google Chrome extensions...',
- function=install_chrome_extensions)
- try_and_print(message='Mozilla Firefox extensions...',
- function=install_firefox_extensions,
- other_results=other_results)
-
- # Configure software
- print_info('Configuring programs')
- install_adblock()
- if global_vars['OS']['Version'] == '10':
- try_and_print(message='ClassicStart...',
- function=config_classicstart, cs='Done')
- try_and_print(message='Explorer (user)...',
- function=config_explorer_user, cs='Done')
-
- # Configure system
- print_info('Configuring system')
- if global_vars['OS']['Version'] == '10':
- try_and_print(message='Explorer (system)...',
- function=config_explorer_system, cs='Done')
- try_and_print(message='Windows Updates...',
- function=config_windows_updates, cs='Done')
- try_and_print(message='Updating Clock...',
- function=update_clock, cs='Done')
-
- # Restart Explorer
- try_and_print(message='Restarting Explorer...',
- function=restart_explorer, cs='Done')
-
- # Summary
- print_info('Summary')
- try_and_print(message='Operating System:',
- function=show_os_name, ns='Unknown', silent_function=False)
- try_and_print(message='Activation:',
- function=show_os_activation, ns='Unknown', silent_function=False)
- if (not windows_is_activated()
- and global_vars['OS']['Version'] in ('8', '8.1', '10')):
- try_and_print(message='BIOS Activation:',
- function=activate_with_bios,
- other_results=other_results)
- try_and_print(message='Secure Boot Status:',
- function=check_secure_boot_status, other_results=other_results)
- try_and_print(message='Installed RAM:',
- function=show_installed_ram, ns='Unknown', silent_function=False)
- show_free_space()
- try_and_print(message='Installed Antivirus:',
- function=get_installed_antivirus, ns='Unknown',
- other_results=other_results, print_return=True)
-
- # Play audio, show devices, open Windows updates, and open Activation
- try_and_print(message='Opening Device Manager...',
- function=open_device_manager, cs='Started')
- try_and_print(message='Opening HWiNFO (Sensors)...',
- function=run_hwinfo_sensors, cs='Started', other_results=other_results)
- try_and_print(message='Opening Windows Updates...',
- function=open_windows_updates, cs='Started')
- if not windows_is_activated():
- try_and_print(message='Opening Windows Activation...',
- function=open_windows_activation, cs='Started')
- sleep(3)
- try_and_print(message='Running XMPlay...',
- function=run_xmplay, cs='Started', other_results=other_results)
- try:
- check_secure_boot_status(show_alert=True)
- except:
- # Only trying to open alert message boxes
- pass
-
- # Done
- print_standard('\nDone.')
- pause('Press Enter to exit...')
- exit_script()
- except SystemExit as sys_exit:
- exit_script(sys_exit.code)
- except:
- major_exception()
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/settings/launchers.py b/.bin/Scripts/settings/launchers.py
index 73a70923..7161c98e 100644
--- a/.bin/Scripts/settings/launchers.py
+++ b/.bin/Scripts/settings/launchers.py
@@ -1,35 +1,20 @@
-# Wizard Kit: Settings - Launchers
+'''Wizard Kit: Settings - Launchers'''
+# pylint: disable=line-too-long
+# vim: sts=2 sw=2 ts=2
LAUNCHERS = {
r'(Root)': {
- 'Activate Windows': {
- 'L_TYPE': 'PyScript',
- 'L_PATH': 'Scripts',
- 'L_ITEM': 'activate.py',
- 'L_ELEV': 'True',
- },
- 'New System Setup': {
- 'L_TYPE': 'PyScript',
- 'L_PATH': 'Scripts',
- 'L_ITEM': 'new_system_setup.py',
- 'L_ELEV': 'True',
- },
- 'System Checklist': {
- 'L_TYPE': 'PyScript',
- 'L_PATH': 'Scripts',
- 'L_ITEM': 'system_checklist.py',
- 'L_ELEV': 'True',
- },
'System Diagnostics': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
'L_ITEM': 'system_diagnostics.py',
'L_ELEV': 'True',
},
- 'User Checklist': {
+ 'System Setup': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
- 'L_ITEM': 'user_checklist.py',
+ 'L_ITEM': 'system_setup.py',
+ 'L_ELEV': 'True',
},
},
r'Data Recovery': {
@@ -55,6 +40,7 @@ LAUNCHERS = {
},
},
r'Data Transfers': {
+ # pylint: disable=bad-continuation
'FastCopy (as ADMIN)': {
'L_TYPE': 'Executable',
'L_PATH': 'FastCopy',
@@ -257,7 +243,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'erunt',
'L_ITEM': 'ERUNT.EXE',
- 'L_ARGS': '%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
+ 'L_ARGS': r'%client_dir%\Backups\Registry\%iso_date% sysreg curuser otherusers',
'L_ELEV': 'True',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@@ -287,13 +273,13 @@ LAUNCHERS = {
r'Drivers': {
'Intel RST (Current Release)': {
'L_TYPE': 'Executable',
- 'L_PATH': '_Drivers\Intel RST',
+ 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': 'SetupRST_17.2.exe',
'L_7ZIP': 'SetupRST_17.2.exe',
},
'Intel RST (Previous Releases)': {
'L_TYPE': 'Folder',
- 'L_PATH': '_Drivers\Intel RST',
+ 'L_PATH': r'_Drivers\Intel RST',
'L_ITEM': '.',
'L_NCMD': 'True',
},
@@ -309,7 +295,7 @@ LAUNCHERS = {
},
'Snappy Driver Installer Origin': {
'L_TYPE': 'Executable',
- 'L_PATH': '_Drivers\SDIO',
+ 'L_PATH': r'_Drivers\SDIO',
'L_ITEM': 'SDIO.exe',
},
},
@@ -435,6 +421,12 @@ LAUNCHERS = {
},
},
r'Misc': {
+ 'Activate Windows': {
+ 'L_TYPE': 'PyScript',
+ 'L_PATH': 'Scripts',
+ 'L_ITEM': 'activate.py',
+ 'L_ELEV': 'True',
+ },
'Cleanup CBS Temp Files': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
@@ -452,6 +444,20 @@ LAUNCHERS = {
'L_PATH': 'ConEmu',
'L_ITEM': 'ConEmu.exe',
},
+ 'Disable Windows Updates': {
+ 'L_TYPE': 'PyScript',
+ 'L_PATH': 'Scripts',
+ 'L_ITEM': 'windows_updates.py',
+ 'L_ARGS': '--disable',
+ 'L_ELEV': 'True',
+ },
+ 'Enable Windows Updates': {
+ 'L_TYPE': 'PyScript',
+ 'L_PATH': 'Scripts',
+ 'L_ITEM': 'windows_updates.py',
+ 'L_ARGS': '--enable',
+ 'L_ELEV': 'True',
+ },
'Enter SafeMode': {
'L_TYPE': 'PyScript',
'L_PATH': 'Scripts',
@@ -491,7 +497,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'XMPlay',
'L_ITEM': 'xmplay.exe',
- 'L_ARGS': '"%bin%\XMPlay\music.7z"',
+ 'L_ARGS': r'"%bin%\XMPlay\music.7z"',
},
},
r'Repairs': {
@@ -551,7 +557,7 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'RKill',
'L_ITEM': 'RKill.exe',
- 'L_ARGS': '-s -l %log_dir%\Tools\RKill.log',
+ 'L_ARGS': r'-s -l %log_dir%\Tools\RKill.log',
'L_ELEV': 'True',
'Extra Code': [
r'call "%bin%\Scripts\init_client_dir.cmd" /Logs',
@@ -594,5 +600,3 @@ LAUNCHERS = {
if __name__ == '__main__':
print("This file is not meant to be called directly.")
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/system_checklist.py b/.bin/Scripts/system_checklist.py
deleted file mode 100644
index a5b86e1e..00000000
--- a/.bin/Scripts/system_checklist.py
+++ /dev/null
@@ -1,133 +0,0 @@
-# Wizard Kit: System Checklist
-
-import os
-import sys
-
-# Init
-sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-from functions.activation import *
-from functions.cleanup import *
-from functions.info import *
-from functions.product_keys import *
-from functions.setup import *
-from functions.sw_diags import *
-init_global_vars()
-os.system('title {}: System Checklist Tool'.format(KIT_NAME_FULL))
-set_log_file('System Checklist.log')
-
-if __name__ == '__main__':
- try:
- stay_awake()
- clear_screen()
- print_info('{}: System Checklist Tool\n'.format(KIT_NAME_FULL))
- ticket_number = get_ticket_number()
- other_results = {
- 'Error': {
- 'BIOSKeyNotFoundError': 'BIOS key not found',
- 'CalledProcessError': 'Unknown Error',
- 'FileNotFoundError': 'File not found',
- 'GenericError': 'Unknown Error',
- 'SecureBootDisabledError': 'Disabled',
- },
- 'Warning': {
- 'OSInstalledLegacyError': 'OS installed Legacy',
- 'SecureBootNotAvailError': 'Not available',
- 'SecureBootUnknownError': 'Unknown',
- }}
- if ENABLED_TICKET_NUMBERS:
- print_info('Starting System Checklist for Ticket #{}\n'.format(
- ticket_number))
-
- # Configure
- print_info('Configure')
- if global_vars['OS']['Version'] == '10':
- try_and_print(message='Explorer...',
- function=config_explorer_system, cs='Done')
- try_and_print(message='Windows Updates...',
- function=config_windows_updates, cs='Done')
- try_and_print(message='Updating Clock...',
- function=update_clock, cs='Done')
-
- # Restart Explorer
- try_and_print(message='Restarting Explorer...',
- function=restart_explorer, cs='Done')
-
- # Cleanup
- print_info('Cleanup')
- try_and_print(message='AdwCleaner...',
- function=cleanup_adwcleaner, cs='Done', other_results=other_results)
- try_and_print(message='Desktop...',
- function=cleanup_desktop, cs='Done')
- try_and_print(message='{}...'.format(KIT_NAME_FULL),
- function=delete_empty_folders, cs='Done',
- folder_path=global_vars['ClientDir'])
-
- # Export system info
- print_info('Backup System Information')
- try_and_print(message='AIDA64 reports...',
- function=run_aida64, cs='Done', other_results=other_results)
- try_and_print(message='File listing...',
- function=backup_file_list, cs='Done', other_results=other_results)
- try_and_print(message='Power plans...',
- function=backup_power_plans, cs='Done')
- try_and_print(message='Product Keys...', other_results=other_results,
- function=run_produkey, cs='Done')
- try_and_print(message='Registry...',
- function=backup_registry, cs='Done', other_results=other_results)
-
- # User data
- print_info('User Data')
- show_user_data_summary()
-
- # Summary
- print_info('Summary')
- try_and_print(message='Operating System:',
- function=show_os_name, ns='Unknown', silent_function=False)
- try_and_print(message='Activation:',
- function=show_os_activation, ns='Unknown', silent_function=False)
- if (not windows_is_activated()
- and global_vars['OS']['Version'] in ('8', '8.1', '10')):
- try_and_print(message='BIOS Activation:',
- function=activate_with_bios,
- other_results=other_results)
- try_and_print(message='Secure Boot Status:',
- function=check_secure_boot_status, other_results=other_results)
- try_and_print(message='Installed RAM:',
- function=show_installed_ram, ns='Unknown', silent_function=False)
- show_free_space()
- try_and_print(message='Installed Antivirus:',
- function=get_installed_antivirus, ns='Unknown',
- other_results=other_results, print_return=True)
- try_and_print(message='Installed Office:',
- function=get_installed_office, ns='Unknown',
- other_results=other_results, print_return=True)
-
- # Play audio, show devices, open Windows updates, and open Activation
- try_and_print(message='Opening Device Manager...',
- function=open_device_manager, cs='Started')
- try_and_print(message='Opening HWiNFO (Sensors)...',
- function=run_hwinfo_sensors, cs='Started', other_results=other_results)
- try_and_print(message='Opening Windows Updates...',
- function=open_windows_updates, cs='Started')
- if not windows_is_activated():
- try_and_print(message='Opening Windows Activation...',
- function=open_windows_activation, cs='Started')
- sleep(3)
- try_and_print(message='Running XMPlay...',
- function=run_xmplay, cs='Started', other_results=other_results)
- try:
- check_secure_boot_status(show_alert=True)
- except:
- # Only trying to open alert message boxes
- pass
-
- # Done
- print_standard('\nDone.')
- pause('Press Enter exit...')
- exit_script()
- except SystemExit as sys_exit:
- exit_script(sys_exit.code)
- except:
- major_exception()
-
-# vim: sts=2 sw=2 ts=2
diff --git a/.bin/Scripts/system_setup.py b/.bin/Scripts/system_setup.py
new file mode 100644
index 00000000..5d49b26d
--- /dev/null
+++ b/.bin/Scripts/system_setup.py
@@ -0,0 +1,354 @@
+'''Wizard Kit: System Setup'''
+# pylint: disable=wildcard-import,wrong-import-position
+# vim: sts=2 sw=2 ts=2
+
+import os
+import sys
+
+# Init
+sys.path.append(os.path.dirname(os.path.realpath(__file__)))
+from collections import OrderedDict
+from functions.activation import *
+from functions.browsers import *
+from functions.cleanup import *
+from functions.info import *
+from functions.product_keys import *
+from functions.setup import *
+from functions.sw_diags import *
+from functions.windows_updates import *
+init_global_vars()
+os.system('title {}: System Setup'.format(KIT_NAME_FULL))
+set_log_file('System Setup.log')
+
+
+# STATIC VARIABLES
+# pylint: disable=bad-whitespace,line-too-long
+OTHER_RESULTS = {
+ 'Error': {
+ 'BIOSKeyNotFoundError': 'BIOS KEY NOT FOUND',
+ 'CalledProcessError': 'UNKNOWN ERROR',
+ 'FileNotFoundError': 'FILE NOT FOUND',
+ 'GenericError': 'UNKNOWN ERROR',
+ 'Not4KAlignedError': 'FALSE',
+ 'SecureBootDisabledError': 'DISABLED',
+ 'WindowsUnsupportedError': 'UNSUPPORTED',
+ },
+ 'Warning': {
+ 'GenericRepair': 'REPAIRED',
+ 'NoProfilesError': 'NO PROFILES FOUND',
+ 'NotInstalledError': 'NOT INSTALLED',
+ 'OSInstalledLegacyError': 'OS INSTALLED LEGACY',
+ 'SecureBootNotAvailError': 'NOT AVAILABLE',
+ 'SecureBootUnknownError': 'UNKNOWN',
+ 'UnsupportedOSError': 'UNSUPPORTED OS',
+ 'WindowsOutdatedError': 'OUTDATED',
+ },
+ }
+SETUP_ACTIONS = OrderedDict({
+ # Install software
+ 'Installing Programs': {'Info': True},
+ 'VCR': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_vcredists, 'Just run': True,},
+ 'LibreOffice': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_libreoffice,
+ 'If answer': 'LibreOffice', 'KWArgs': {'quickstart': False, 'register_mso_types': True, 'use_mso_formats': True, 'vcredist': False},
+ },
+ 'Ninite bundle': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_ninite_bundle, 'KWArgs': {'cs': 'STARTED'},},
+
+ # Browsers
+ 'Scanning for browsers': {'Info': True},
+ 'Scan': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': scan_for_browsers, 'Just run': True, 'KWArgs': {'skip_ie': True},},
+ 'Backing up browsers': {'Info': True},
+ 'Backup browsers': {'New': False, 'Fab': True, 'Cur': True, 'HW': False, 'Function': backup_browsers, 'Just run': True,},
+
+ # Install extensions
+ 'Installing Extensions': {'Info': True},
+ 'Classic Shell skin': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Function': install_classicstart_skin, 'Win10 only': True,},
+ 'Chrome extensions': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_chrome_extensions,},
+ 'Firefox extensions': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_firefox_extensions,},
+
+ # Configure software'
+ 'Configuring Programs': {'Info': True},
+ 'Browser add-ons': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_adblock, 'Just run': True,
+ 'Pause': 'Please enable uBlock Origin for all browsers',
+ },
+ 'Classic Start': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Function': config_classicstart, 'Win10 only': True,},
+ 'Config Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': config_windows_updates, 'Win10 only': True,},
+ 'Enable Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': enable_windows_updates, 'KWArgs': {'silent': True},},
+ 'Explorer (system)': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': config_explorer_system, 'Win10 only': True,},
+ 'Explorer (user)': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': config_explorer_user, 'Win10 only': True,},
+ 'Restart Explorer': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': restart_explorer,},
+ 'Update Clock': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': update_clock,},
+
+ # Cleanup
+ 'Cleaning up': {'Info': True},
+ 'AdwCleaner': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': cleanup_adwcleaner,},
+ 'Desktop': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': cleanup_desktop,},
+ 'KIT_NAME_FULL': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': delete_empty_folders,},
+
+ # System Info
+ 'Exporting system info': {'Info': True},
+ 'AIDA64 Report': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': run_aida64,},
+ 'File listing': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': backup_file_list,},
+ 'Power plans': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': backup_power_plans,},
+ 'Product Keys': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_produkey,},
+ 'Registry': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': backup_registry,},
+
+ # Show Summary
+ 'Summary': {'Info': True},
+ 'Operating System': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_os_name, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_os_activation, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'BIOS Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': activate_with_bios, 'If not activated': True,},
+ 'Secure Boot': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': check_secure_boot_status, 'KWArgs': {'show_alert': False},},
+ 'Installed RAM': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_installed_ram, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Temp size': {'New': False, 'Fab': False, 'Cur': True, 'HW': False, 'Function': show_temp_files_size, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Show free space': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': show_free_space, 'Just run': True,},
+ 'Installed AV': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': get_installed_antivirus, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
+ 'Installed Office': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': get_installed_office, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
+ 'Partitions 4K aligned': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': check_4k_alignment, 'KWArgs': {'cs': 'TRUE', 'ns': 'FALSE'},},
+
+ # Open things
+ 'Opening Programs': {'Info': True},
+ 'Device Manager': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': open_device_manager, 'KWArgs': {'cs': 'STARTED'},},
+ 'HWiNFO sensors': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_hwinfo_sensors, 'KWArgs': {'cs': 'STARTED'},},
+ 'Speed test': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': open_speedtest, 'KWArgs': {'cs': 'STARTED'},},
+ 'Windows Updates': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': open_windows_updates, 'KWArgs': {'cs': 'STARTED'},},
+ 'Windows Activation': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': open_windows_activation, 'If not activated': True, 'KWArgs': {'cs': 'STARTED'},},
+ 'Sleep': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': sleep, 'Just run': True, 'KWArgs': {'seconds': 3},},
+ 'XMPlay': {'New': True, 'Fab': True, 'Cur': True, 'HW': True, 'Function': run_xmplay, 'KWArgs': {'cs': 'STARTED'},},
+ })
+SETUP_ACTION_KEYS = (
+ 'Function',
+ 'If not activated',
+ 'Info',
+ 'Just run',
+ 'KWArgs',
+ 'Pause',
+ )
+SETUP_QUESTIONS = {
+ # AV
+ 'MSE': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
+
+ # LibreOffice
+ 'LibreOffice': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
+
+ # Ninite
+ 'Base': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Ninite': True},
+ 'Missing': {'New': False, 'Fab': True, 'Cur': False, 'HW': False, 'Ninite': True},
+ 'Standard': {'New': True, 'Fab': True, 'Cur': False, 'HW': False, 'Ninite': True},
+ }
+# pylint: enable=bad-whitespace,line-too-long
+
+
+# Functions
+def check_os_and_abort():
+ """Check OS and prompt to abort if not supported."""
+ result = try_and_print(
+ message='OS support status...',
+ function=check_os_support_status,
+ cs='GOOD',
+ )
+ if not result['CS'] and 'Unsupported' in result['Error']:
+ print_warning('OS version not supported by this script')
+ if not ask('Continue anyway? (NOT RECOMMENDED)'):
+ abort()
+
+
+def get_actions(setup_mode, answers):
+ """Get actions to perform based on setup_mode, returns OrderedDict."""
+ actions = OrderedDict({})
+ for _key, _val in SETUP_ACTIONS.items():
+ _action = {}
+ _if_answer = _val.get('If answer', False)
+ _win10_only = _val.get('Win10 only', False)
+
+ # Set enabled status
+ _enabled = _val.get(setup_mode, False)
+ if _if_answer:
+ _enabled = _enabled and answers[_if_answer]
+ if _win10_only:
+ _enabled = _enabled and global_vars['OS']['Version'] == '10'
+ _action['Enabled'] = _enabled
+
+ # Set other keys
+ for _sub_key in SETUP_ACTION_KEYS:
+ _action[_sub_key] = _val.get(_sub_key, None)
+
+ # Fix KWArgs
+ if _action.get('KWArgs', {}) is None:
+ _action['KWArgs'] = {}
+
+ # Handle "special" actions
+ if _key == 'KIT_NAME_FULL':
+ # Cleanup WK folders
+ _key = KIT_NAME_FULL
+ _action['KWArgs'] = {'folder_path': global_vars['ClientDir']}
+ elif _key == 'Ninite bundle':
+ # Add install_ninite_bundle() kwargs
+ _action['KWArgs'].update({
+ kw.lower(): kv for kw, kv in answers.items()
+ if SETUP_QUESTIONS.get(kw, {}).get('Ninite', False)
+ })
+ elif _key == 'Explorer (user)':
+ # Explorer settings (user)
+ _action['KWArgs'] = {'setup_mode': setup_mode}
+
+ # Add to dict
+ actions[_key] = _action
+
+ return actions
+
+
+def get_answers(setup_mode):
+ """Get setup answers based on setup_mode and user input, returns dict."""
+ answers = {k: v.get(setup_mode, False) for k, v in SETUP_QUESTIONS.items()}
+
+ # Answer setup questions as needed
+ if answers['MSE'] is None and global_vars['OS']['Version'] == '7':
+ answers.update(get_av_selection())
+
+ if answers['LibreOffice'] is None:
+ answers['LibreOffice'] = ask('Install LibreOffice?')
+
+ return answers
+
+
+def get_av_selection():
+ """Get AV selection."""
+ av_answers = {
+ 'MSE': False,
+ }
+ av_options = [
+ {
+ 'Name': 'Microsoft Security Essentials',
+ 'Disabled': global_vars['OS']['Version'] not in ['7'],
+ },
+ ]
+ actions = [
+ {'Name': 'None', 'Letter': 'N'},
+ {'Name': 'Quit', 'Letter': 'Q'},
+ ]
+
+ # Show menu
+ selection = menu_select(
+ 'Please select an option to install',
+ main_entries=av_options,
+ action_entries=actions)
+ if selection.isnumeric():
+ index = int(selection) - 1
+ if 'Microsoft' in av_options[index]['Name']:
+ av_answers['MSE'] = True
+ elif selection == 'Q':
+ abort()
+
+ return av_answers
+
+
+def get_mode():
+ """Get mode via menu_select, returns str."""
+ setup_mode = None
+ mode_options = [
+ {'Name': 'New', 'Display Name': 'New / Clean install (no data)'},
+ {'Name': 'Data', 'Display Name': 'Clean install with data migration'},
+ {'Name': 'Cur', 'Display Name': 'Original OS (post-repair or overinstall)'},
+ {'Name': 'HW', 'Display Name': 'Hardware service (i.e. no software work)'},
+ ]
+ actions = [
+ {'Name': 'Quit', 'Letter': 'Q'},
+ ]
+
+ # Get selection
+ selection = menu_select(
+ 'Please select a setup mode',
+ main_entries=mode_options,
+ action_entries=actions)
+ if selection.isnumeric():
+ index = int(selection) - 1
+ setup_mode = mode_options[index]['Name']
+ elif selection == 'Q':
+ abort()
+
+ return setup_mode
+
+
+def main():
+ """Main function."""
+ stay_awake()
+ clear_screen()
+
+ # Check installed OS
+ check_os_and_abort()
+
+ # Get setup mode
+ setup_mode = get_mode()
+
+ # Get answers to setup questions
+ answers = get_answers(setup_mode)
+
+ # Get actions to perform
+ actions = get_actions(setup_mode, answers)
+
+ # Perform actions
+ for action, values in actions.items():
+ kwargs = values.get('KWArgs', {})
+
+ # Print info lines
+ if values.get('Info', False):
+ print_info(action)
+ continue
+
+ # Print disabled actions
+ if not values.get('Enabled', False):
+ show_data(
+ message='{}...'.format(action),
+ data='DISABLED',
+ warning=True,
+ )
+ continue
+
+ # Check Windows activation if requested
+ if values.get('If not activated', False) and windows_is_activated():
+ # Skip
+ continue
+
+ # Run function
+ if values.get('Just run', False):
+ values['Function'](**kwargs)
+ else:
+ result = try_and_print(
+ message='{}...'.format(action),
+ function=values['Function'],
+ other_results=OTHER_RESULTS,
+ **kwargs)
+
+ # Wait for Ninite proc(s)
+ if action == 'Ninite bundle':
+ print_standard('Waiting for installations to finish...')
+ try:
+ for proc in result['Out']:
+ proc.wait()
+ except KeyboardInterrupt:
+ pass
+
+ # Pause
+ if values.get('Pause', False):
+ print_standard(values['Pause'])
+ pause()
+
+ # Show alert box for SecureBoot issues
+ try:
+ check_secure_boot_status(show_alert=True)
+ except Exception: # pylint: disable=broad-except
+ # Ignoring exceptions since we just want to show the popup
+ pass
+
+ # Done
+ pause('Press Enter to exit... ')
+
+
+if __name__ == '__main__':
+ try:
+ main()
+ exit_script()
+ except SystemExit as sys_exit:
+ exit_script(sys_exit.code)
+ except: # pylint: disable=bare-except
+ major_exception()
diff --git a/.bin/Scripts/user_checklist.py b/.bin/Scripts/user_checklist.py
deleted file mode 100644
index 0abd88f3..00000000
--- a/.bin/Scripts/user_checklist.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# Wizard Kit: User Checklist
-
-import os
-import sys
-
-# Init
-sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-from functions.browsers import *
-from functions.cleanup import *
-from functions.setup import *
-init_global_vars()
-os.system('title {}: User Checklist Tool'.format(KIT_NAME_FULL))
-set_log_file('User Checklist ({USERNAME}).log'.format(**global_vars['Env']))
-
-if __name__ == '__main__':
- try:
- stay_awake()
- clear_screen()
- print_info('{}: User Checklist\n'.format(KIT_NAME_FULL))
- other_results = {
- 'Warning': {
- 'NotInstalledError': 'Not installed',
- 'NoProfilesError': 'No profiles found',
- }}
- answer_config_browsers = ask('Install adblock?')
- if answer_config_browsers:
- answer_reset_browsers = ask(
- 'Reset browsers to safe defaults first?')
- if global_vars['OS']['Version'] == '10':
- answer_config_classicshell = ask('Configure ClassicShell?')
- answer_config_explorer_user = ask('Configure Explorer?')
-
- # Cleanup
- print_info('Cleanup')
- try_and_print(message='Desktop...',
- function=cleanup_desktop, cs='Done')
-
- # Scan for supported browsers
- print_info('Scanning for browsers')
- scan_for_browsers()
-
- # Homepages
- print_info('Current homepages')
- list_homepages()
-
- # Backup
- print_info('Backing up browsers')
- backup_browsers()
-
- # Reset
- if answer_config_browsers and answer_reset_browsers:
- print_info('Resetting browsers')
- reset_browsers()
-
- # Configure
- print_info('Configuring programs')
- if answer_config_browsers:
- install_adblock()
- if global_vars['OS']['Version'] == '10':
- if answer_config_classicshell:
- try_and_print(message='ClassicStart...',
- function=config_classicstart, cs='Done')
- if answer_config_explorer_user:
- try_and_print(message='Explorer...',
- function=config_explorer_user, cs='Done')
- if (not answer_config_browsers
- and not answer_config_classicshell
- and not answer_config_explorer_user):
- print_warning(' Skipped')
- else:
- if not answer_config_browsers:
- print_warning(' Skipped')
-
- # Restart Explorer
- try_and_print(message='Restarting Explorer...',
- function=restart_explorer, cs='Done')
-
- # Run speedtest
- popen_program(['start', '', 'https://fast.com'], shell=True)
-
- # Done
- print_standard('\nDone.')
- pause('Press Enter to exit...')
- exit_script()
- except SystemExit as sys_exit:
- exit_script(sys_exit.code)
- except:
- major_exception()
-
-# vim: sts=2 sw=2 ts=2
From 3611ff920afbd89b351d673f489673d5402325c3 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 20:30:38 -0600
Subject: [PATCH 31/63] Updated functions.cleanup
---
.bin/Scripts/functions/cleanup.py | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/.bin/Scripts/functions/cleanup.py b/.bin/Scripts/functions/cleanup.py
index acbd2d65..1e49e4ca 100644
--- a/.bin/Scripts/functions/cleanup.py
+++ b/.bin/Scripts/functions/cleanup.py
@@ -71,6 +71,7 @@ def cleanup_cbs(dest_folder):
def cleanup_d7ii():
+ # pylint: disable=too-many-branches
"""Sort d7II logs and remove temp items."""
d7_path = r'{}\d7II'.format(global_vars['ClientDir'])
d7_reports = r'{} Reports'.format(d7_path)
@@ -79,9 +80,9 @@ def cleanup_d7ii():
# Logs & Reports
if os.path.exists(d7_reports):
for entry in os.scandir(d7_reports):
- r = re.match(r'(\d+)-(\d+)-(\d+)', entry.name)
+ _r = re.match(r'(\d+)-(\d+)-(\d+)', entry.name)
d7_date = '{}-{:02d}-{:02d}'.format(
- r.group(1), int(r.group(2)), int(r.group(3)))
+ _r.group(1), int(_r.group(2)), int(_r.group(3)))
d7_mlogs = r'{}\Malware Logs'.format(entry.path)
log_dest = r'{SYSTEMDRIVE}\{prefix}\Logs\{date}'.format(
prefix=KIT_NAME_SHORT,
@@ -139,13 +140,9 @@ def cleanup_d7ii():
os.rmdir(d7_path)
except OSError:
pass
-
+
# Restore default UAC settings
- 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)
+ restore_default_uac()
def cleanup_desktop():
"""Move known backup files and reports into the ClientDir."""
@@ -230,7 +227,7 @@ def delete_registry_key(hive, key, recurse=False):
# Delete all subkeys first
with winreg.OpenKeyEx(hive, key, 0, access) as k:
key_info = winreg.QueryInfoKey(k)
- for x in range(key_info[0]):
+ for _i in range(key_info[0]): # pylint: disable=unused-variable
subkey = r'{}\{}'.format(key, winreg.EnumKey(k, 0))
delete_registry_key(hive, subkey)
From dba39dd9c449a0b102d3e3a4658ab42614203415 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 20:32:30 -0600
Subject: [PATCH 32/63] Don't use MS formats in LibreOffice by default
---
.bin/Scripts/system_setup.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/system_setup.py b/.bin/Scripts/system_setup.py
index 5d49b26d..83108081 100644
--- a/.bin/Scripts/system_setup.py
+++ b/.bin/Scripts/system_setup.py
@@ -49,7 +49,7 @@ SETUP_ACTIONS = OrderedDict({
'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},
+ 'If answer': 'LibreOffice', 'KWArgs': {'quickstart': False, 'register_mso_types': True, 'use_mso_formats': False, 'vcredist': False},
},
'Ninite bundle': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_ninite_bundle, 'KWArgs': {'cs': 'STARTED'},},
From d1af82e114cd3c6247cfac49020827a40bb55ea4 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 20:42:33 -0600
Subject: [PATCH 33/63] Restore default UAC in System Setup
* Fixed mode names
* Fixes issue #119
---
.bin/Scripts/system_setup.py | 95 ++++++++++++++++++------------------
1 file changed, 48 insertions(+), 47 deletions(-)
diff --git a/.bin/Scripts/system_setup.py b/.bin/Scripts/system_setup.py
index 83108081..5fa239ab 100644
--- a/.bin/Scripts/system_setup.py
+++ b/.bin/Scripts/system_setup.py
@@ -47,73 +47,74 @@ OTHER_RESULTS = {
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,
+ 'VCR': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': install_vcredists, 'Just run': True,},
+ 'LibreOffice': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': install_libreoffice,
'If answer': 'LibreOffice', 'KWArgs': {'quickstart': False, 'register_mso_types': True, 'use_mso_formats': False, 'vcredist': False},
},
- 'Ninite bundle': {'New': True, 'Fab': True, 'Cur': True, 'HW': False, 'Function': install_ninite_bundle, 'KWArgs': {'cs': 'STARTED'},},
+ 'Ninite bundle': {'New': True, 'Dat': 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},},
+ 'Scan': {'New': True, 'Dat': 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,},
+ 'Backup browsers': {'New': False, 'Dat': 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,},
+ 'Classic Shell skin': {'New': True, 'Dat': True, 'Cur': False, 'HW': False, 'Function': install_classicstart_skin, 'Win10 only': True,},
+ 'Chrome extensions': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': install_chrome_extensions,},
+ 'Firefox extensions': {'New': True, 'Dat': 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,
+ 'Browser add-ons': {'New': True, 'Dat': 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,},
+ 'Classic Start': {'New': True, 'Dat': True, 'Cur': False, 'HW': False, 'Function': config_classicstart, 'Win10 only': True,},
+ 'Config Windows Updates': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': config_windows_updates, 'Win10 only': True,},
+ 'Enable Windows Updates': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': enable_windows_updates, 'KWArgs': {'silent': True},},
+ 'Explorer (system)': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': config_explorer_system, 'Win10 only': True,},
+ 'Explorer (user)': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': config_explorer_user, 'Win10 only': True,},
+ 'Restart Explorer': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': restart_explorer,},
+ 'Restore default UAC': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': restore_default_uac,},
+ 'Update Clock': {'New': True, 'Dat': 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,},
+ 'AdwCleaner': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': cleanup_adwcleaner,},
+ 'Desktop': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': cleanup_desktop,},
+ 'KIT_NAME_FULL': {'New': True, 'Dat': 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,},
+ 'AIDA64 Report': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': run_aida64,},
+ 'File listing': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': backup_file_list,},
+ 'Power plans': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': backup_power_plans,},
+ 'Product Keys': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': run_produkey,},
+ 'Registry': {'New': True, 'Dat': 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'},},
+ 'Operating System': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': show_os_name, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Activation': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': show_os_activation, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'BIOS Activation': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': activate_with_bios, 'If not activated': True,},
+ 'Secure Boot': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': check_secure_boot_status, 'KWArgs': {'show_alert': False},},
+ 'Installed RAM': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': show_installed_ram, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Temp size': {'New': False, 'Dat': False, 'Cur': True, 'HW': False, 'Function': show_temp_files_size, 'KWArgs': {'ns': 'UNKNOWN', 'silent_function': False},},
+ 'Show free space': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': show_free_space, 'Just run': True,},
+ 'Installed AV': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': get_installed_antivirus, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
+ 'Installed Office': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': get_installed_office, 'KWArgs': {'ns': 'UNKNOWN', 'print_return': True},},
+ 'Partitions 4K aligned': {'New': True, 'Dat': 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'},},
+ 'Device Manager': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': open_device_manager, 'KWArgs': {'cs': 'STARTED'},},
+ 'HWiNFO sensors': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': run_hwinfo_sensors, 'KWArgs': {'cs': 'STARTED'},},
+ 'Speed test': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': open_speedtest, 'KWArgs': {'cs': 'STARTED'},},
+ 'Windows Updates': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': open_windows_updates, 'KWArgs': {'cs': 'STARTED'},},
+ 'Windows Activation': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Function': open_windows_activation, 'If not activated': True, 'KWArgs': {'cs': 'STARTED'},},
+ 'Sleep': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': sleep, 'Just run': True, 'KWArgs': {'seconds': 3},},
+ 'XMPlay': {'New': True, 'Dat': True, 'Cur': True, 'HW': True, 'Function': run_xmplay, 'KWArgs': {'cs': 'STARTED'},},
})
SETUP_ACTION_KEYS = (
'Function',
@@ -125,15 +126,15 @@ SETUP_ACTION_KEYS = (
)
SETUP_QUESTIONS = {
# AV
- 'MSE': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
+ 'MSE': {'New': None, 'Dat': None, 'Cur': None, 'HW': False, 'Ninite': True},
# LibreOffice
- 'LibreOffice': {'New': None, 'Fab': None, 'Cur': None, 'HW': False, 'Ninite': True},
+ 'LibreOffice': {'New': None, 'Dat': 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},
+ 'Base': {'New': True, 'Dat': True, 'Cur': True, 'HW': False, 'Ninite': True},
+ 'Missing': {'New': False, 'Dat': True, 'Cur': False, 'HW': False, 'Ninite': True},
+ 'Standard': {'New': True, 'Dat': True, 'Cur': False, 'HW': False, 'Ninite': True},
}
# pylint: enable=bad-whitespace,line-too-long
@@ -247,7 +248,7 @@ def get_mode():
setup_mode = None
mode_options = [
{'Name': 'New', 'Display Name': 'New / Clean install (no data)'},
- {'Name': 'Data', 'Display Name': 'Clean install with data migration'},
+ {'Name': 'Dat', '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)'},
]
From 96c806198a453a132cb5365657625ca336bef9df Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 20:46:17 -0600
Subject: [PATCH 34/63] Removed redundant UAC section
---
.bin/Scripts/functions/cleanup.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.bin/Scripts/functions/cleanup.py b/.bin/Scripts/functions/cleanup.py
index 1e49e4ca..595ff5f9 100644
--- a/.bin/Scripts/functions/cleanup.py
+++ b/.bin/Scripts/functions/cleanup.py
@@ -141,9 +141,6 @@ def cleanup_d7ii():
except OSError:
pass
- # Restore default UAC settings
- restore_default_uac()
-
def cleanup_desktop():
"""Move known backup files and reports into the ClientDir."""
dest_folder = r'{LogDir}\Tools'.format(**global_vars)
From 87668c6ad08dc5a71a964777376106f7fa9d7c15 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 11 Jun 2019 20:52:45 -0600
Subject: [PATCH 35/63] Avoid rare crash when uploading results for review
* Fixes issue #117
---
.bin/Scripts/functions/hw_diags.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index db5d01ca..ea19e90f 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -1141,7 +1141,9 @@ def run_hw_tests(state):
show_results(state)
# Upload for review
- if ENABLED_UPLOAD_DATA and ask('Upload results for review?'):
+ if (ENABLED_UPLOAD_DATA
+ and DEBUG_MODE
+ and ask('Upload results for review?')):
try_and_print(
message='Saving debug reports...',
function=save_debug_reports,
From 441a6ad66fdc3e83f2f638478aaf7e4e79394278 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 14:05:45 -0600
Subject: [PATCH 36/63] Prevent ddrescue-tui crash when LogDir is missing
* Fixes issue #115
---
.bin/Scripts/functions/ddrescue.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index 463adf4d..be306f2c 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -278,6 +278,7 @@ class RecoveryState():
raise GenericError('Unsupported mode')
self.get_smart_source()
self.set_working_dir()
+ os.makedirs(global_vars['LogDir'], exist_ok=True)
def add_block_pair(self, source, dest):
"""Run safety checks and append new BlockPair to internal list."""
From 90f5285067af65204d0da321ba126c120aec0948 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 14:58:27 -0600
Subject: [PATCH 37/63] Updated tmux menu launchers
* Use current TMUX session if present
* Can switch TMUX session without nesting
* Fixes issue #114
---
.bin/Scripts/ddrescue-tui | 29 +++++++++++++++++++++++------
.bin/Scripts/hw-diags | 26 ++++++++++++++++++++------
2 files changed, 43 insertions(+), 12 deletions(-)
diff --git a/.bin/Scripts/ddrescue-tui b/.bin/Scripts/ddrescue-tui
index e41c6bb1..650015af 100755
--- a/.bin/Scripts/ddrescue-tui
+++ b/.bin/Scripts/ddrescue-tui
@@ -8,10 +8,10 @@ MENU="ddrescue-tui-menu"
function ask() {
while :; do
- read -p "$1 " -r answer
- if echo "$answer" | egrep -iq '^(y|yes|sure)$'; then
+ read -p "$1 [Y/N] " -r answer
+ if echo "$answer" | grep -Eiq '^(y|yes|sure)$'; then
return 0
- elif echo "$answer" | egrep -iq '^(n|no|nope)$'; then
+ elif echo "$answer" | grep -Eiq '^(n|no|nope)$'; then
return 1
fi
done
@@ -26,7 +26,16 @@ die () {
if tmux list-session | grep -q "$SESSION_NAME"; then
echo "WARNING: tmux session $SESSION_NAME already exists."
echo ""
- if ask "Kill current session?"; then
+ if ask "Connect to current session?"; then
+ if [[ -n "${TMUX:-}" ]]; then
+ # Running inside TMUX, switch to session
+ tmux switch-client -t "$SESSION_NAME"
+ else
+ # Running outside TMUX, attach to session
+ tmux attach-session -t "$SESSION_NAME"
+ fi
+ exit 0
+ elif ask "Kill current session and start new session?"; then
tmux kill-session -t "$SESSION_NAME" || \
die "Failed to kill session: $SESSION_NAME"
else
@@ -38,6 +47,14 @@ if tmux list-session | grep -q "$SESSION_NAME"; then
fi
fi
-# Start session
-tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" $*
+# Start/Rename session
+if [[ -n "${TMUX:-}" ]]; then
+ # Running inside TMUX, rename session/window and open the menu
+ tmux rename-session "$SESSION_NAME"
+ tmux rename-window "$WINDOW_NAME"
+ "$MENU" "$@"
+else
+ # Running outside TMUX, start/attach to session
+ tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" "$@"
+fi
diff --git a/.bin/Scripts/hw-diags b/.bin/Scripts/hw-diags
index d3a1cb21..9de50f9c 100755
--- a/.bin/Scripts/hw-diags
+++ b/.bin/Scripts/hw-diags
@@ -9,9 +9,9 @@ MENU="hw-diags-menu"
function ask() {
while :; do
read -p "$1 [Y/N] " -r answer
- if echo "$answer" | egrep -iq '^(y|yes|sure)$'; then
+ if echo "$answer" | grep -Eiq '^(y|yes|sure)$'; then
return 0
- elif echo "$answer" | egrep -iq '^(n|no|nope)$'; then
+ elif echo "$answer" | grep -Eiq '^(n|no|nope)$'; then
return 1
fi
done
@@ -27,8 +27,14 @@ if tmux list-session | grep -q "$SESSION_NAME"; then
echo "WARNING: tmux session $SESSION_NAME already exists."
echo ""
if ask "Connect to current session?"; then
- # Do nothing, the command below will attach/connect
- echo ""
+ if [[ -n "${TMUX:-}" ]]; then
+ # Running inside TMUX, switch to session
+ tmux switch-client -t "$SESSION_NAME"
+ else
+ # Running outside TMUX, attach to session
+ tmux attach-session -t "$SESSION_NAME"
+ fi
+ exit 0
elif ask "Kill current session and start new session?"; then
tmux kill-session -t "$SESSION_NAME" || \
die "Failed to kill session: $SESSION_NAME"
@@ -41,6 +47,14 @@ if tmux list-session | grep -q "$SESSION_NAME"; then
fi
fi
-# Start session
-tmux new-session -A -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" $*
+# Start/Rename session
+if [[ -n "${TMUX:-}" ]]; then
+ # Running inside TMUX, rename session/window and open the menu
+ tmux rename-session "$SESSION_NAME"
+ tmux rename-window "$WINDOW_NAME"
+ "$MENU" "$@"
+else
+ # Running outside TMUX, start/attach to session
+ tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" "$@"
+fi
From e30e52e8807da4c3c6d3ec6ca95e2924a488da7a Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 15:31:30 -0600
Subject: [PATCH 38/63] New launch-in-tmux script
* Generic run cmd in tmux wrapper
* ddrescue-tui and hw-diags source this script to run their respective menus
* Reduces duplicate code
---
.bin/Scripts/ddrescue-tui | 57 +++------------------------------
.bin/Scripts/hw-diags | 59 +++-------------------------------
.bin/Scripts/launch-in-tmux | 64 +++++++++++++++++++++++++++++++++++++
3 files changed, 73 insertions(+), 107 deletions(-)
create mode 100755 .bin/Scripts/launch-in-tmux
diff --git a/.bin/Scripts/ddrescue-tui b/.bin/Scripts/ddrescue-tui
index 650015af..6ee8ad57 100755
--- a/.bin/Scripts/ddrescue-tui
+++ b/.bin/Scripts/ddrescue-tui
@@ -2,59 +2,10 @@
#
## Wizard Kit: ddrescue TUI Launcher
+source launch-in-tmux
+
SESSION_NAME="ddrescue-tui"
WINDOW_NAME="ddrescue TUI"
-MENU="ddrescue-tui-menu"
-
-function ask() {
- while :; do
- read -p "$1 [Y/N] " -r answer
- if echo "$answer" | grep -Eiq '^(y|yes|sure)$'; then
- return 0
- elif echo "$answer" | grep -Eiq '^(n|no|nope)$'; then
- return 1
- fi
- done
-}
-
-die () {
- echo "$0:" "$@" >&2
- exit 1
-}
-
-# Check for running session
-if tmux list-session | grep -q "$SESSION_NAME"; then
- echo "WARNING: tmux session $SESSION_NAME already exists."
- echo ""
- if ask "Connect to current session?"; then
- if [[ -n "${TMUX:-}" ]]; then
- # Running inside TMUX, switch to session
- tmux switch-client -t "$SESSION_NAME"
- else
- # Running outside TMUX, attach to session
- tmux attach-session -t "$SESSION_NAME"
- fi
- exit 0
- elif ask "Kill current session and start new session?"; then
- tmux kill-session -t "$SESSION_NAME" || \
- die "Failed to kill session: $SESSION_NAME"
- else
- echo "Aborted."
- echo ""
- echo -n "Press Enter to exit... "
- read -r
- exit 0
- fi
-fi
-
-# Start/Rename session
-if [[ -n "${TMUX:-}" ]]; then
- # Running inside TMUX, rename session/window and open the menu
- tmux rename-session "$SESSION_NAME"
- tmux rename-window "$WINDOW_NAME"
- "$MENU" "$@"
-else
- # Running outside TMUX, start/attach to session
- tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" "$@"
-fi
+TMUX_CMD="ddrescue-tui-menu"
+launch_in_tmux "$@"
diff --git a/.bin/Scripts/hw-diags b/.bin/Scripts/hw-diags
index 9de50f9c..70f84db4 100755
--- a/.bin/Scripts/hw-diags
+++ b/.bin/Scripts/hw-diags
@@ -1,60 +1,11 @@
#!/bin/bash
#
-## Wizard Kit: HW Diagnostics - Menu Launcher
+## Wizard Kit: HW Diagnostics Launcher
+
+source launch-in-tmux
SESSION_NAME="hw-diags"
WINDOW_NAME="Hardware Diagnostics"
-MENU="hw-diags-menu"
-
-function ask() {
- while :; do
- read -p "$1 [Y/N] " -r answer
- if echo "$answer" | grep -Eiq '^(y|yes|sure)$'; then
- return 0
- elif echo "$answer" | grep -Eiq '^(n|no|nope)$'; then
- return 1
- fi
- done
-}
-
-die () {
- echo "$0:" "$@" >&2
- exit 1
-}
-
-# Check for running session
-if tmux list-session | grep -q "$SESSION_NAME"; then
- echo "WARNING: tmux session $SESSION_NAME already exists."
- echo ""
- if ask "Connect to current session?"; then
- if [[ -n "${TMUX:-}" ]]; then
- # Running inside TMUX, switch to session
- tmux switch-client -t "$SESSION_NAME"
- else
- # Running outside TMUX, attach to session
- tmux attach-session -t "$SESSION_NAME"
- fi
- exit 0
- elif ask "Kill current session and start new session?"; then
- tmux kill-session -t "$SESSION_NAME" || \
- die "Failed to kill session: $SESSION_NAME"
- else
- echo "Aborted."
- echo ""
- echo -n "Press Enter to exit... "
- read -r
- exit 0
- fi
-fi
-
-# Start/Rename session
-if [[ -n "${TMUX:-}" ]]; then
- # Running inside TMUX, rename session/window and open the menu
- tmux rename-session "$SESSION_NAME"
- tmux rename-window "$WINDOW_NAME"
- "$MENU" "$@"
-else
- # Running outside TMUX, start/attach to session
- tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$MENU" "$@"
-fi
+TMUX_CMD="hw-diags-menu"
+launch_in_tmux "$@"
diff --git a/.bin/Scripts/launch-in-tmux b/.bin/Scripts/launch-in-tmux
new file mode 100755
index 00000000..e737b574
--- /dev/null
+++ b/.bin/Scripts/launch-in-tmux
@@ -0,0 +1,64 @@
+#!/bin/bash
+#
+## Wizard Kit: TMUX Launcher
+
+function ask() {
+ while :; do
+ read -p "$1 [Y/N] " -r answer
+ if echo "$answer" | grep -Eiq '^(y|yes|sure)$'; then
+ return 0
+ elif echo "$answer" | grep -Eiq '^(n|no|nope)$'; then
+ return 1
+ fi
+ done
+}
+
+die () {
+ echo "$0:" "$@" >&2
+ exit 1
+}
+
+function launch_in_tmux() {
+ # Check for required vars
+ [[ -n "${SESSION_NAME:-}" ]] || die "Required variable missing (SESSION_NAME)"
+ [[ -n "${WINDOW_NAME:-}" ]] || die "Required variable missing (WINDOW_NAME)"
+ [[ -n "${TMUX_CMD:-}" ]] || die "Required variable missing (TMUX_CMD)"
+
+ # Check for running session
+ if tmux list-session | grep -q "$SESSION_NAME"; then
+ echo "WARNING: tmux session $SESSION_NAME already exists."
+ echo ""
+ if ask "Connect to current session?"; then
+ if [[ -n "${TMUX:-}" ]]; then
+ # Running inside TMUX, switch to session
+ tmux switch-client -t "$SESSION_NAME"
+ else
+ # Running outside TMUX, attach to session
+ tmux attach-session -t "$SESSION_NAME"
+ fi
+ exit 0
+ elif ask "Kill current session and start new session?"; then
+ tmux kill-session -t "$SESSION_NAME" || \
+ die "Failed to kill session: $SESSION_NAME"
+ else
+ echo "Aborted."
+ echo ""
+ echo -n "Press Enter to exit... "
+ read -r
+ exit 0
+ fi
+ fi
+
+ # Start/Rename session
+ if [[ -n "${TMUX:-}" ]]; then
+ # Running inside TMUX, rename session/window and open the menu
+ tmux rename-session "$SESSION_NAME"
+ tmux rename-window "$WINDOW_NAME"
+ "$TMUX_CMD" "$@"
+ tmux rename-session "${SESSION_NAME}_DONE"
+ tmux rename-window "${WINDOW_NAME}_DONE"
+ else
+ # Running outside TMUX, start/attach to session
+ tmux new-session -s "$SESSION_NAME" -n "$WINDOW_NAME" "$TMUX_CMD" "$@"
+ fi
+}
From c50627867efcac6baa4225a8204a29ff1cc6174b Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 15:37:19 -0600
Subject: [PATCH 39/63] Switch to prev tmux from hw-diags & ddrescue-tui
* Only during normal exits, not done for aborts/crashes
---
.bin/Scripts/ddrescue-tui-menu | 2 ++
.bin/Scripts/functions/tmux.py | 12 ++++++++++++
.bin/Scripts/hw-diags-menu | 2 ++
3 files changed, 16 insertions(+)
diff --git a/.bin/Scripts/ddrescue-tui-menu b/.bin/Scripts/ddrescue-tui-menu
index f65e24e1..eab8cd3f 100755
--- a/.bin/Scripts/ddrescue-tui-menu
+++ b/.bin/Scripts/ddrescue-tui-menu
@@ -45,6 +45,7 @@ if __name__ == '__main__':
# Done
print_standard('\nDone.')
pause("Press Enter to exit...")
+ tmux_switch_client()
exit_script()
except GenericAbort:
abort()
@@ -55,6 +56,7 @@ if __name__ == '__main__':
print_error(msg)
abort()
except SystemExit as sys_exit:
+ tmux_switch_client()
exit_script(sys_exit.code)
except:
major_exception()
diff --git a/.bin/Scripts/functions/tmux.py b/.bin/Scripts/functions/tmux.py
index 81522268..8c6ad327 100644
--- a/.bin/Scripts/functions/tmux.py
+++ b/.bin/Scripts/functions/tmux.py
@@ -141,6 +141,18 @@ def tmux_split_window(
return result.stdout.decode().strip()
+def tmux_switch_client(target_session=None):
+ """Switch to target tmux session, or previous if none specified."""
+ cmd = ['tmux', 'switch-client']
+ if target_session:
+ cmd.extend(['-t', target_session])
+ else:
+ # Switch to previous instead
+ cmd.append('-p')
+
+ run_program(cmd, check=False)
+
+
def tmux_update_pane(
pane_id, command=None, working_dir=None,
text=None, watch=None, watch_cmd='cat'):
diff --git a/.bin/Scripts/hw-diags-menu b/.bin/Scripts/hw-diags-menu
index fc95e04a..7a122ae7 100755
--- a/.bin/Scripts/hw-diags-menu
+++ b/.bin/Scripts/hw-diags-menu
@@ -23,6 +23,7 @@ if __name__ == '__main__':
sleep(1)
pause('Press Enter to exit...')
except SystemExit as sys_exit:
+ tmux_switch_client()
exit_script(sys_exit.code)
except:
# Cleanup
@@ -59,6 +60,7 @@ if __name__ == '__main__':
# Done
tmux_kill_all_panes()
+ tmux_switch_client()
exit_script()
# vim: sts=2 sw=2 ts=2
From 5b5c99e6f85fd27b683fd788a5e56905818eee09 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 15:48:11 -0600
Subject: [PATCH 40/63] Don't kill current tmux session from ddrescue-tui
* Just kill the panes and let launch-in-tmux handle the session
---
.bin/Scripts/functions/ddrescue.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index be306f2c..42d81b72 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -851,7 +851,7 @@ def menu_ddrescue(source_path, dest_path, run_mode):
menu_main(state)
# Done
- run_program(['tmux', 'kill-window'])
+ tmux_kill_all_panes()
exit_script()
From e798503f6f2db6fd3382e5ba62dbbbe9a2820b33 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 16:37:04 -0600
Subject: [PATCH 41/63] Save RAM details to CpuObj
---
.bin/Scripts/functions/hw_diags.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index ea19e90f..fe390e12 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -52,6 +52,12 @@ class CpuObj():
continue
self.lscpu[_field] = _data
+ # Get RAM details as well
+ ram_details = get_ram_details()
+ self.ram_total = human_readable_size(ram_details.pop('Total', 0)).strip()
+ self.ram_dimms = [
+ '{}x {}'.format(v, k) for k, v in sorted(ram_details.items())]
+
def generate_cpu_report(self):
"""Generate CPU report with data from all tests."""
report = []
@@ -59,11 +65,8 @@ class CpuObj():
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)))
+ report.append(' {} ({})'.format(self.ram_total, ', '.join(self.ram_dimms)))
# Tests
for test in self.tests.values():
From cbf37d8ea19dda19e924b53a9524382395e17fce Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 16:56:17 -0600
Subject: [PATCH 42/63] Include RAM details in CPU osTicket posts
* Addresses issue #88
---
.bin/Scripts/functions/osticket.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.bin/Scripts/functions/osticket.py b/.bin/Scripts/functions/osticket.py
index 4b3c5adf..b55346d8 100644
--- a/.bin/Scripts/functions/osticket.py
+++ b/.bin/Scripts/functions/osticket.py
@@ -204,6 +204,9 @@ class osTicket():
# Device
report.append(dev.description)
+ if hasattr(dev, 'ram_total'):
+ report.append('{} RAM ({})'.format(
+ dev.ram_total, ', '.join(dev.ram_dimms)))
report.append(' ')
# Test reports
From 20d054245d6a2f0ac78c930e739c388402986b74 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 17:07:34 -0600
Subject: [PATCH 43/63] Include 4K alignment warning in osTicket post
* Addresses issue #88
---
.bin/Scripts/functions/osticket.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.bin/Scripts/functions/osticket.py b/.bin/Scripts/functions/osticket.py
index b55346d8..7164ccd6 100644
--- a/.bin/Scripts/functions/osticket.py
+++ b/.bin/Scripts/functions/osticket.py
@@ -243,6 +243,8 @@ class osTicket():
report.append('Volumes:')
report.extend(self.generate_volume_report(dev, results))
report.append(' ')
+ if not dev.is_4k_aligned():
+ report.append('! NOTE: One or more partitions are not 4K aligned')
# Asterisk
if results['Asterisk']:
From 112800c98b14ef2ea31bd38966b601008b5bfcb4 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 12 Jun 2019 17:12:12 -0600
Subject: [PATCH 44/63] Don't fail drives for SMART 188/BC
* Fixes issue #100
---
.bin/Scripts/settings/hw_diags.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/settings/hw_diags.py b/.bin/Scripts/settings/hw_diags.py
index 9f0e1391..d3ea1d1f 100644
--- a/.bin/Scripts/settings/hw_diags.py
+++ b/.bin/Scripts/settings/hw_diags.py
@@ -85,7 +85,7 @@ ATTRIBUTES = {
10: {'Hex': '10', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
184: {'Hex': 'B8', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
187: {'Hex': 'BB', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
- 188: {'Hex': 'BC', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
+ 188: {'Hex': 'BC', 'Critical': False, 'Ignore': True, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
196: {'Hex': 'C4', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
197: {'Hex': 'C5', 'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, },
198: {'Hex': 'C6', 'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, },
From 6e9de7f2fad145f71c8e8f0152abb32c1c1555c4 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 19 Jun 2019 21:29:57 -0600
Subject: [PATCH 45/63] Added safety checks to d7II Launcher
* Addresses issue #99
---
.bin/Scripts/settings/launchers.py | 47 ++++++++++++++++++++++++++++++
1 file changed, 47 insertions(+)
mode change 100644 => 100755 .bin/Scripts/settings/launchers.py
diff --git a/.bin/Scripts/settings/launchers.py b/.bin/Scripts/settings/launchers.py
old mode 100644
new mode 100755
index e310c558..93097dcc
--- a/.bin/Scripts/settings/launchers.py
+++ b/.bin/Scripts/settings/launchers.py
@@ -8,6 +8,53 @@ LAUNCHERS = {
'L_TYPE': 'Executable',
'L_PATH': 'd7II',
'L_ITEM': 'd7II.exe',
+ 'Extra Code': [
+ r'rem Pre-d7II safety checks',
+ r'rem NOTE: This is an ugly hack but it works',
+ r'',
+ r':Init2',
+ r'echo Running safety checks...',
+ r'set ARCH=32',
+ r'if /i "%PROCESSOR_ARCHITECTURE%" == "AMD64" set "ARCH=64"',
+ r'set "AV_REMOVER=%bin%\AVRemover\AVRemover.exe"',
+ r'set "PYTHON=%bin%\Python\x32\python.exe"',
+ r'if %ARCH% equ 64 (',
+ r' set "AV_REMOVER=%bin%\AVRemover\AVRemover64.exe"',
+ r' set "PYTHON=%bin%\Python\x64\python.exe"',
+ r')',
+ r'',
+ r':PythonChecks',
+ r'if not exist "%PYTHON%" goto ErrorPythonNotFound',
+ r'"%PYTHON%" --version >nul || goto ErrorPythonUnsupported',
+ r'echo Python: OK',
+ r'',
+ r':AVCheck',
+ r'rem Run ESETs AV Removal tool for tech to review',
+ r'echo Please check for running AV',
+ r'start "" "%AV_REMOVER%" /wait',
+ r'echo.',
+ r'echo Press Enter to Launch d7II...',
+ r'pause>nul',
+ r'goto DefineLaunch',
+ r'',
+ r':: Pre-d7II Errors',
+ r':ErrorPythonNotFound',
+ r'rem Broken Kit',
+ r'echo.',
+ r'echo ERROR: The Python executable is missing.',
+ r'echo This kit is damaged and needs to be rebuilt',
+ r'goto Abort',
+ r'',
+ r':ErrorPythonUnsupported',
+ r'rem The Windows installation lacks Windows update KB2999226 needed to run Python',
+ r'echo.',
+ r'echo ERROR: Failed to run Python, try installing Windows update KB2999226.',
+ r'echo NOTE: That update is from October 2015 so this system is SEVERELY outdated',
+ r'if exist "%bin%\..\Installers\Extras\Windows Updates" (',
+ r' start "" "explorer.exe" "%bin%\..\Installers\Extras\Windows Updates"',
+ r')',
+ r'goto Abort',
+ ],
},
'System Setup': {
'L_TYPE': 'PyScript',
From 772b807c3d7a3285ce836c3ebd1e960f93f200e5 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 19 Jun 2019 21:38:30 -0600
Subject: [PATCH 46/63] Added AVRemover sections
---
.bin/Scripts/functions/update.py | 13 +++++++++++++
.bin/Scripts/settings/sources.py | 4 +++-
.bin/Scripts/update_kit.py | 1 +
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/.bin/Scripts/functions/update.py b/.bin/Scripts/functions/update.py
index e2ac3f21..05653484 100755
--- a/.bin/Scripts/functions/update.py
+++ b/.bin/Scripts/functions/update.py
@@ -1177,6 +1177,19 @@ def update_winaiorepair():
# Uninstallers
+def update_eset_av_remover():
+# ESET AVRemover32
+ ## NOTE: Lives in .bin uncompressed
+ # Stop running processes
+ for exe in ['AVRemover32.exe', 'AVRemover64.exe']:
+ kill_process(exe)
+
+ # Download
+ dest = r'{}\AVRemover'.format(global_vars['BinDir'])
+ download_generic(dest, 'AVRemover.exe', SOURCE_URLS['AVRemover32'])
+ download_generic(dest, 'AVRemover64.exe', SOURCE_URLS['AVRemover64'])
+
+
def update_iobit_uninstaller():
# Stop running processes
kill_process('IObitUninstallerPortable.exe')
diff --git a/.bin/Scripts/settings/sources.py b/.bin/Scripts/settings/sources.py
index 817441b5..e1537f33 100644
--- a/.bin/Scripts/settings/sources.py
+++ b/.bin/Scripts/settings/sources.py
@@ -15,8 +15,10 @@ SOURCE_URLS = {
'ClassicStartSkin': 'http://www.classicshell.net/forum/download/file.php?id=3001&sid=9a195960d98fd754867dcb63d9315335',
'Du': 'https://download.sysinternals.com/files/DU.zip',
'ERUNT': 'http://www.aumha.org/downloads/erunt.zip',
- 'ESET Online Scanner': 'https://download.eset.com/com/eset/tools/online_scanner/latest/esetonlinescanner_enu.exe',
+ 'ESET AVRemover32': 'https://download.eset.com/com/eset/tools/installers/av_remover/latest/avremover_nt32_enu.exe',
+ 'ESET AVRemover64': 'https://download.eset.com/com/eset/tools/installers/av_remover/latest/avremover_nt64_enu.exe',
'ESET NOD32 AV': 'https://download.eset.com/com/eset/apps/home/eav/windows/latest/eav_nt64.exe',
+ 'ESET Online Scanner': 'https://download.eset.com/com/eset/tools/online_scanner/latest/esetonlinescanner_enu.exe',
'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': 'https://fastcopy.jp/archive/FastCopy380_installer.exe',
diff --git a/.bin/Scripts/update_kit.py b/.bin/Scripts/update_kit.py
index 086535a4..4ac91ffb 100644
--- a/.bin/Scripts/update_kit.py
+++ b/.bin/Scripts/update_kit.py
@@ -93,6 +93,7 @@ if __name__ == '__main__':
# Uninstallers
print_info(' Uninstallers')
+ try_and_print(message='ESET AV Remover...', function=update_eset_av_remover, other_results=other_results, width=40)
try_and_print(message='IObit Uninstaller...', function=update_iobit_uninstaller, other_results=other_results, width=40)
## Review ##
From b8e09e83ce03eda01a268763b3b7888fe4df439f Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Thu, 27 Jun 2019 18:57:45 -0600
Subject: [PATCH 47/63] Fixed issue #124
---
.linux_items/include/airootfs/etc/skel/.update_network | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.linux_items/include/airootfs/etc/skel/.update_network b/.linux_items/include/airootfs/etc/skel/.update_network
index fb3ec990..5ab2a78c 100755
--- a/.linux_items/include/airootfs/etc/skel/.update_network
+++ b/.linux_items/include/airootfs/etc/skel/.update_network
@@ -3,7 +3,7 @@
## Setup network and update hostname
# Wait for WiFi
-sleep 1s
+sleep 3s
# Set hostname
IP="$(ip a show scope global \
From 747277b121b2a5bb6705e0c73add27faada034d9 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Thu, 27 Jun 2019 20:11:02 -0600
Subject: [PATCH 48/63] Adjusted Super+t keyboard shortcut
* Now it opens to a standard urxvt window (no tmux attaching)
---
.linux_items/include_x/airootfs/etc/skel/.config/i3/config | 2 +-
.linux_items/include_x/airootfs/etc/skel/.config/openbox/rc.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.linux_items/include_x/airootfs/etc/skel/.config/i3/config b/.linux_items/include_x/airootfs/etc/skel/.config/i3/config
index 102b50f5..ea9d6829 100644
--- a/.linux_items/include_x/airootfs/etc/skel/.config/i3/config
+++ b/.linux_items/include_x/airootfs/etc/skel/.config/i3/config
@@ -73,7 +73,7 @@ bindsym $mod+f exec "thunar ~"
bindsym $mod+i exec "hardinfo"
bindsym $mod+m exec "urxvt -title 'Mount All Volumes' -e mount-all-volumes gui"
bindsym $mod+s exec "urxvt -title 'Hardware Diagnostics' -e hw-diags --quick"
-bindsym $mod+t exec "urxvt -e zsh -c 'tmux new-session -A -t general; zsh'"
+bindsym $mod+t exec "urxvt"
bindsym $mod+v exec "urxvt -title 'Hardware Sensors' -e watch -c -n1 -t hw-sensors"
bindsym $mod+w exec "firefox"
diff --git a/.linux_items/include_x/airootfs/etc/skel/.config/openbox/rc.xml b/.linux_items/include_x/airootfs/etc/skel/.config/openbox/rc.xml
index e563ec04..e99f37ad 100644
--- a/.linux_items/include_x/airootfs/etc/skel/.config/openbox/rc.xml
+++ b/.linux_items/include_x/airootfs/etc/skel/.config/openbox/rc.xml
@@ -329,7 +329,7 @@
- urxvt -e zsh -c 'tmux new-session -A -t general; zsh'
+ urxvt
From 18daa41e2a00e62df8a7c846c4e35d3f67a3c7f9 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Thu, 27 Jun 2019 20:13:09 -0600
Subject: [PATCH 49/63] Trimmed down motd
---
.linux_items/include/airootfs/etc/motd | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/.linux_items/include/airootfs/etc/motd b/.linux_items/include/airootfs/etc/motd
index c84d47a0..2a0a183b 100644
--- a/.linux_items/include/airootfs/etc/motd
+++ b/.linux_items/include/airootfs/etc/motd
@@ -1,9 +1,2 @@
-Welcome to the [32m______[0m
-
-Some common commands:
-[34m%[0m hw-diags
-[34m%[0m hw-info
-[34m%[0m mount-all-volumes
-[34m%[0m mount-backup-shares
-[34m%[0m connect-to-network
+[2J[HWelcome to the [32m______[0m
From 91d8185ede7f76e4337e114e9e741fa05573c5df Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Thu, 27 Jun 2019 20:14:14 -0600
Subject: [PATCH 50/63] Updated startup scripts
* Moved starting apps from .update_x into .start_desktop_apps
* This allowed .update_x to be shown on screen during startup
* Included "... Done" messages for clarity
* Removed x_ok logic (didn't really work)
* Replaced with a generic message that _should_ be left on screen if X fails
---
.../include/airootfs/etc/skel/.update_network | 4 +++
.../include_x/airootfs/etc/skel/.Xauthority | 0
.../airootfs/etc/skel/.config/i3/config | 2 +-
.../etc/skel/.config/openbox/autostart | 4 ++-
.../airootfs/etc/skel/.start_desktop_apps | 24 +++++++++++++
.../include_x/airootfs/etc/skel/.update_x | 35 ++++++++-----------
.../include_x/airootfs/etc/skel/.xinitrc | 1 +
.../include_x/airootfs/etc/skel/.zlogin | 22 ++++++------
8 files changed, 58 insertions(+), 34 deletions(-)
create mode 100644 .linux_items/include_x/airootfs/etc/skel/.Xauthority
create mode 100755 .linux_items/include_x/airootfs/etc/skel/.start_desktop_apps
diff --git a/.linux_items/include/airootfs/etc/skel/.update_network b/.linux_items/include/airootfs/etc/skel/.update_network
index 5ab2a78c..6939afac 100755
--- a/.linux_items/include/airootfs/etc/skel/.update_network
+++ b/.linux_items/include/airootfs/etc/skel/.update_network
@@ -3,9 +3,12 @@
## Setup network and update hostname
# Wait for WiFi
+echo -n "Waiting for network... "
sleep 3s
+echo "Done"
# Set hostname
+echo -n "Updating hostname... "
IP="$(ip a show scope global \
| grep inet \
| head -1 \
@@ -19,4 +22,5 @@ fi
if [[ "${NEW_HOSTNAME:+x}" ]]; then
sudo hostnamectl set-hostname "${NEW_HOSTNAME}"
fi
+echo "Done"
diff --git a/.linux_items/include_x/airootfs/etc/skel/.Xauthority b/.linux_items/include_x/airootfs/etc/skel/.Xauthority
new file mode 100644
index 00000000..e69de29b
diff --git a/.linux_items/include_x/airootfs/etc/skel/.config/i3/config b/.linux_items/include_x/airootfs/etc/skel/.config/i3/config
index ea9d6829..bc92b2d8 100644
--- a/.linux_items/include_x/airootfs/etc/skel/.config/i3/config
+++ b/.linux_items/include_x/airootfs/etc/skel/.config/i3/config
@@ -320,4 +320,4 @@ bar {
height 26
}
-exec --no-startup-id /home/tech/.update_x
+exec urxvt -title "Initializing..." -e /home/tech/.update_x
diff --git a/.linux_items/include_x/airootfs/etc/skel/.config/openbox/autostart b/.linux_items/include_x/airootfs/etc/skel/.config/openbox/autostart
index 13b9088c..1f96ce36 100755
--- a/.linux_items/include_x/airootfs/etc/skel/.config/openbox/autostart
+++ b/.linux_items/include_x/airootfs/etc/skel/.config/openbox/autostart
@@ -1,3 +1,5 @@
#openbox-autostart
-$HOME/.update_x &
+/usr/bin/urxvt -title "Initializing..." -e "$HOME/.update_x"
+"$HOME/.start_desktop_apps" &
+
diff --git a/.linux_items/include_x/airootfs/etc/skel/.start_desktop_apps b/.linux_items/include_x/airootfs/etc/skel/.start_desktop_apps
new file mode 100755
index 00000000..d4a9abe2
--- /dev/null
+++ b/.linux_items/include_x/airootfs/etc/skel/.start_desktop_apps
@@ -0,0 +1,24 @@
+#!/bin/env bash
+#
+## Start desktop apps based on WM
+
+# Start common apps
+feh --bg-fill "$HOME/.wallpaper"
+compton --backend xrender --xrender-sync --xrender-sync-fence &
+sleep 1s
+x0vncserver -display :0 -passwordfile $HOME/.vnc/passwd -AlwaysShared &
+conky &
+nm-applet &
+volumeicon &
+
+# Start WM specific apps
+if fgrep -q "i3" /proc/cmdline; then
+ # i3
+ i3-msg restart
+else
+ # openbox
+ openbox --restart
+ tint2 &
+ cbatticon --hide-notification &
+fi
+
diff --git a/.linux_items/include_x/airootfs/etc/skel/.update_x b/.linux_items/include_x/airootfs/etc/skel/.update_x
index d7baea80..e8829371 100755
--- a/.linux_items/include_x/airootfs/etc/skel/.update_x
+++ b/.linux_items/include_x/airootfs/etc/skel/.update_x
@@ -5,6 +5,8 @@
REGEX_XRANDR='^.* ([0-9]+)x([0-9]+)\+[0-9]+\+[0-9]+.* ([0-9]+)mm x ([0-9]+)mm.*$'
REGEX_URXVT='(URxvt.geometry:\s+).*'
+echo -n "Getting display details... "
+
# Get screen data
xrandr_str="$(xrandr | grep mm | head -1)"
width_px="$(echo "${xrandr_str}" | sed -r "s/${REGEX_XRANDR}/\1/")"
@@ -29,8 +31,12 @@ width_urxvt="$(echo "${width_px} * 112/1280" | bc)"
height_urxvt="$(echo "${height_px} * 33/720" | bc)"
offset_urxvt="24"
+echo "Done"
+
# Update settings if necessary
if [[ "${dpi}" -ge 192 ]]; then
+ echo -n "Updating settings for HiDPI... "
+
# Conky
sed -i 's/minimum_size 180 0/minimum_size 360 0/' "${HOME}/.conkyrc_base"
sed -i 's/maximum_width 180/maximum_width 360/' "${HOME}/.conkyrc_base"
@@ -64,6 +70,9 @@ if [[ "${dpi}" -ge 192 ]]; then
width_urxvt="$(echo "${width_urxvt} / 2" | bc)"
height_urxvt="$(echo "${height_urxvt} / 2" | bc)"
offset_urxvt="$(echo "${offset_urxvt} * 2" | bc)"
+
+ # Done
+ echo "Done"
fi
# Update URxvt (Always)
@@ -71,33 +80,19 @@ urxvt_geometry="${width_urxvt}x${height_urxvt}+${offset_urxvt}+${offset_urxvt}"
sed -i -r "s/${REGEX_URXVT}/\1${urxvt_geometry}/" "${HOME}/.Xresources"
# Update conky
+echo -n "Updating conky... "
$HOME/.update_conky
+echo "Done"
# Update X
+echo -n "Updating X... "
xset s off
xset -dpms
xrdb -merge $HOME/.Xresources
+echo "Done"
-# Start common desktop apps
-feh --bg-fill "$HOME/.wallpaper"
-compton --backend xrender --xrender-sync --xrender-sync-fence &
-sleep 1s
-x0vncserver -display :0 -passwordfile $HOME/.vnc/passwd -AlwaysShared &
-conky &
-nm-applet &
-volumeicon &
-
-# Start WM specific apps
+# Start desktop apps for i3
if fgrep -q "i3" /proc/cmdline; then
- # i3
- i3-msg restart
-else
- # openbox
- openbox --restart
- tint2 &
- cbatticon --hide-notification &
+ i3-msg exec $HOME/.start_desktop_apps
fi
-# Prevent Xorg from being killed by .zlogin
-touch "/tmp/x_ok"
-
diff --git a/.linux_items/include_x/airootfs/etc/skel/.xinitrc b/.linux_items/include_x/airootfs/etc/skel/.xinitrc
index 7085a4df..54dc1428 100755
--- a/.linux_items/include_x/airootfs/etc/skel/.xinitrc
+++ b/.linux_items/include_x/airootfs/etc/skel/.xinitrc
@@ -3,5 +3,6 @@
dbus-update-activation-environment --systemd DISPLAY
eval $(ssh-agent)
export SSH_AUTH_SOCK
+xrdb -merge $HOME/.Xresources
exec openbox-session
diff --git a/.linux_items/include_x/airootfs/etc/skel/.zlogin b/.linux_items/include_x/airootfs/etc/skel/.zlogin
index 8901bb31..1b005d4d 100644
--- a/.linux_items/include_x/airootfs/etc/skel/.zlogin
+++ b/.linux_items/include_x/airootfs/etc/skel/.zlogin
@@ -1,7 +1,7 @@
setterm -blank 0 -powerdown 0 2>/dev/null
if [ "$(fgconsole 2>/dev/null)" -eq "1" ]; then
# Connect to network and update hostname
- $HOME/.update_network
+ "${HOME}/.update_network"
# Update settings if using i3
if fgrep -q "i3" /proc/cmdline; then
@@ -11,18 +11,16 @@ if [ "$(fgconsole 2>/dev/null)" -eq "1" ]; then
# Start X or HW-diags
if ! fgrep -q "nox" /proc/cmdline; then
- # Kill Xorg after 30 seconds if it doesn't fully initialize
- (sleep 30s; if ! [[ -f "/tmp/x_ok" ]]; then pkill '(Xorg|startx)'; fi) &
+ # Show freeze warning
+ echo ""
+ echo "NOTE: Not all GPUs/displays are supported."
+ echo " If the system is frozen on this screen"
+ echo " please restart and try CLI mode instead"
+ echo ""
- # Try starting X
- startx >/dev/null
-
- # Run Hw-Diags CLI if necessary
- if ! [[ -f "/tmp/x_ok" ]]; then
- echo "There was an issue starting Xorg, starting CLI interface..."
- sleep 2s
- hw-diags --cli
- fi
+ # Start x
+ echo "Starting X..."
+ startx >/dev/null 2>&1
else
hw-diags --cli
fi
From a868d284523a107c4a133e91a1d5df2eadc94fbf Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 2 Jul 2019 20:52:59 -0600
Subject: [PATCH 51/63] Fix NVMe/SMART attribute color logic
---
.bin/Scripts/settings/hw_diags.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.bin/Scripts/settings/hw_diags.py b/.bin/Scripts/settings/hw_diags.py
index d3ea1d1f..04eb9faa 100644
--- a/.bin/Scripts/settings/hw_diags.py
+++ b/.bin/Scripts/settings/hw_diags.py
@@ -94,9 +94,10 @@ ATTRIBUTES = {
},
}
ATTRIBUTE_COLORS = (
+ # NOTE: The order here is important; least important to most important.
+ ('Warning', 'YELLOW'),
('Error', 'RED'),
('Maximum', 'PURPLE'),
- ('Warning', 'YELLOW'),
)
KEY_NVME = 'nvme_smart_health_information_log'
KEY_SMART = 'ata_smart_attributes'
From 00772402558c9fe2fb69f20cdb0b94350b1ca406 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 2 Jul 2019 20:53:35 -0600
Subject: [PATCH 52/63] Support new color codes in osTicket posts
* Addresses #105
---
.bin/Scripts/functions/hw_diags.py | 15 +++++++++++++--
.bin/Scripts/functions/osticket.py | 11 ++++++-----
.bin/Scripts/settings/osticket.py | 6 ++++++
3 files changed, 25 insertions(+), 7 deletions(-)
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index 144f6698..39551a56 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -1215,8 +1215,12 @@ def run_hw_tests(state):
v['Objects'][-1].update_status('N/A')
if k == TESTS_CPU[-1]:
# Last CPU test run, post CPU results
+ cpu_failed = False
+ for test in state.cpu.tests.values():
+ cpu_failed = cpu_failed or test.failed
+ color_code = 'Diags FAIL' if cpu_failed else 'Diags'
state.ost.post_device_results(
- state.cpu, state.ticket_id, state.ticket_name)
+ state.cpu, state.ticket_id, state.ticket_name, color_code)
# Recheck attributes
if state.tests['NVMe / SMART']['Enabled']:
for test_obj in state.tests['NVMe / SMART']['Objects']:
@@ -1251,8 +1255,15 @@ def run_hw_tests(state):
if _disk_tests_enabled and state.disks and not state.ost.disabled:
print_standard('Posting results to osTicket...')
for disk in state.disks:
+ # Set color code
+ color_code = 'Diags'
+ for test in disk.tests.values():
+ if test.disabled:
+ continue
+ if test.failed or not (test.passed or 'N/A' in test.status):
+ color_code = 'Diags FAIL'
state.ost.post_device_results(
- disk, state.ticket_id, state.ticket_name)
+ disk, state.ticket_id, state.ticket_name, color_code)
# Check if disk checkbox needs updating
all_disks_passed = True
diff --git a/.bin/Scripts/functions/osticket.py b/.bin/Scripts/functions/osticket.py
index 7164ccd6..3c2981be 100644
--- a/.bin/Scripts/functions/osticket.py
+++ b/.bin/Scripts/functions/osticket.py
@@ -502,15 +502,15 @@ class osTicket():
# Done
return data
- def post_device_results(self, dev, ticket_id, ticket_name):
+ def post_device_results(self, dev, ticket_id, ticket_name, color='Diags'):
"""Generate osTicket friendly report and post as response to ticket."""
if not dev.tests:
# No test results available, aborting post
return
response = self.generate_report(dev, ticket_id, ticket_name)
- self.post_response(response, ticket_id)
+ self.post_response(response, ticket_id, color)
- def post_response(self, response, ticket_id):
+ def post_response(self, response, ticket_id, color='Normal'):
"""Post a reply to a ticket in osTicket."""
self.connect()
@@ -525,12 +525,13 @@ class osTicket():
# Build SQL cmd
sql_cmd = "INSERT INTO `{Name}`.`{Response}`".format(
**OSTICKET['Database'], **OSTICKET['Tables'])
- sql_cmd += " (ticket_id, staff_id, staff_name, response, created)"
+ sql_cmd += " (ticket_id, staff_id, staff_name, response, created, code)"
sql_cmd += " VALUES ("
sql_cmd += " '{}',".format(ticket_id)
sql_cmd += " '{ID}', '{Name}',".format(**OSTICKET['Staff'])
sql_cmd += " '{}',".format(response.replace("'", "\\'"))
- sql_cmd += " '{}'".format(time.strftime("%Y-%m-%d %H:%M:%S"))
+ sql_cmd += " '{}',".format(time.strftime("%Y-%m-%d %H:%M:%S"))
+ sql_cmd += " '{}'".format(OSTICKET['Color Codes'][color])
sql_cmd += " );"
# Run SQL cmd
diff --git a/.bin/Scripts/settings/osticket.py b/.bin/Scripts/settings/osticket.py
index dc30ced3..66a360f0 100644
--- a/.bin/Scripts/settings/osticket.py
+++ b/.bin/Scripts/settings/osticket.py
@@ -1,6 +1,12 @@
# Wizard Kit: Settings - osTicket
OSTICKET = {
+ 'Color Codes': {
+ 'Normal': '0',
+ 'Contact': '1',
+ 'Diags': '2',
+ 'Diags FAIL': '3',
+ },
'Database': {
'Name': 'osticket',
'User': 'wizardkit',
From 8c6fcfe99cbbb5d6855c10a9d525ddce8d7baabe Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 3 Jul 2019 19:32:25 -0600
Subject: [PATCH 53/63] Fail drives for old age but allow further testing
* Addresses issue #103
---
.bin/Scripts/functions/hw_diags.py | 15 +++++++++++++++
.bin/Scripts/settings/hw_diags.py | 4 ++--
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index 39551a56..bf3a0c6c 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -166,6 +166,18 @@ class DiskObj():
# self.dd_skip_extra == 0 is fine
pass
+ def is_aging(self):
+ """Check if power-on hours is above the threshold, returns bool."""
+ if self.nvme_attributes:
+ poh = self.nvme_attributes.get('power_on_hours', {}).get('raw', -1)
+ elif self.smart_attributes:
+ poh = self.smart_attributes.get(9, {}).get('raw', -1)
+
+ error_thresh = ATTRIBUTES['SMART'][9]['Error']
+ max_thresh = ATTRIBUTES['SMART'][9]['Maximum']
+
+ return error_thresh <= poh < max_thresh
+
def check_attributes(self):
"""Check NVMe / SMART attributes for errors, returns bool."""
attr_type = self.attr_type
@@ -1745,6 +1757,9 @@ def run_nvme_smart_tests(state, test, update_mode=False):
if test.failed and not update_mode:
for t in ['badblocks', 'I/O Benchmark']:
dev.disable_test(t, 'Denied')
+ if dev.is_aging() and not update_mode:
+ test.failed = True
+ test.update_status('FAIL')
# Done
update_progress_pane(state)
diff --git a/.bin/Scripts/settings/hw_diags.py b/.bin/Scripts/settings/hw_diags.py
index 04eb9faa..2d6104f3 100644
--- a/.bin/Scripts/settings/hw_diags.py
+++ b/.bin/Scripts/settings/hw_diags.py
@@ -76,12 +76,12 @@ ATTRIBUTES = {
'NVMe': {
'critical_warning': {'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, },
'media_errors': {'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, },
- 'power_on_hours': {'Critical': False, 'Ignore': True, 'Warning': 17532, 'Error': 26298, 'Maximum': None, },
+ 'power_on_hours': {'Critical': False, 'Ignore': True, 'Warning': 17532, 'Error': 26298, 'Maximum': 122724,},
'unsafe_shutdowns': {'Critical': False, 'Ignore': True, 'Warning': 1, 'Error': None, 'Maximum': None, },
},
'SMART': {
5: {'Hex': '05', 'Critical': True, 'Ignore': False, 'Warning': None, 'Error': 1, 'Maximum': None, },
- 9: {'Hex': '09', 'Critical': False, 'Ignore': True, 'Warning': 17532, 'Error': 26298, 'Maximum': None, },
+ 9: {'Hex': '09', 'Critical': False, 'Ignore': True, 'Warning': 17532, 'Error': 26298, 'Maximum': 122724,},
10: {'Hex': '10', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
184: {'Hex': 'B8', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
187: {'Hex': 'BB', 'Critical': False, 'Ignore': False, 'Warning': 1, 'Error': 10, 'Maximum': 10000, },
From f5d806a51accd60e64cf332cdcd2300e1c3ead9a Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Wed, 3 Jul 2019 20:16:45 -0600
Subject: [PATCH 54/63] Adjusted safety checks in ddrescue-tui
* Added NVMe/SMART check for destination
* Addresses issue #102
---
.bin/Scripts/functions/ddrescue.py | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/.bin/Scripts/functions/ddrescue.py b/.bin/Scripts/functions/ddrescue.py
index b507b127..ff843d06 100644
--- a/.bin/Scripts/functions/ddrescue.py
+++ b/.bin/Scripts/functions/ddrescue.py
@@ -290,10 +290,10 @@ class RecoveryState():
if source.is_dir():
raise GenericError('Invalid source "{}"'.format(
source.path))
- elif not dest.is_dev():
+ if not dest.is_dev():
raise GenericError('Invalid destination "{}"'.format(
dest.path))
- elif source.size > dest.size:
+ if source.size > dest.size:
raise GenericError(
'Destination is too small, refusing to continue.')
else:
@@ -301,13 +301,13 @@ class RecoveryState():
if not source.is_dev():
raise GenericError('Invalid source "{}"'.format(
source.path))
- elif not dest.is_dir():
+ if not dest.is_dir():
raise GenericError('Invalid destination "{}"'.format(
dest.path))
- elif (source.size * 1.2) > dest.size:
+ if (source.size * 1.2) > dest.size:
raise GenericError(
'Not enough free space, refusing to continue.')
- elif dest.fstype.lower() not in RECOMMENDED_FSTYPES:
+ if dest.fstype.lower() not in RECOMMENDED_FSTYPES:
print_error(
'Destination filesystem "{}" is not recommended.'.format(
dest.fstype.upper()))
@@ -316,13 +316,25 @@ class RecoveryState():
print_standard(' ')
if not ask('Proceed anyways? (Strongly discouraged)'):
raise GenericAbort()
- elif not is_writable_dir(dest):
+ if not is_writable_dir(dest):
raise GenericError(
'Destination is not writable, refusing to continue.')
- elif not is_writable_filesystem(dest):
+ if not is_writable_filesystem(dest):
raise GenericError(
'Destination is mounted read-only, refusing to continue.')
+ # Destination NVMe/SMART safety check
+ if dest.is_dev():
+ disk_obj = DiskObj(dest.path)
+ if disk_obj.nvme_attributes or disk_obj.smart_attributes:
+ if not disk_obj.check_attributes():
+ raise GenericError(
+ 'NVMe/SMART issue detected on destination, refusing to continue.')
+ else:
+ print_warning('No NVMe or SMART data available for destination')
+ if not ask('Proceed anyways?'):
+ raise GenericAbort()
+
# Safety checks passed
self.block_pairs.append(BlockPair(self.mode, source, dest))
From e614e08d0ca64edc2854715259b8c41cf4c22fb2 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Sun, 7 Jul 2019 16:40:07 -0600
Subject: [PATCH 55/63] Disable Chrome notifications for new profiles
* Addresses issue #101
---
.bin/Scripts/settings/setup.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.bin/Scripts/settings/setup.py b/.bin/Scripts/settings/setup.py
index 12d6cef1..a3b61d61 100644
--- a/.bin/Scripts/settings/setup.py
+++ b/.bin/Scripts/settings/setup.py
@@ -27,6 +27,10 @@ MOZILLA_FIREFOX_UBO_PATH = r'{}\{}\ublock_origin.xpi'.format(
os.environ.get('PROGRAMFILES'),
r'Mozilla Firefox\distribution\extensions')
SETTINGS_GOOGLE_CHROME = {
+ r'Software\Policies\Google\Chrome': {
+ 'DWORD Items': {'DefaultNotificationsSetting': 2},
+ # 1: Allow, 2: Don't allow, 3: Ask
+ },
r'Software\Google\Chrome\Extensions\cjpalhdlnbpafiamejdnhcphjbkeiagm': {
'SZ Items': {
'update_url': 'https://clients2.google.com/service/update2/crx'},
From f9a0a11ee8b6d1134796e719ccfffaa065b1dc76 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Sun, 7 Jul 2019 17:39:27 -0600
Subject: [PATCH 56/63] Fix waiting for AVRemover
---
.bin/Scripts/settings/launchers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.bin/Scripts/settings/launchers.py b/.bin/Scripts/settings/launchers.py
index 93097dcc..98640cd2 100755
--- a/.bin/Scripts/settings/launchers.py
+++ b/.bin/Scripts/settings/launchers.py
@@ -31,7 +31,7 @@ LAUNCHERS = {
r':AVCheck',
r'rem Run ESETs AV Removal tool for tech to review',
r'echo Please check for running AV',
- r'start "" "%AV_REMOVER%" /wait',
+ r'start "" /wait "%AV_REMOVER%"',
r'echo.',
r'echo Press Enter to Launch d7II...',
r'pause>nul',
From 8cb599ee60e4bf7e9bad5dc4edc4e16cf6f839f1 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Sun, 7 Jul 2019 17:58:44 -0600
Subject: [PATCH 57/63] Added CreateRestorePoint.cmd for use in d7II
* Addresses issue #104
---
.bin/d7ii/3rd Party Tools/CreateRestorePoint.cmd | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 .bin/d7ii/3rd Party Tools/CreateRestorePoint.cmd
diff --git a/.bin/d7ii/3rd Party Tools/CreateRestorePoint.cmd b/.bin/d7ii/3rd Party Tools/CreateRestorePoint.cmd
new file mode 100644
index 00000000..e89f24b3
--- /dev/null
+++ b/.bin/d7ii/3rd Party Tools/CreateRestorePoint.cmd
@@ -0,0 +1,11 @@
+@echo off
+
+setlocal
+
+rem Enable System Restore
+PowerShell -Command Enable-ComputerRestore -Drive %SYSTEMDRIVE%\
+
+rem Create Restore Point
+PowerShell -Command Checkpoint-Computer -Description 1201-d7II
+
+endlocal
\ No newline at end of file
From 90f0fb58d9ec83a72c27b413748d2f870cea57b0 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Sun, 7 Jul 2019 17:59:29 -0600
Subject: [PATCH 58/63] Updated d7II config
* Added CreateRestorePoint step
* Removed Python check since it's done before launching d7II
* Addresses issues #99 & #104
---
.bin/d7ii/Config/AltText.ini | 4 ++--
.bin/d7ii/Config/AppOverrides.ini | 4 ++--
.bin/d7ii/Config/Profiles/Default.cfg | 2 +-
.bin/d7ii/Config/SortOrder/MalwareBox2.cfg | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.bin/d7ii/Config/AltText.ini b/.bin/d7ii/Config/AltText.ini
index 0ce2c640..c46df099 100644
--- a/.bin/d7ii/Config/AltText.ini
+++ b/.bin/d7ii/Config/AltText.ini
@@ -5,7 +5,7 @@ Autoruns (Verify and Log)=Manages Startup Items
Google Chrome Software Removal Tool=Remove add-ons, extensions, toolbars, and other software that may interfere with the operation of Google Chrome.
VipreRescueScanner (Deep Scan)=Virus scanner (Designed for both the Malware Removal and the Offline Operations tab)
VipreRescueScanner (Quick Scan)=Virus scanner (Designed for both the Malware Removal and the Offline Operations tab)
-=Python Check
+=Create Restore Point
[ReportDesc]
Autoruns=Examined Windows startup items and removed unnecessary entries.
Autoruns_Copy=Examined Windows startup items and removed unnecessary entries.
@@ -36,4 +36,4 @@ VipreRescueScanner (Quick Scan)=Ran virus scans (Vipre)
22=Repaired the Windows Update services responsible for Windows Update functionality.
38=Performed repair routines to ensure the Winsock is operating properly.
83=Examined internet speed/bandwidth.
-=Python compatibility check
+=Create Restore Point
diff --git a/.bin/d7ii/Config/AppOverrides.ini b/.bin/d7ii/Config/AppOverrides.ini
index 2d55f1f7..0e07e944 100644
--- a/.bin/d7ii/Config/AppOverrides.ini
+++ b/.bin/d7ii/Config/AppOverrides.ini
@@ -36,7 +36,7 @@ EmailBeforeExecution=0
PriorAlert=0
[]
PostRunApp=
-AlwaysAttemptDownload=1
-DLafterXdays=.5
+AlwaysAttemptDownload=0
+DLafterXdays=5
EmailBeforeExecution=0
PriorAlert=0
diff --git a/.bin/d7ii/Config/Profiles/Default.cfg b/.bin/d7ii/Config/Profiles/Default.cfg
index ba51d4e8..e4dc26d6 100644
--- a/.bin/d7ii/Config/Profiles/Default.cfg
+++ b/.bin/d7ii/Config/Profiles/Default.cfg
@@ -782,7 +782,7 @@ WizardKit System Diagnostics=1
1=1
RKill (Auto)=1
Disable Windows Updates=1
-Python Check=1
+Create Restore Point=1
[Malware3]
ComboFix=0
ComboFix (Uninstall)=0
diff --git a/.bin/d7ii/Config/SortOrder/MalwareBox2.cfg b/.bin/d7ii/Config/SortOrder/MalwareBox2.cfg
index f26c90a5..ef117ee8 100644
--- a/.bin/d7ii/Config/SortOrder/MalwareBox2.cfg
+++ b/.bin/d7ii/Config/SortOrder/MalwareBox2.cfg
@@ -1 +1 @@
-Python Check|Disable Windows Updates|98|RKill (Auto)|Kaspersky TDSSKiller (Silent)|WizardKit System Diagnostics|34|Emsisoft a2cmd Deep Scan|HitmanPro|1|98|
+Create Restore Point|Disable Windows Updates|98|RKill (Auto)|Kaspersky TDSSKiller (Silent)|WizardKit System Diagnostics|34|Emsisoft a2cmd Deep Scan|HitmanPro|1|98|
From a732c3a9ff456580525b167a81be2ea5cdd33c53 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Mon, 8 Jul 2019 18:20:49 -0600
Subject: [PATCH 59/63] Post UNKNOWN CPU results in DIAG FAIL color
---
.bin/Scripts/functions/hw_diags.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/.bin/Scripts/functions/hw_diags.py b/.bin/Scripts/functions/hw_diags.py
index bf3a0c6c..ef2bf23b 100644
--- a/.bin/Scripts/functions/hw_diags.py
+++ b/.bin/Scripts/functions/hw_diags.py
@@ -1230,6 +1230,7 @@ def run_hw_tests(state):
cpu_failed = False
for test in state.cpu.tests.values():
cpu_failed = cpu_failed or test.failed
+ cpu_failed = cpu_failed or not test.passed
color_code = 'Diags FAIL' if cpu_failed else 'Diags'
state.ost.post_device_results(
state.cpu, state.ticket_id, state.ticket_name, color_code)
From 1d2ebf254e3728aa20af3e03cdcd83d1ef8507cb Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Mon, 8 Jul 2019 19:05:55 -0600
Subject: [PATCH 60/63] Bumped tool versions
---
.bin/Scripts/build_kit.ps1 | 8 ++++----
.bin/Scripts/settings/sources.py | 22 +++++++++++-----------
2 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/.bin/Scripts/build_kit.ps1 b/.bin/Scripts/build_kit.ps1
index 76e69966..6280b032 100644
--- a/.bin/Scripts/build_kit.ps1
+++ b/.bin/Scripts/build_kit.ps1
@@ -83,17 +83,17 @@ if ($MyInvocation.InvocationName -ne ".") {
DownloadFile -Path $Path -Name "7z-extra.7z" -Url "https://www.7-zip.org/a/7z1900-extra.7z"
# ConEmu
- $Url = "https://github.com/Maximus5/ConEmu/releases/download/v19.03.10/ConEmuPack.190310.7z"
+ $Url = "https://github.com/Maximus5/ConEmu/releases/download/v19.06.23/ConEmuPack.190623.7z"
DownloadFile -Path $Path -Name "ConEmuPack.7z" -Url $Url
# Notepad++
- $Url = "https://notepad-plus-plus.org/repository/7.x/7.6.4/npp.7.6.4.bin.minimalist.7z"
+ $Url = "https://notepad-plus-plus.org/repository/7.x/7.7.1/npp.7.7.1.bin.minimalist.7z"
DownloadFile -Path $Path -Name "npp.7z" -Url $Url
# Python
- $Url = "https://www.python.org/ftp/python/3.7.2/python-3.7.2.post1-embed-win32.zip"
+ $Url = "https://www.python.org/ftp/python/3.7.4/python-3.7.4-embed-win32.zip"
DownloadFile -Path $Path -Name "python32.zip" -Url $Url
- $Url = "https://www.python.org/ftp/python/3.7.2/python-3.7.2.post1-embed-amd64.zip"
+ $Url = "https://www.python.org/ftp/python/3.7.4/python-3.7.4-embed-amd64.zip"
DownloadFile -Path $Path -Name "python64.zip" -Url $Url
# Python: psutil
diff --git a/.bin/Scripts/settings/sources.py b/.bin/Scripts/settings/sources.py
index e1537f33..04e50758 100644
--- a/.bin/Scripts/settings/sources.py
+++ b/.bin/Scripts/settings/sources.py
@@ -3,12 +3,12 @@
# 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',
+ 'Adobe Reader DC': 'https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/1901220034/AcroRdrDC1901220034_en_US.exe',
'AdwCleaner': 'https://downloads.malwarebytes.com/file/adwcleaner',
- 'AIDA64': 'http://download.aida64.com/aida64engineer599.zip',
+ 'AIDA64': 'http://download.aida64.com/aida64engineer600.zip',
'aria2': 'https://github.com/aria2/aria2/releases/download/release-1.34.0/aria2-1.34.0-win-32bit-build1.zip',
'Autoruns': 'https://download.sysinternals.com/files/Autoruns.zip',
- 'BleachBit': 'https://download.bleachbit.org/BleachBit-2.0-portable.zip',
+ 'BleachBit': 'https://download.bleachbit.org/BleachBit-2.2-portable.zip',
'BlueScreenView32': 'http://www.nirsoft.net/utils/bluescreenview.zip',
'BlueScreenView64': 'http://www.nirsoft.net/utils/bluescreenview-x64.zip',
'Caffeine': 'http://www.zhornsoftware.co.uk/caffeine/caffeine.zip',
@@ -21,22 +21,22 @@ SOURCE_URLS = {
'ESET Online Scanner': 'https://download.eset.com/com/eset/tools/online_scanner/latest/esetonlinescanner_enu.exe',
'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': 'https://fastcopy.jp/archive/FastCopy380_installer.exe',
+ 'FastCopy': 'http://ftp.vector.co.jp/71/78/2323/FastCopy382_installer.exe',
'FurMark': 'https://geeks3d.com/dl/get/569',
- 'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/1709472/ublock_origin-1.18.6-an+fx.xpi',
+ 'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/3027669/ublock_origin-1.20.0-an+fx.xpi',
'HitmanPro32': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
- 'HWiNFO': 'http://files2.majorgeeks.com/377527622c5325acc1cb937fb149d0de922320c0/systeminfo/hwi_602.zip',
+ 'HWiNFO': 'http://files2.majorgeeks.com/8742c668ee52f7cbe5181d609ff800f3a37492c5/systeminfo/hwi_608.zip',
'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',
+ 'LibreOffice': 'https://download.documentfoundation.org/libreoffice/stable/6.2.5/win/x86_64/LibreOffice_6.2.5_Win_x64.msi',
'Linux Reader': 'https://www.diskinternals.com/download/Linux_Reader.exe',
'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',
- 'NotepadPlusPlus': 'https://notepad-plus-plus.org/repository/7.x/7.6.4/npp.7.6.4.bin.minimalist.7z',
- 'Office Deployment Tool': 'https://download.microsoft.com/download/2/7/A/27AF1BE6-DD20-4CB4-B154-EBAB8A7D4A7E/officedeploymenttool_11509-33604.exe',
+ 'NotepadPlusPlus': 'https://notepad-plus-plus.org/repository/7.x/7.7.1/npp.7.7.1.bin.minimalist.7z',
+ 'Office Deployment Tool': 'https://download.microsoft.com/download/2/7/A/27AF1BE6-DD20-4CB4-B154-EBAB8A7D4A7E/officedeploymenttool_11617-33601.exe',
'ProduKey32': 'http://www.nirsoft.net/utils/produkey.zip',
'ProduKey64': 'http://www.nirsoft.net/utils/produkey-x64.zip',
'PuTTY': 'https://the.earth.li/~sgtatham/putty/latest/w32/putty.zip',
@@ -45,14 +45,14 @@ SOURCE_URLS = {
'SDIO Themes': 'http://snappy-driver-installer.org/downloads/SDIO_Themes.zip',
'SDIO Torrent': 'http://snappy-driver-installer.org/downloads/SDIO_Update.torrent',
'ShutUp10': 'https://dl5.oo-software.com/files/ooshutup10/OOSU10.exe',
- 'smartmontools': 'https://700-105252244-gh.circle-artifacts.com/0/builds/smartmontools-win32-setup-7.1-r4914.exe',
+ 'smartmontools': 'https://738-105252244-gh.circle-artifacts.com/0/builds/smartmontools-win32-setup-7.1-r4934.exe',
'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.1-windows-i686-bin.zip',
'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.1-windows-x86_64-bin.zip',
'WinAIO Repair': 'http://www.tweaking.com/files/setups/tweaking.com_windows_repair_aio.zip',
'Winapp2': 'https://github.com/MoscaDotTo/Winapp2/archive/master.zip',
- 'WizTree': 'https://antibody-software.com/files/wiztree_3_28_portable.zip',
+ 'WizTree': 'https://antibody-software.com/files/wiztree_3_29_portable.zip',
'XMPlay 7z': 'https://support.xmplay.com/files/16/xmp-7z.zip?v=800962',
'XMPlay Game': 'https://support.xmplay.com/files/12/xmp-gme.zip?v=515637',
'XMPlay RAR': 'https://support.xmplay.com/files/16/xmp-rar.zip?v=409646',
From 3f467f6f1c7fe8ee48d5d7ef20ccf3eb7c7e9b3a Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Mon, 8 Jul 2019 23:39:44 -0600
Subject: [PATCH 61/63] Updated main.py
---
.bin/Scripts/settings/main.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.bin/Scripts/settings/main.py b/.bin/Scripts/settings/main.py
index 896d9c62..04e8858b 100644
--- a/.bin/Scripts/settings/main.py
+++ b/.bin/Scripts/settings/main.py
@@ -20,8 +20,8 @@ TECH_PASSWORD='Sorted1201'
# Root Certificate Authority
ROOT_CA_NAME='1201_Root_CA.crt'
# Server IP addresses
-OFFICE_SERVER_IP='10.11.1.20'
-QUICKBOOKS_SERVER_IP='10.11.1.20'
+OFFICE_SERVER_IP='10.120.1.15'
+QUICKBOOKS_SERVER_IP='10.120.1.15'
# Time Zones
LINUX_TIME_ZONE='America/Los_Angeles' # See 'timedatectl list-timezones' for valid values
WINDOWS_TIME_ZONE='Pacific Standard Time' # See 'tzutil /l' for valid values
From e526a6736f2d49e2a9e668c175e7df4a3ec91058 Mon Sep 17 00:00:00 2001
From: 2Shirt <2xShirt@gmail.com>
Date: Tue, 9 Jul 2019 01:10:19 -0600
Subject: [PATCH 62/63] New theme
---
Images/ConEmu.jpg | Bin 703256 -> 270260 bytes
Images/Linux.jpg | Bin 191170 -> 262629 bytes
Images/Pxelinux.jpg | Bin 71851 -> 60367 bytes
Images/Syslinux.jpg | Bin 71851 -> 60367 bytes
Images/WinPE.jpg | Bin 278234 -> 264985 bytes
Images/rEFInd.png | Bin 1264569 -> 1242888 bytes
6 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/Images/ConEmu.jpg b/Images/ConEmu.jpg
index 2d8ef42bfd6645278778a815b3e019016e9c542d..e4565baa138c9ef6432e1b0f8c8f73773d807924 100644
GIT binary patch
literal 270260
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