Merge branch 'auto-repair' into dev
This commit is contained in:
commit
461bc8ac9c
14 changed files with 1729 additions and 133 deletions
94
scripts/auto_repairs.py
Normal file
94
scripts/auto_repairs.py
Normal 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()
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
73
scripts/wk/cfg/tools.py
Normal 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.")
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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
185
scripts/wk/kit/tools.py
Normal 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.")
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
7
scripts/wk/repairs/__init__.py
Normal file
7
scripts/wk/repairs/__init__.py
Normal 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
1188
scripts/wk/repairs/win.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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."""
|
||||
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:
|
||||
obj = getattr(sys.modules[__name__], name, None)
|
||||
if obj:
|
||||
return obj
|
||||
|
||||
# Try builtin classes
|
||||
obj = getattr(sys.modules['builtins'], name)
|
||||
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='',
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue