Merge branch 'auto-repair' into dev

This commit is contained in:
2Shirt 2021-05-02 01:10:45 -06:00
commit 461bc8ac9c
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C
14 changed files with 1729 additions and 133 deletions

94
scripts/auto_repairs.py Normal file
View file

@ -0,0 +1,94 @@
"""Wizard Kit: Auto-Repair Tool"""
# vim: sts=2 sw=2 ts=2
import os
import sys
os.chdir(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.getcwd())
import wk # pylint: disable=wrong-import-position
# Classes
class MenuEntry():
# pylint: disable=too-few-public-methods
"""Simple class to allow cleaner code below."""
def __init__(self, name, function=None, **kwargs):
self.name = name
# Color reboot entries
if name == 'Reboot':
self.name = wk.std.color_string(
['Reboot', ' ', '(Forced)'],
['YELLOW', None, 'ORANGE'],
sep='',
)
# Set details
self.details = {
'Function': function,
'Selected': True,
**kwargs,
}
# STATIC VARIABLES
BASE_MENUS = {
'Groups': {
'Backup Settings': (
MenuEntry('Enable RegBack', 'auto_enable_regback'),
MenuEntry('Enable System Restore', 'auto_system_restore_enable'),
MenuEntry('Set System Restore Size', 'auto_system_restore_set_size'),
MenuEntry('Create System Restore', 'auto_system_restore_create'),
#MenuEntry('Backup Browsers', #TODO),
MenuEntry('Backup Power Plans', 'auto_backup_power_plans'),
MenuEntry('Backup Registry', 'auto_backup_registry'),
),
'Windows Repairs': (
MenuEntry('Disable Windows Updates', 'auto_windows_updates_disable'),
MenuEntry('Reset Windows Updates', 'auto_windows_updates_reset'),
MenuEntry('Reboot', 'auto_reboot'),
MenuEntry('CHKDSK', 'auto_chkdsk'),
MenuEntry('DISM RestoreHealth', 'auto_dism'),
MenuEntry('SFC Scan', 'auto_sfc'),
MenuEntry('Clear Proxy Settings', 'auto_reset_proxy'),
MenuEntry('Disable Pending Renames', 'auto_disable_pending_renames'),
MenuEntry('Registry Repairs', 'auto_repair_registry'),
MenuEntry('Reset UAC', 'auto_restore_uac_defaults'),
MenuEntry('Reset Windows Policies', 'auto_reset_windows_policies'),
),
'Malware Cleanup': (
MenuEntry('BleachBit', 'auto_bleachbit'),
MenuEntry('HitmanPro', 'auto_hitmanpro'),
MenuEntry('KVRT', 'auto_kvrt'),
MenuEntry('Windows Defender', 'auto_microsoft_defender'),
MenuEntry('Reboot', 'auto_reboot'),
),
'Manual Steps': (
MenuEntry('AdwCleaner', 'auto_adwcleaner'),
MenuEntry('IO Bit Uninstaller', 'auto_iobit_uninstaller'),
MenuEntry('Enable Windows Updates', 'auto_windows_updates_enable'),
),
},
'Options': (
MenuEntry('Kill Explorer'),
MenuEntry('Run RKill'),
MenuEntry('Run TDSSKiller (once)'),
MenuEntry('Sync Clock'),
MenuEntry('Use Autologon'),
),
'Actions': (
MenuEntry('Options'),
MenuEntry('Start', Separator=True),
MenuEntry('Quit'),
),
}
if __name__ == '__main__':
try:
wk.repairs.win.run_auto_repairs(BASE_MENUS)
except SystemExit:
raise
except: #pylint: disable=bare-except
wk.std.major_exception()

View file

@ -13,6 +13,7 @@ from wk import kit
from wk import log
from wk import net
from wk import os
from wk import repairs
from wk import std
from wk import sw
from wk import tmux

View file

