# Migration to Python started #
* PoSH has an extreme slowdown for some systems while it runs an optimization
** pass for .NET on the first PoSH script execution.
** This is reason enough to move to an alternative.
* New additions:
* User Data Transfer script
* Will extract from a WIM or copy from a folder
* Uses wimlib-imagex for images and FastCopy for folders
* Removes undesired items after transfer/extraction
* HWiNFO
* Missing ODD repair registry patch
* Q-Dir
* SW Bundle install script
* ConEmu
* Moving back to ConEmu for better performance.
* Copy-WizardKit
* Now uses FastCopy
* functions.py
* Ported init.ps1 to Python using functions.py (from WinPE) as a base
* Launch.cmd
* Elevating programs/scripts now done using a temp VBScript file
* Can run Python scripts (using either the 32 or 64 bit runtime)
* transferred_keys.cmd
* Expanded searched paths
* Misc
* Lots of variables and files renamed
* Lots of hard-coded paths are now in variables
* Should only be set in scripts in %bin%\Scripts
* Moved a subset of the Diagnostics launchers to a new 'Extras' folder
* The launchers moved are those that are less-often used
* Refactored FindBin code to be more concise
* Renamed "KitDir" "ClientDir" to indicate that it is on the client's system
* Removed GeForce Experience launcher as it now requires an account
* Added link to NVIDIA's driver webpage to download the correct driver
* Removed AMD's Gaming Evolved launcher
* This is usually bundled with the GPU driver anyway
* Switched back to ConEmu
* Variable and script names are now more descriptive
* i.e. checklist -> final_checklist, and HH -> %kit_dir%
* (not happy with %kit_dir%, will probably change again)
368 lines
No EOL
15 KiB
Python
368 lines
No EOL
15 KiB
Python
# Wizard Kit: Menu
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import time
|
|
import traceback
|
|
import winreg
|
|
|
|
from functions import *
|
|
|
|
# Init
|
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
|
bin = os.path.abspath('..\\')
|
|
## Check bootup type
|
|
BOOT_TYPE = 'Legacy'
|
|
try:
|
|
reg_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'System\\CurrentControlSet\\Control')
|
|
reg_value = winreg.QueryValueEx(reg_key, 'PEFirmwareType')[0]
|
|
if reg_value == 2:
|
|
BOOT_TYPE = 'UEFI'
|
|
except:
|
|
BOOT_TYPE = 'Unknown'
|
|
|
|
class AbortException(Exception):
|
|
pass
|
|
|
|
def abort_to_main_menu(message='Returning to main menu...'):
|
|
print_warning(message)
|
|
pause('Press Enter to return to main menu... ')
|
|
raise AbortException
|
|
|
|
def menu_backup_imaging():
|
|
"""Take backup images of partition(s) in the WIM format and save them to a backup share"""
|
|
|
|
# Mount backup shares
|
|
os.system('cls')
|
|
mount_backup_shares()
|
|
|
|
# Set ticket number
|
|
ticket = None
|
|
while ticket is None:
|
|
tmp = input('Enter ticket number: ')
|
|
if re.match(r'^([0-9]+([-_]?\w+|))$', tmp):
|
|
ticket = tmp
|
|
|
|
# Select disk to backup
|
|
disk = select_disk('For which drive are we creating backups?')
|
|
if disk is None:
|
|
abort_to_main_menu()
|
|
|
|
# Get list of partitions that can't be imaged
|
|
bad_parts = [p['Number'] for p in disk['Partitions'] if 'Letter' not in p or re.search(r'(RAW|Unknown)', p['FileSystem'])]
|
|
|
|
# Bail if no partitions are found (that can be imaged)
|
|
num_parts = len(disk['Partitions'])
|
|
if num_parts == 0 or num_parts == len(bad_parts):
|
|
abort_to_main_menu(' No partitions can be imaged for the selected drive')
|
|
|
|
# Select destination
|
|
dest = select_destination()
|
|
if dest is None:
|
|
abort_to_main_menu('Aborting Backup Creation')
|
|
|
|
# List (and update) partition details for selected drive
|
|
os.system('cls')
|
|
print('Create Backup - Details:\n')
|
|
print(' Drive: {Size}\t[{Table}] ({Type}) {Name}\n'.format(**disk))
|
|
clobber_risk = 0
|
|
width=len(str(len(disk['Partitions'])))
|
|
for par in disk['Partitions']:
|
|
# Detail each partition
|
|
if par['Number'] in bad_parts:
|
|
print_warning(' * Partition {Number:>{width}}:\t{Size} {FileSystem}\t\t{q}{Name}{q}\t{Description} ({OS})'.format(
|
|
width=width,
|
|
q='"' if par['Name'] != '' else '',
|
|
**par))
|
|
else:
|
|
# Update info for WIM capture
|
|
par['ImageName'] = str(par['Name'])
|
|
if par['ImageName'] == '':
|
|
par['ImageName'] = 'Unknown'
|
|
if 'IP' in dest:
|
|
par['ImagePath'] = '\\\\{IP}\\{Share}\\{ticket}'.format(ticket=ticket, **dest)
|
|
else:
|
|
par['ImagePath'] = '{Letter}:\\{ticket}'.format(ticket=ticket, **dest)
|
|
par['ImageFile'] = '{Number}_{ImageName}'.format(**par)
|
|
par['ImageFile'] = '{fixed_name}.wim'.format(fixed_name=re.sub(r'\W', '_', par['ImageFile']))
|
|
|
|
# Check for existing backups
|
|
par['ImageExists'] = False
|
|
if os.path.exists('{ImagePath}\\{ImageFile}'.format(**par)):
|
|
par['ImageExists'] = True
|
|
clobber_risk += 1
|
|
print_info(' + Partition {Number:>{width}}:\t{Size} {FileSystem} (Used: {Used Space})\t{q}{Name}{q}'.format(
|
|
width=width,
|
|
q='"' if par['Name'] != '' else '',
|
|
**par))
|
|
else:
|
|
print(' Partition {Number:>{width}}:\t{Size} {FileSystem} (Used: {Used Space})\t{q}{Name}{q}'.format(
|
|
width=width,
|
|
q='"' if par['Name'] != '' else '',
|
|
**par))
|
|
print('') # Spacer
|
|
|
|
# Add warning about partition(s) that will be skipped
|
|
if len(bad_parts) > 1:
|
|
print_warning(' * Unable to backup these partitions')
|
|
elif len(bad_parts) == 1:
|
|
print_warning(' * Unable to backup this partition')
|
|
if clobber_risk > 1:
|
|
print_info(' + These partitions already have backup images on {Display Name}:'.format(**dest))
|
|
elif clobber_risk == 1:
|
|
print_info(' + This partition already has a backup image on {Display Name}:'.format(**dest))
|
|
if clobber_risk + len(bad_parts) > 1:
|
|
print_warning('\nIf you continue the partitions marked above will NOT be backed up.\n')
|
|
if clobber_risk + len(bad_parts) == 1:
|
|
print_warning('\nIf you continue the partition marked above will NOT be backed up.\n')
|
|
|
|
# (re)display the destination
|
|
print(' Destination:\t{name}\n'.format(name=dest.get('Display Name', dest['Name'])))
|
|
|
|
# Ask to proceed
|
|
if (not ask('Proceed with backup?')):
|
|
abort_to_main_menu('Aborting Backup Creation')
|
|
|
|
# Backup partition(s)
|
|
print('\n\nStarting task.\n')
|
|
errors = False
|
|
for par in disk['Partitions']:
|
|
print(' Partition {Number} Backup...\t\t'.format(**par), end='', flush=True)
|
|
if par['Number'] in bad_parts:
|
|
print_warning('Skipped.')
|
|
else:
|
|
cmd = '{bin}\\wimlib\\wimlib-imagex capture {Letter}:\\ "{ImagePath}\\{ImageFile}" "{ImageName}" "{ImageName}" --compress=fast'.format(bin=bin, **par)
|
|
if par['ImageExists']:
|
|
print_warning('Skipped.')
|
|
else:
|
|
try:
|
|
os.makedirs('{ImagePath}'.format(**par), exist_ok=True)
|
|
run_program(cmd)
|
|
print_success('Complete.')
|
|
except subprocess.CalledProcessError as err:
|
|
print_error('Failed.')
|
|
errors = True
|
|
par['Error'] = err.stderr.decode().splitlines()
|
|
|
|
# Verify backup(s)
|
|
if len(par) - len(bad_parts) > 1:
|
|
print('\n\n Verifying backups\n')
|
|
else:
|
|
print('\n\n Verifying backup\n')
|
|
for par in disk['Partitions']:
|
|
if par['Number'] not in bad_parts:
|
|
print(' Partition {Number} Image...\t\t'.format(**par), end='', flush=True)
|
|
cmd = '{bin}\\wimlib\\wimlib-imagex verify "{ImagePath}\\{ImageFile}" --nocheck'.format(bin=bin, **par)
|
|
if not os.path.exists('{ImagePath}\\{ImageFile}'.format(**par)):
|
|
print_error('Missing.')
|
|
else:
|
|
try:
|
|
run_program(cmd)
|
|
print_success('OK.')
|
|
except subprocess.CalledProcessError as err:
|
|
print_error('Damaged.')
|
|
errors = True
|
|
par['Error'] = par.get('Error', []) + err.stderr.decode().splitlines()
|
|
|
|
# Print summary
|
|
if errors:
|
|
print_warning('\nErrors were encountered and are detailed below.')
|
|
for par in [p for p in disk['Partitions'] if 'Error' in p]:
|
|
print(' Partition {Number} Error:'.format(**par))
|
|
for line in par['Error']:
|
|
line = line.strip()
|
|
if line != '':
|
|
print_error('\t{line}'.format(line=line))
|
|
time.sleep(300)
|
|
else:
|
|
print_success('\nNo errors were encountered during imaging.')
|
|
time.sleep(10)
|
|
pause('\nPress Enter to return to main menu... ')
|
|
|
|
def menu_windows_setup():
|
|
"""Format a drive, partition for MBR or GPT, apply a Windows image, and rebuild the boot files"""
|
|
|
|
# Select the version of Windows to apply
|
|
os.system('cls')
|
|
selected_windows_version = select_windows_version()
|
|
if selected_windows_version is None:
|
|
abort_to_main_menu('Aborting Windows setup')
|
|
|
|
# Find Windows image
|
|
image = find_windows_image(selected_windows_version['ImageFile'])
|
|
if not any(image):
|
|
print_error('Failed to find Windows source image for {winver}'.format(winver=selected_windows_version['Name']))
|
|
abort_to_main_menu('Aborting Windows setup')
|
|
|
|
# Select drive to use as the OS drive
|
|
dest_drive = select_disk('To which drive are we installing Windows?')
|
|
if dest_drive is None:
|
|
abort_to_main_menu('Aborting Windows setup')
|
|
|
|
# Confirm drive format
|
|
print_warning('All data will be deleted from the following drive:')
|
|
print_warning('\t{Size}\t({Table}) {Name}'.format(**dest_drive))
|
|
if (not ask('Is this correct?')):
|
|
abort_to_main_menu('Aborting Windows setup')
|
|
|
|
# MBR/Legacy or GPT/UEFI?
|
|
use_gpt = True
|
|
if (BOOT_TYPE == 'UEFI'):
|
|
if (not ask("Setup drive using GPT (UEFI) layout?")):
|
|
use_gpt = False
|
|
else:
|
|
if (ask("Setup drive using MBR (legacy) layout?")):
|
|
use_gpt = False
|
|
|
|
# Safety check
|
|
print_warning('SAFETY CHECK\n')
|
|
print_error(' FORMATTING:\tDrive: {Size}\t[{Table}] ({Type}) {Name}'.format(**dest_drive))
|
|
if len(dest_drive['Partitions']) > 0:
|
|
width=len(str(len(dest_drive['Partitions'])))
|
|
for par in dest_drive['Partitions']:
|
|
if 'Letter' not in par or re.search(r'(RAW)', par['FileSystem']):
|
|
print_error('\t\tPartition {Number:>{width}}:\t{Size} {q}{Name}{q} ({FileSystem})\t\t{Description} ({OS})'.format(
|
|
width=width,
|
|
q='"' if par['Name'] != '' else '',
|
|
**par))
|
|
else:
|
|
print_error('\t\tPartition {Number:>{width}}:\t{Size} {q}{Name}{q} ({FileSystem})\t\t(Used space: {Used Space})'.format(
|
|
width=width,
|
|
q='"' if par['Name'] != '' else '',
|
|
**par))
|
|
else:
|
|
print_warning('\t\tNo partitions found')
|
|
if (use_gpt):
|
|
print(' Using: \tGPT (UEFI) layout')
|
|
else:
|
|
print(' Using: \tMBR (legacy) layout')
|
|
print_info(' Installing:\t{winver}'.format(winver=selected_windows_version['Name']))
|
|
if (not ask('\nIs this correct?')):
|
|
abort_to_main_menu('Aborting Windows setup')
|
|
|
|
# Release currently used volume letters (ensures that the drives will get S, T, & W as needed below)
|
|
remove_volume_letters(keep=image['Source'])
|
|
|
|
# Format and partition drive
|
|
if (use_gpt):
|
|
format_gpt(dest_drive, selected_windows_version['Family'])
|
|
else:
|
|
format_mbr(dest_drive)
|
|
|
|
# Apply Windows image
|
|
errors = False
|
|
print(' Applying image...')
|
|
cmd = '{bin}\\wimlib\\wimlib-imagex apply "{File}.{Ext}" "{ImageName}" W:\\ {Glob}'.format(bin=bin, **image, **selected_windows_version)
|
|
try:
|
|
run_program(cmd)
|
|
except subprocess.CalledProcessError:
|
|
errors = True
|
|
print_error('Failed to apply Windows image.')
|
|
|
|
# Create boot files
|
|
if not errors:
|
|
print(' Creating boot files...'.format(**selected_windows_version))
|
|
try:
|
|
run_program('bcdboot W:\\Windows /s S: /f ALL')
|
|
except subprocess.CalledProcessError:
|
|
errors = True
|
|
print_error('Failed to create boot files.')
|
|
if re.search(r'^(8|10)', selected_windows_version['Family']):
|
|
try:
|
|
run_program('W:\\Windows\\System32\\reagentc /setreimage /path T:\\Recovery\\WindowsRE /target W:\\Windows')
|
|
except subprocess.CalledProcessError:
|
|
# errors = True # Changed to warning.
|
|
print_warning('Failed to setup WindowsRE files.')
|
|
|
|
# Print summary
|
|
if errors:
|
|
print_warning('\nErrors were encountered during setup.')
|
|
time.sleep(300)
|
|
else:
|
|
print_success('\nNo errors were encountered during setup.')
|
|
time.sleep(10)
|
|
pause('\nPress Enter to return to main menu... ')
|
|
|
|
def menu_tools():
|
|
tools = [
|
|
{'Name': 'Blue Screen View', 'Folder': 'BlueScreenView', 'File': 'BlueScreenView.exe'},
|
|
{'Name': 'CPU-Z', 'Folder': 'CPU-Z', 'File': 'cpuz.exe'},
|
|
{'Name': 'Explorer++', 'Folder': 'Explorer++', 'File': 'Explorer++.exe'},
|
|
{'Name': 'Fast Copy', 'Folder': 'FastCopy', 'File': 'FastCopy.exe', 'Args': ['/log', '/logfile=X:\WK\Info\FastCopy.log', '/cmd=noexist_only', '/utf8', '/skip_empty_dir', '/linkdest', '/open_window', '/balloon=FALSE', r'/exclude=$RECYCLE.BIN;$Recycle.Bin;.AppleDB;.AppleDesktop;.AppleDouble;.com.apple.timemachine.supported;.dbfseventsd;.DocumentRevisions-V100*;.DS_Store;.fseventsd;.PKInstallSandboxManager;.Spotlight*;.SymAV*;.symSchedScanLockxz;.TemporaryItems;.Trash*;.vol;.VolumeIcon.icns;desktop.ini;Desktop?DB;Desktop?DF;hiberfil.sys;lost+found;Network?Trash?Folder;pagefile.sys;Recycled;RECYCLER;System?Volume?Information;Temporary?Items;Thumbs.db']},
|
|
{'Name': 'HWiNFO', 'Folder': 'HWiNFO', 'File': 'HWiNFO.exe'},
|
|
{'Name': 'HW Monitor', 'Folder': 'HWMonitor', 'File': 'HWMonitor.exe'},
|
|
{'Name': 'NT Password Editor', 'Folder': 'NT Password Editor', 'File': 'ntpwedit.exe'},
|
|
{'Name': 'Notepad2', 'Folder': 'Notepad2', 'File': 'Notepad2-Mod.exe'},
|
|
{'Name': 'PhotoRec', 'Folder': 'TestDisk', 'File': 'photorec_win.exe', 'Args': ['-new_console:n']},
|
|
{'Name': 'Prime95', 'Folder': 'Prime95', 'File': 'prime95.exe'},
|
|
{'Name': 'ProduKey', 'Folder': 'ProduKey', 'File': 'ProduKey.exe', 'Args': ['/external', '/ExtractEdition:1']},
|
|
{'Name': 'Q-Dir', 'Folder': 'Q-Dir', 'File': 'Q-Dir.exe'},
|
|
{'Name': 'TestDisk', 'Folder': 'TestDisk', 'File': 'testdisk_win.exe', 'Args': ['-new_console:n']},
|
|
]
|
|
actions = [
|
|
{'Name': 'Main Menu', 'Letter': 'M'},
|
|
]
|
|
|
|
# Menu loop
|
|
while True:
|
|
selection = menu_select('Tools Menu', tools, actions)
|
|
|
|
if (selection.isnumeric()):
|
|
tool = tools[int(selection)-1]
|
|
cmd = ['{bin}\\{folder}\\{file}'.format(bin=bin, folder=tool['Folder'], file=tool['File'])]
|
|
if tool['Name'] == 'Blue Screen View':
|
|
# Select path to scan
|
|
minidump_path = select_minidump_path()
|
|
if minidump_path is not None:
|
|
tool['Args'] = ['/MiniDumpFolder', minidump_path]
|
|
if 'Args' in tool:
|
|
cmd += tool['Args']
|
|
try:
|
|
subprocess.Popen(cmd)
|
|
except:
|
|
print_error('Failed to run {prog}'.format(prog=tool['Name']))
|
|
time.sleep(2)
|
|
elif (selection == 'M'):
|
|
break
|
|
|
|
def menu_main():
|
|
menus = [
|
|
{'Name': 'Create Backups', 'Menu': menu_backup_imaging},
|
|
{'Name': 'Install Windows', 'Menu': menu_windows_setup},
|
|
{'Name': 'Misc Tools', 'Menu': menu_tools},
|
|
]
|
|
actions = [
|
|
{'Name': 'Command Prompt', 'Letter': 'C'},
|
|
{'Name': 'Reboot', 'Letter': 'R'},
|
|
{'Name': 'Shutdown', 'Letter': 'S'},
|
|
]
|
|
|
|
# Main loop
|
|
while True:
|
|
selection = menu_select('Main Menu', menus, actions, secret_exit=True)
|
|
|
|
if (selection.isnumeric()):
|
|
try:
|
|
menus[int(selection)-1]['Menu']()
|
|
except AbortException:
|
|
pass
|
|
except:
|
|
print_error('Major exception in: {menu}'.format(menu=menus[int(selection)-1]['Name']))
|
|
print_warning(' Please let The Wizard know and he\'ll look into it (Please include the details below).')
|
|
print(traceback.print_exc())
|
|
print_info(' You can reboot and try again but if this crashes again an alternative approach is required.')
|
|
time.sleep(300)
|
|
pause('Press Enter to shutdown...')
|
|
run_program(['wpeutil', 'shutdown'])
|
|
elif (selection == 'C'):
|
|
run_program(['cmd', '-new_console:n'], check=False)
|
|
elif (selection == 'R'):
|
|
run_program(['wpeutil', 'reboot'])
|
|
elif (selection == 'S'):
|
|
run_program(['wpeutil', 'shutdown'])
|
|
else:
|
|
quit()
|
|
|
|
if __name__ == '__main__':
|
|
menu_main() |