@ -5,4 +5,5 @@ from wk.cfg import hw
from wk.cfg import log
from wk.cfg import main
from wk.cfg import net
from wk.cfg import tools
from wk.cfg import ufd

73
scripts/wk/cfg/tools.py Normal file
View file

@ -0,0 +1,73 @@
"""WizardKit: Config - Tools"""
# pylint: disable=line-too-long
# vim: sts=2 sw=2 ts=2
# Download frequency in days
DOWNLOAD_FREQUENCY = 7
# Sources
SOURCES = {
'Adobe Reader DC': 'https://ardownload2.adobe.com/pub/adobe/reader/win/AcrobatDC/2100120145/AcroRdrDC2100120145_en_US.exe',
'AdwCleaner': 'https://downloads.malwarebytes.com/file/adwcleaner',
'AIDA64': 'https://download.aida64.com/aida64engineer633.zip',
'aria2': 'https://github.com/aria2/aria2/releases/download/release-1.35.0/aria2-1.35.0-win-32bit-build1.zip',
'Autoruns': 'https://download.sysinternals.com/files/Autoruns.zip',
'BleachBit': 'https://download.bleachbit.org/BleachBit-4.2.0-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',
'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 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.1005.x86.en-US.zip',
'Everything64': 'https://www.voidtools.com/Everything-1.4.1.1005.x64.en-US.zip',
'FastCopy': 'https://ftp.vector.co.jp/73/10/2323/FastCopy392_installer.exe',
'FurMark': 'https://geeks3d.com/dl/get/569',
'Firefox uBO': 'https://addons.mozilla.org/firefox/downloads/file/3740966/ublock_origin-1.34.0-an+fx.xpi',
'HitmanPro': 'https://dl.surfright.nl/HitmanPro.exe',
'HitmanPro64': 'https://dl.surfright.nl/HitmanPro_x64.exe',
'HWiNFO': 'https://files1.majorgeeks.com/c8a055180587599139f8f454712dcc618cd1740e/systeminfo/hwi_702.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': 'https://devbuilds.s.kaspersky-labs.com/devbuilds/KVRT/latest/full/KVRT.exe',
'LibreOffice': 'https://download.documentfoundation.org/libreoffice/stable/7.1.2/win/x86_64/LibreOffice_7.1.2_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://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.portable.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',
'RegDelNull': 'https://download.sysinternals.com/files/Regdelnull.zip',
'RKill': 'https://download.bleepingcomputer.com/grinler/rkill.exe',
'Samsung Magician': 'https://s3.ap-northeast-2.amazonaws.com/global.semi.static/SAMSUNG_SSD_v5_3_0_181121/CD0C7CC1BE00525FAC4675B9E502899B41D5C3909ECE3AA2FB6B74A766B2A1EA/Samsung_Magician_Installer.zip',
'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://1278-105252244-gh.circle-artifacts.com/0/builds/smartmontools-win32-setup-7.3-r5216.exe',
'TDSSKiller': 'https://media.kaspersky.com/utilities/VirusUtilities/EN/tdsskiller.exe',
'TestDisk': 'https://www.cgsecurity.org/testdisk-7.2-WIP.win.zip',
'wimlib32': 'https://wimlib.net/downloads/wimlib-1.13.3-windows-i686-bin.zip',
'wimlib64': 'https://wimlib.net/downloads/wimlib-1.13.3-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://wiztreefree.com/files/wiztree_3_39_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',
'XMPlay WAModern': 'https://support.xmplay.com/files/10/WAModern.zip?v=207099',
'XMPlay': 'https://support.xmplay.com/files/20/xmplay383.zip?v=298195',
'XYplorerFree': 'https://www.xyplorer.com/download/xyplorer_free_noinstall.zip',
}
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -130,7 +130,7 @@ def get_json_from_command(cmd, check=True, encoding='utf-8', errors='ignore'):
return json_data
def get_procs(name, exact=True):
def get_procs(name, exact=True, try_again=True):
"""Get process object(s) based on name, returns list of proc objects."""
LOG.debug('name: %s, exact: %s', name, exact)
processes = []
@ -141,6 +141,11 @@ def get_procs(name, exact=True):
if re.search(regex, proc.name(), re.IGNORECASE):
processes.append(proc)
# Try again?
if not processes and try_again:
time.sleep(1)
processes = get_procs(name, exact, try_again=False)
# Done
return processes
@ -242,10 +247,10 @@ def wait_for_procs(name, exact=True, timeout=None):
"""Wait for all process matching name."""
LOG.debug('name: %s, exact: %s, timeout: %s', name, exact, timeout)
target_procs = get_procs(name, exact=exact)
results = psutil.wait_procs(target_procs, timeout=timeout)
procs = psutil.wait_procs(target_procs, timeout=timeout)
# Raise exception if necessary
if results[1]: # Alive processes
if procs[1]: # Alive processes
raise psutil.TimeoutExpired(name=name, seconds=timeout)

View file

@ -192,5 +192,11 @@ def recursive_copy(source, dest, overwrite=False):
raise FileExistsError(f'Refusing to delete file: {dest}')
def rename_item(path, new_path):
"""Rename item, returns pathlib.Path."""
path = pathlib.Path(path)
return path.rename(new_path)
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -3,5 +3,7 @@
import platform
from wk.kit import tools
if platform.system() == 'Linux':
from wk.kit import ufd

185
scripts/wk/kit/tools.py Normal file
View file

@ -0,0 +1,185 @@
"""WizardKit: Tool Functions"""
# vim: sts=2 sw=2 ts=2
from datetime import datetime, timedelta
import logging
import pathlib
import sys
import requests
from wk.cfg.main import ARCHIVE_PASSWORD
from wk.cfg.tools import SOURCES, DOWNLOAD_FREQUENCY
from wk.exe import popen_program, run_program
from wk.std import GenericError
# STATIC VARIABLES
ARCH = '64' if sys.maxsize > 2**32 else '32'
LOG = logging.getLogger(__name__)
# "GLOBAL" VARIABLES
CACHED_DIRS = {}
# Functions
def download_file(out_path, source_url, as_new=False, overwrite=False):
"""Download a file using requests, returns pathlib.Path."""
out_path = pathlib.Path(out_path).resolve()
if as_new:
out_path = out_path.with_suffix(f'{out_path.suffix}.new')
cursor_left = '\u001B[14D'
print(f'Downloading...{cursor_left}', end='', flush=True)
# Avoid clobbering
if out_path.exists() and not overwrite:
raise FileExistsError(f'Refusing to clobber {out_path}')
# Create destination directory
out_path.parent.mkdir(parents=True, exist_ok=True)
# Request download
response = requests.get(source_url, stream=True)
if not response.ok:
name = out_path.name
if as_new:
name = name[:-4]
LOG.error(
'Failed to download file (status %s): %s',
response.status_code, name,
)
raise GenericError(f'Failed to download file: {name}')
# Write to file
with open(out_path, 'wb') as _f:
for chunk in response.iter_content(chunk_size=128):
_f.write(chunk)
# Done
print(f' {cursor_left}', end='', flush=True)
return out_path
def download_tool(folder, name):
"""Download tool."""
out_path = find_kit_dir('.bin').joinpath(f'{folder}/{name}.exe')
up_to_date = False
# Check if tool is up to date
try:
ctime = datetime.fromtimestamp(out_path.stat().st_ctime)
up_to_date = datetime.now() - ctime < timedelta(days=DOWNLOAD_FREQUENCY)
except FileNotFoundError:
# Ignore - we'll download it below
pass
if out_path.exists() and up_to_date:
LOG.info('Skip downloading up-to-date tool: %s', name)
return
# Download
LOG.info('Downloading tool: %s', name)
source_url = SOURCES[name]
try:
new_file = download_file(out_path, source_url, as_new=True)
new_file.replace(out_path)
except GenericError:
# Ignore as long as there's still a version present
if not out_path.exists():
raise
def extract_archive(archive, out_path, *args, mode='x', silent=True):
"""Extract an archive to out_path."""
out_path = pathlib.Path(out_path).resolve()
out_path.parent.mkdir(parents=True, exist_ok=True)
cmd = [get_tool_path('7-Zip', '7za'), mode, archive, f'-o{out_path}', *args]
if silent:
cmd.extend(['-bso0', '-bse0', '-bsp0'])
# Extract
run_program(cmd)
def find_kit_dir(name=None):
"""Find folder in kit, returns pathlib.Path.
Search is performed in the script's path and then recursively upwards.
If name is given then search for that instead."""
cur_path = pathlib.Path(__file__).resolve().parent
search = name if name else '.bin'
# Search
if name in CACHED_DIRS:
return CACHED_DIRS[name]
while not cur_path.match(cur_path.anchor):
if cur_path.joinpath(search).exists():
break
cur_path = cur_path.parent
# Check
if cur_path.match(cur_path.anchor):
raise FileNotFoundError(f'Failed to find kit dir, {name=}')
if name:
cur_path = cur_path.joinpath(name)
# Done
CACHED_DIRS[name] = cur_path
return cur_path
def get_tool_path(folder, name, check=True):
"""Get tool path, returns pathlib.Path"""
bin_dir = find_kit_dir('.bin')
# "Search"
tool_path = bin_dir.joinpath(f'{folder}/{name}{ARCH}.exe')
if not tool_path.exists():
tool_path = tool_path.with_stem(name)
# Missing?
if check and not tool_path.exists():
raise FileNotFoundError(f'Failed to find tool, {folder=}, {name=}')
# Done
return tool_path
def run_tool(
folder, name, *run_args,
cbin=False, cwd=False, download=False, popen=False,
**run_kwargs,
):
"""Run tool from the kit or the Internet, returns proc obj.
proc will be either subprocess.CompletedProcess or subprocess.Popen."""
proc = None
# Extract from .cbin
if cbin:
extract_archive(
find_kit_dir('.cbin').joinpath(folder).with_suffix('.7z'),
find_kit_dir('.bin').joinpath(folder),
'-aos', f'-p{ARCHIVE_PASSWORD}',
)
# Download tool
if download:
download_tool(folder, name)
# Run
tool_path = get_tool_path(folder, name)
cmd = [tool_path, *run_args]
if cwd:
run_kwargs['cwd'] = tool_path.parent
if popen:
proc = popen_program(cmd, **run_kwargs)
else:
proc = run_program(cmd, check=False, **run_kwargs)
# Done
return proc
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -17,8 +17,7 @@ if os.name == 'nt':
# Example: "C:\WK\1955-11-05\WizardKit"
DEFAULT_LOG_DIR = (
f'{os.environ.get("SYSTEMDRIVE", "C:")}/'
f'{cfg.main.KIT_NAME_SHORT}/'
f'{time.strftime("%Y-%m-%d")}'
f'{cfg.main.KIT_NAME_SHORT}/Logs/'
)
else:
# Example: "/home/tech/Logs"

View file

@ -5,19 +5,24 @@ import logging
import os
import pathlib
import platform
import winreg
from contextlib import suppress
import psutil
try:
import winreg
except ImportError as err:
if platform.system() == 'Windows':
raise err
from wk.borrowed import acpi
from wk.exe import run_program
from wk.io import non_clobber_path
from wk.log import format_log_path
from wk.std import GenericError, GenericWarning, sleep
# STATIC VARIABLES
LOG = logging.getLogger(__name__)
CONEMU = 'ConEmuPID' in os.environ
KNOWN_DATA_TYPES = {
'BINARY': winreg.REG_BINARY,
'DWORD': winreg.REG_DWORD,
@ -56,7 +61,7 @@ REG_MSISERVER = r'HKLM\SYSTEM\CurrentControlSet\Control\SafeBoot\Network\MSIServ
SLMGR = pathlib.Path(f'{os.environ.get("SYSTEMROOT")}/System32/slmgr.vbs')
# Functions
# Activation Functions
def activate_with_bios():
"""Attempt to activate Windows with a key stored in the BIOS."""
# Code borrowed from https://github.com/aeruder/get_win8key
@ -96,36 +101,6 @@ def activate_with_bios():
raise GenericError('Activation Failed')
def disable_safemode():
"""Edit BCD to remove safeboot value."""
cmd = ['bcdedit', '/deletevalue', '{default}', 'safeboot']
run_program(cmd)
def disable_safemode_msi():
"""Disable MSI access under safemode."""
cmd = ['reg', 'delete', REG_MSISERVER, '/f']
run_program(cmd)
def enable_safemode():
"""Edit BCD to set safeboot as default."""
cmd = ['bcdedit', '/set', '{default}', 'safeboot', 'network']
run_program(cmd)
def enable_safemode_msi():
"""Enable MSI access under safemode."""
cmd = ['reg', 'add', REG_MSISERVER, '/f']
run_program(cmd)
cmd = [
'reg', 'add', REG_MSISERVER, '/ve',
'/t', 'REG_SZ',
'/d', 'Service', '/f',
]
run_program(cmd)
def get_activation_string():
"""Get activation status, returns str."""
cmd = ['cscript', '//nologo', SLMGR, '/xpr']
@ -144,75 +119,6 @@ def is_activated():
return act_str and 'permanent' in act_str
def run_chkdsk_offline():
"""Set filesystem 'dirty bit' to force a CHKDSK during startup."""
cmd = f'fsutil dirty set {os.environ.get("SYSTEMDRIVE")}'
proc = run_program(cmd.split(), check=False)
# Check result
if proc.returncode > 0:
raise GenericError('Failed to set dirty bit.')
def run_chkdsk_online():
"""Run CHKDSK in a split window.
NOTE: If run on Windows 8+ online repairs are attempted.
"""
cmd = ['CHKDSK', os.environ.get('SYSTEMDRIVE', 'C:')]
if OS_VERSION >= 8:
cmd.extend(['/scan', '/perf'])
log_path = format_log_path(log_name='CHKDSK', tool=True)
err_path = log_path.with_suffix('.err')
# Run scan
proc = run_program(cmd, check=False)
# Check result
if proc.returncode == 1:
raise GenericWarning('Repaired (or manually aborted)')
if proc.returncode > 1:
raise GenericError('Issue(s) detected')
# Save output
os.makedirs(log_path.parent, exist_ok=True)
with open(log_path, 'w') as _f:
_f.write(proc.stdout)
with open(err_path, 'w') as _f:
_f.write(proc.stderr)
def run_sfc_scan():
"""Run SFC and save results."""
cmd = ['sfc', '/scannow']
log_path = format_log_path(log_name='SFC', tool=True)
err_path = log_path.with_suffix('.err')
# Run SFC
proc = run_program(cmd, check=False, encoding='utf-16')
# Fix paths
log_path = non_clobber_path(log_path)
err_path = non_clobber_path(err_path)
# Save output
os.makedirs(log_path.parent, exist_ok=True)
with open(log_path, 'w') as _f:
_f.write(proc.stdout)
with open(err_path, 'w') as _f:
_f.write(proc.stderr)
# Check result
if 'did not find any integrity violations' in proc.stdout:
pass
elif 'successfully repaired' in proc.stdout:
raise GenericWarning('Repaired')
elif 'found corrupt files' in proc.stdout:
raise GenericError('Corruption detected')
else:
raise OSError
# Registry Functions
def reg_delete_key(hive, key, recurse=False):
# pylint: disable=raise-missing-from
@ -225,7 +131,7 @@ def reg_delete_key(hive, key, recurse=False):
# Delete subkeys first
if recurse:
with suppress(WindowsError), winreg.OpenKey(hive, key) as open_key:
with suppress(OSError), winreg.OpenKey(hive, key) as open_key:
while True:
subkey = fr'{key}\{winreg.EnumKey(open_key, 0)}'
reg_delete_key(hive, subkey, recurse=recurse)
@ -407,5 +313,106 @@ def reg_set_value(hive, key, name, data, data_type, option=None):
winreg.SetValue(hive, key, data_type, data)
# Safe Mode Functions
def disable_safemode():
"""Edit BCD to remove safeboot value."""
cmd = ['bcdedit', '/deletevalue', '{default}', 'safeboot']
run_program(cmd)
def disable_safemode_msi():
"""Disable MSI access under safemode."""
cmd = ['reg', 'delete', REG_MSISERVER, '/f']
run_program(cmd)
def enable_safemode():
"""Edit BCD to set safeboot as default."""
cmd = ['bcdedit', '/set', '{default}', 'safeboot', 'network']
run_program(cmd)
def enable_safemode_msi():
"""Enable MSI access under safemode."""
cmd = ['reg', 'add', REG_MSISERVER, '/f']
run_program(cmd)
cmd = [
'reg', 'add', REG_MSISERVER, '/ve',
'/t', 'REG_SZ',
'/d', 'Service', '/f',
]
run_program(cmd)
# Service Functions
def disable_service(service_name):
"""Set service startup to disabled."""
cmd = ['sc', 'config', service_name, 'start=', 'disabled']
run_program(cmd, check=False)
# Verify service was disabled
if get_service_start_type(service_name) != 'disabled':
raise GenericError(f'Failed to disable service {service_name}')
def enable_service(service_name, start_type='auto'):
"""Enable service by setting start type."""
cmd = ['sc', 'config', service_name, 'start=', start_type]
psutil_type = 'automatic'
if start_type == 'demand':
psutil_type = 'manual'
# Enable service
run_program(cmd, check=False)
# Verify service was enabled
if get_service_start_type(service_name) != psutil_type:
raise GenericError(f'Failed to enable service {service_name}')
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:
status = 'missing?'
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:
start_type = 'missing?'
return start_type
def start_service(service_name):
"""Stop service."""
cmd = ['net', 'start', service_name]
run_program(cmd, check=False)
# Verify service was started
if not get_service_status(service_name) in ('running', 'start_pending'):
raise GenericError(f'Failed to start service {service_name}')
def stop_service(service_name):
"""Stop service."""
cmd = ['net', 'stop', service_name]
run_program(cmd, check=False)
# Verify service was stopped
if not get_service_status(service_name) == 'stopped':
raise GenericError(f'Failed to stop service {service_name}')
if __name__ == '__main__':
print("This file is not meant to be called directly.")

View file

@ -0,0 +1,7 @@
"""WizardKit: repairs module init"""
# vim: sts=2 sw=2 ts=2
import platform
if platform.system() == 'Windows':
from wk.repairs import win

1188
scripts/wk/repairs/win.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -18,6 +18,7 @@ import time
import traceback
from collections import OrderedDict
from functools import cache
import requests
@ -381,12 +382,17 @@ class Menu():
# Done
return selected_entry
def simple_select(self, prompt='Please make a selection: '):
def simple_select(self, prompt='Please make a selection: ', update=True):
"""Display menu and make a single selection, returns tuple."""
self._update()
if update:
self._update()
user_selection = self._user_select(prompt)
return self._resolve_selection(user_selection)
def update(self):
"""Update menu with default settings."""
self._update()
class TryAndPrint():
# pylint: disable=too-many-instance-attributes
@ -455,7 +461,7 @@ class TryAndPrint():
# Done
return message
def _format_function_output(self, output):
def _format_function_output(self, output, msg_good):
"""Format function output for use in try_and_print(), returns str."""
LOG.debug('Formatting output: %s', output)
@ -468,6 +474,10 @@ class TryAndPrint():
if not isinstance(stdout, str):
stdout = stdout.decode('utf8')
output = stdout.strip().splitlines()
if not output:
# Going to treat these as successes (for now)
LOG.warning('Program output was empty, assuming good result.')
return color_string(msg_good, 'GREEN')
else:
try:
output = list(output)
@ -489,24 +499,41 @@ class TryAndPrint():
# Done
return result_msg
@cache
def _get_exception(self, name):
# pylint: disable=no-self-use
"""Get exception by name, returns exception object.
[Doctest]
>>> self._get_exception('AttributeError')
>>> t = TryAndPrint()
>>> t._get_exception('AttributeError')
<class 'AttributeError'>
>>> self._get_exception('CalledProcessError')
>>> t._get_exception('CalledProcessError')
<class 'subprocess.CalledProcessError'>
>>> self._get_exception('GenericError')
<class 'std.GenericError'>
>>> t._get_exception('GenericError')
<class 'wk.std.GenericError'>
"""
LOG.debug('Getting exception: %s', name)
try:
obj = getattr(sys.modules[__name__], name)
except AttributeError:
# Try builtin classes
obj = getattr(sys.modules['builtins'], name)
obj = getattr(sys.modules[__name__], name, None)
if obj:
return obj
# Try builtin classes
obj = getattr(sys.modules['builtins'], name, None)
if obj:
return obj
# Try all modules
for _mod in sys.modules:
obj = getattr(sys.modules[_mod], name, None)
if obj:
break
# Check if not found
if not obj:
raise AttributeError(f'Failed to find exception: {name}')
# Done
return obj
def _log_result(self, message, result_msg):
@ -560,12 +587,11 @@ class TryAndPrint():
verbose,
)
f_exception = None
catch_all = catch_all if catch_all else self.catch_all
msg_good = msg_good if msg_good else self.msg_good
output = None
result_msg = 'UNKNOWN'
if catch_all is None:
catch_all = self.catch_all
if verbose is None:
verbose = self.verbose
verbose = verbose if verbose else self.verbose
# Build exception tuples
e_exceptions = tuple(self._get_exception(e) for e in self.list_errors)
@ -600,17 +626,18 @@ class TryAndPrint():
else:
# Success
if output:
result_msg = self._format_function_output(output)
result_msg = self._format_function_output(output, msg_good)
print(result_msg)
else:
result_msg = msg_good if msg_good else self.msg_good
result_msg = msg_good
print_success(result_msg, log=False)
# Done
self._log_result(message, result_msg)
return {
'Failed': bool(f_exception),
'Exception': f_exception,
'Failed': bool(f_exception),
'Message': result_msg,
'Output': output,
}
@ -645,7 +672,6 @@ def ask(prompt='Kotaero!'):
def beep(repeat=1):
"""Play system bell with optional repeat."""
# TODO: Verify Windows functionality
while repeat >= 1:
# Print bell char without a newline
print('\a', end='', flush=True)
@ -977,11 +1003,13 @@ def set_title(title):
print_error('Setting the title is only supported under Windows.')
def show_data(message, data, color=None):
"""Display info using standard WIDTH and INDENT."""
def show_data(message, data, color=None, indent=None, width=None):
"""Display info using default or provided indent and width."""
colors = (None, color if color else None)
indent = INDENT if indent is None else indent
width = WIDTH if width is None else width
print_colored(
(f'{" "*INDENT}{message:<{WIDTH}}', data),
(f'{" "*indent}{message:<{width}}', data),
colors,
log=True,
sep='',

View file

@ -89,7 +89,7 @@
<value name="ProcessNewConArg" type="hex" data="01"/>
<value name="ProcessCmdStart" type="hex" data="00"/>
<value name="ProcessCtrlZ" type="hex" data="00"/>
<value name="SuppressBells" type="hex" data="01"/>
<value name="SuppressBells" type="hex" data="00"/>
<value name="ConsoleExceptionHandler" type="hex" data="00"/>
<value name="UseClink" type="hex" data="01"/>
<value name="StoreTaskbarkTasks" type="hex" data="01"/>