From 4ed08ebb9b617a3a21e418e418941d10a03ebf85 Mon Sep 17 00:00:00 2001 From: Alan Mason <1923621+2Shirt@users.noreply.github.com> Date: Fri, 24 Nov 2017 20:54:40 -0800 Subject: [PATCH] 2016-02: Retroactive Updates --- System32/Winpeshl.ini | 5 + System32/menu.cmd | 9 +- WK/ConEmu/ConEmu.xml | 643 ++++++++++++++++++++++++++++++ WK/{ => Explorer++}/config.xml | 3 +- WK/FastCopy.ini | 165 -------- WK/{ => HWMonitor}/hwmonitorw.ini | 0 WK/{ => Notepad2}/Notepad2.ini | 0 WK/Scripts/WK.ps1 | 631 ++++------------------------- WK/Scripts/imaging.ps1 | 463 +++++++++++++++++++++ WK/Scripts/init.ps1 | 91 +++++ WK/Scripts/servers.ps1 | 85 ++++ make-cd.cmd | 7 +- make.cmd | 37 +- 13 files changed, 1399 insertions(+), 740 deletions(-) create mode 100644 System32/Winpeshl.ini create mode 100644 WK/ConEmu/ConEmu.xml rename WK/{ => Explorer++}/config.xml (95%) delete mode 100644 WK/FastCopy.ini rename WK/{ => HWMonitor}/hwmonitorw.ini (100%) rename WK/{ => Notepad2}/Notepad2.ini (100%) create mode 100644 WK/Scripts/imaging.ps1 create mode 100644 WK/Scripts/servers.ps1 diff --git a/System32/Winpeshl.ini b/System32/Winpeshl.ini new file mode 100644 index 0000000..313d0a7 --- /dev/null +++ b/System32/Winpeshl.ini @@ -0,0 +1,5 @@ +[LaunchApp] +[LaunchApps] +wpeinit +wpeutil updatebootinfo +"%SystemDrive%\WK\ConEmu\ConEmu64.exe", /cmd PowerShell -ExecutionPolicy Bypass "%SystemDrive%\WK\Scripts\WK.ps1" -new_console:n \ No newline at end of file diff --git a/System32/menu.cmd b/System32/menu.cmd index 2de9884..eb9c80b 100644 --- a/System32/menu.cmd +++ b/System32/menu.cmd @@ -12,7 +12,7 @@ for %%f in (%*) do ( ) :LaunchMenu -PowerShell -ExecutionPolicy Bypass %systemdrive%\WK\Scripts\WK.ps1 +"%SystemDrive%\WK\ConEmu\ConEmu64.exe" /cmd PowerShell -ExecutionPolicy Bypass "%SystemDrive%\WK\Scripts\WK.ps1" -new_console:n goto Done :Abort @@ -27,10 +27,5 @@ goto Exit :Exit echo. -rem echo Press any key to exit... -rem pause>nul popd -endlocal -cls -echo Careful now... -echo. \ No newline at end of file +endlocal \ No newline at end of file diff --git a/WK/ConEmu/ConEmu.xml b/WK/ConEmu/ConEmu.xml new file mode 100644 index 0000000..26a3611 --- /dev/null +++ b/WK/ConEmu/ConEmu.xml @@ -0,0 +1,643 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WK/config.xml b/WK/Explorer++/config.xml similarity index 95% rename from WK/config.xml rename to WK/Explorer++/config.xml index 90a1914..342f0de 100644 --- a/WK/config.xml +++ b/WK/Explorer++/config.xml @@ -68,7 +68,7 @@ 4 - + @@ -108,5 +108,6 @@ + diff --git a/WK/FastCopy.ini b/WK/FastCopy.ini deleted file mode 100644 index 6251717..0000000 --- a/WK/FastCopy.ini +++ /dev/null @@ -1,165 +0,0 @@ -[main] -bufsize="128" -max_transize="16" -nonbuf_minsize_ntfs2="64" -nonbuf_minsize_fat="128" -is_readosbuf="0" -max_history="10" -default_copy_mode="1" -skip_empty_dir="1" -ignore_error="1" -estimate_mode="0" -disk_mode="0" -is_toplevel="0" -is_errlog="1" -is_utf8log="1" -filelog_mode="0" -aclerr_log="0" -streamerr_log="0" -is_samedir_rename="1" -shext_autoclose="1" -shext_tasktray="0" -shext_dd_noconfirm="0" -shext_right_noconfirm="0" -exec_confirm="0" -force_start="0" -lcid="-1" -speed_level="11" -overwrite_del="0" -acl="0" -stream="0" -verify="0" -nsa_del="0" -deldir_with_filter="0" -move_attr="0" -serial_move="0" -serial_verify_move="0" -reparse2="1" -extend_filter="0" -taskbarMode="0" -infoSpan="2" -win_pos="-10000,-10000,-10000,-10000" -driveMap="" -[src_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[dst_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[del_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[include_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[exclude_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[fromdate_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[todate_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[minsize_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[maxsize_history] -0="" -1="" -2="" -3="" -4="" -5="" -6="" -7="" -8="" -9="" -[finaction_0] -title="Normal" -sound="" -cmd="" -shutdown_time="-1" -flags="1" -[finaction_1] -title="Standby" -sound="" -cmd="" -shutdown_time="60" -flags="65" -[finaction_2] -title="Hibernate" -sound="" -cmd="" -shutdown_time="60" -flags="129" -[finaction_3] -title="Shutdown" -sound="" -cmd="" -shutdown_time="60" -flags="257" diff --git a/WK/hwmonitorw.ini b/WK/HWMonitor/hwmonitorw.ini similarity index 100% rename from WK/hwmonitorw.ini rename to WK/HWMonitor/hwmonitorw.ini diff --git a/WK/Notepad2.ini b/WK/Notepad2/Notepad2.ini similarity index 100% rename from WK/Notepad2.ini rename to WK/Notepad2/Notepad2.ini diff --git a/WK/Scripts/WK.ps1 b/WK/Scripts/WK.ps1 index e063361..f39f397 100644 --- a/WK/Scripts/WK.ps1 +++ b/WK/Scripts/WK.ps1 @@ -4,571 +4,100 @@ $wd = $(Split-Path $MyInvocation.MyCommand.Path) pushd "$wd" . .\init.ps1 +. .\servers.ps1 +. .\imaging.ps1 clear $host.UI.RawUI.WindowTitle = "WK PE Tool" $logpath = "$WKPath\Info\$date" md "$logpath" 2>&1 | Out-Null $log = "$logpath\winpe.log" -$source_server = "10.0.0.10" -$backup_servers = @( - @{ "ip"="10.0.0.10"; - "letter"="Z"; - "name"="ServerOne"; - "path"="Backups"}, - @{ "ip"="10.0.0.11"; - "name"="ServerTwo"; - "letter"="Y"; - "path"="Backups"} - ) -$backup_user = "backup" -$backup_pass = "Abracadabra" # Functions -function apply-image { - Param([string]$image, [int]$index) - $path = "" - $split_image = $false - $split_image_pattern = "" - - # Check for local source - $volumes = Get-Volume | Where-Object {$_.Size -ne 0 -and $_.DriveLetter -match '^[C-Z]$'} - foreach ($v in $volumes) { - $letter = $v.DriveLetter + ":" - if (Test-Path "$letter\sources\$image.wim") { - $path = "$letter\sources\$image.wim" - } elseif (Test-Path "$letter\sources\$image.swm") { - $path = "$letter\sources\$image.swm" - $split_image = $true - $split_image_pattern = "$letter\sources\$image*.swm" - } - } - - # Check for remote source (if necessary) - if ($path -match '^$') { - net use z: "\\$source_server\Windows" /user:guest notarealpassword - if (Test-Path "Z:\$image.wim") { - $path = "Z:\$image.wim" - } elseif (Test-Path "Z:\$image.swm") { - $path = "Z:\$image.swm" - } - } - - # Expand Image - if ($path -match '\.wim$') { - Expand-WindowsImage -ImagePath "$path" -Index $index -ApplyPath 'W:\' - } elseif ($path -match '\.swm$') { - Expand-WindowsImage -ImagePath "$path" -Index $index -ApplyPath 'W:\' -SplitImageFilePattern "$split_image_pattern" - } else { - net use z: /delete - throw "Source image not found." - } - net use z: /delete -} -function format-gpt { - Param($dest_disk) - wk-write "Drive will use a GPT (UEFI) layout." - - # Double-check we have the right drive - ## I don't trust the order will be the same for diskpart & PS Storage Cmdlets - $_sel_uid = $dest_disk.Guid - if ($dest_disk.PartitionStyle -imatch "MBR") { - # MBR disks don't have GUIDs and use the signature in hex instead - $_sel_uid = "{0:x}" -f $dest_disk.Signature - } - $diskpart_script = "select disk {0}`r`n" -f $dest_disk.DiskNumber - $diskpart_script += "uniqueid disk" - Out-File -encoding 'UTF8' -filepath "$wd\diskpart.script" -inputobject $diskpart_script - Start-Process "diskpart" -argumentlist @("/s", "$wd\diskpart.script") -wait -nonewwindow -PassThru -RedirectStandardOutput "$wd\drive_uid" | Out-Null - if (!(Get-Content "$wd\drive_uid" | Where-Object {$_ -imatch $_sel_uid})) { - # GUIDs do not match - wk-error "Diskpart failed to select the same disk for formatting, aborting setup." - wk-warn "This system requires manual formatting & setup" - wk-write "" - throw "Failed to format disk" - } else { - wk-write ("Selecting Disk {0} ({1})" -f $dest_disk.DiskNumber, $_sel_uid) - } - - # Generate Diskpart script and execute - ## NOTE 1: PS Storage Cmdlets can't be used; See Keith Garner's response here: - ## https://social.technet.microsoft.com/Forums/en-US/9d78da31-557f-4408-89e0-a1603f7ebe0d - ## - ## NOTE 2: This overwrites existing diskpart.script file without confirmation. - $diskpart_script = "select disk {0}`r`n" -f $dest_disk.DiskNumber - $diskpart_script += "clean`r`n" - $diskpart_script += "convert gpt`r`n" - - # 1. Windows RE tools partition (Windows 8+) - if ($dest_windows_version.Name -match '^Windows (8|10)') { - $diskpart_script += "create partition primary size=300`r`n" - $diskpart_script += "format quick fs=ntfs label='Windows RE tools'`r`n" - $diskpart_script += "assign letter='T'`r`n" - $diskpart_script += "set id='de94bba4-06d1-4d40-a16a-bfd50179d6ac'`r`n" - $diskpart_script += "gpt attributes=0x8000000000000001`r`n" - } - - # 2. System partition - $diskpart_script += "create partition efi size=260`r`n" - ## NOTE: Allows for Advanced Format 4Kn drives - $diskpart_script += "format quick fs=fat32 label='System'`r`n" - $diskpart_script += "assign letter='S'`r`n" - - # 3. Microsoft Reserved (MSR) partition - $diskpart_script += "create partition msr size=128`r`n" - - # 4. Windows partition - $diskpart_script += "create partition primary`r`n" - $diskpart_script += "format quick fs=ntfs label='Windows'`r`n" - $diskpart_script += "assign letter='W'`r`n" - - # Run script - Out-File -encoding 'UTF8' -filepath "$wd\diskpart.script" -inputobject $diskpart_script - Start-Process "diskpart" -argumentlist @("/s", "$wd\diskpart.script") -wait -nonewwindow -} -function format-mbr { - Param($dest_disk) - wk-write "Drive will use a MBR (legacy) layout." - - if ($dest_disk.PartitionStyle -notmatch '^RAW$') { - # Only clean if necessary - Clear-Disk $dest_disk.DiskNumber -RemoveData -RemoveOEM -Confirm:$false - } - Initialize-Disk $dest_disk.DiskNumber -PartitionStyle 'MBR' - New-Partition -DiskNumber $dest_disk.DiskNumber -Size 100Mb -DriveLetter 'S' -IsActive:$true - New-Partition -DiskNumber $dest_disk.DiskNumber -UseMaximumSize -DriveLetter 'W' -IsActive:$false - Format-Volume -DriveLetter 'S' -FileSystem 'NTFS' -NewFileSystemLabel 'System Reserved' - Format-Volume -DriveLetter 'W' -FileSystem 'NTFS' -NewFileSystemLabel 'Windows' -} -function select-disk { - $_skipped_parts = 0 - # Check if any source drives were detected - $disks = Get-Disk | Where-Object {$_.Size -ne 0} | Sort-Object -Property "Number" - if ($disks.count -eq 0) { - wk-error "No suitable source drives were detected." - return $false - } elseif ($disks.count -eq $null) { - # Assuming only one disk is available - $answer = $disks.DiskNumber - } else { - # Build source menu - $menu_imaging_source = "For which drive are we creating backup image(s)?`r`n`r`n" - $valid_answers = @("M", "m") - foreach ($_ in $disks) { - $valid_answers += $_.DiskNumber - $menu_imaging_source += "{0}: {1,4:N0} Gb`t[{2}] ({3}) {4}`r`n" -f $_.DiskNumber, ($_.Size / 1GB), $_.PartitionStyle, $_.BusType, $_.FriendlyName - } - $menu_imaging_source += "`r`n" - $menu_imaging_source += "M: Main Menu`r`n" - $menu_imaging_source += "`r`n" - $menu_imaging_source += "Please make a selection`r`n" - - # Select source - do { - clear - $answer = read-host -prompt $menu_imaging_source - } until ($valid_answers -contains $answer) - } - - if ($answer -imatch '^\d+$') { - # Valid disk selected - clear - $_d = Get-Disk -number $answer - $_disk_details = "Disk:`t{0,4:N0} Gb`t[{1}] ({2}) {3}" -f ($_d.Size / 1GB), $_d.PartitionStyle, $_d.BusType, $_d.FriendlyName - wk-write "$_disk_details" - wk-write "Partition(s):" - - # Print partition info - $partitions = Get-Partition -DiskNumber $_d.DiskNumber - foreach ($_p in $partitions) { - # Assign letter - Add-PartitionAccessPath -DiskNumber $_d.DiskNumber -PartitionNumber $_p.PartitionNumber -AssignDriveLetter 2>&1 | Out-Null - - # Update partition info - $_p = Get-Partition -DiskNumber $_d.DiskNumber -PartitionNumber $_p.PartitionNumber - $_v = Get-Volume -Partition $_p - - # Set size label - $_size = (human-size $_p.size 0) - if ($_v -ne $null) { - $_used = (human-size ($_v.Size - $_v.SizeRemaining) 0) - } - - # Print partition info - if ($_p.AccessPaths -eq $null) { - # No drive letter - $_msg = " *{0:N0}:`t ({1}) {2}" -f $_p.PartitionNumber, $_size, $_p.Type - wk-error "$_msg" - $_skipped_parts += 1 - } else { - # Has drive letter - $_path = $_p.AccessPaths | Where-Object {$_ -imatch '^\w:\\$'} - $_label = " {0}" -f $_p.Type - if ($_v -and $_v.FileSystemLabel -ne "") { - $_label = '"{0}"' -f $_v.FileSystemLabel - } - $_msg = " {0:N0}:`t{1} ({2,6}) {3} ({4} used)" -f $_p.PartitionNumber, $_path, $_size, $_label, $_used - wk-write "$_msg" - } - } - if ($_skipped_parts -gt 0) { - wk-warn " *`tUnable to backup these partition(s)" - } - wk-write "" - if (ask " Backup these partition(s)?") { - return $_d - } else { - return $false - } - } - - return $answer -} -function select-server { - # Build server menu - $avail_servers = @(gdr | Where-Object {$_.DisplayRoot -imatch '\\\\'}) - if ($avail_servers.count -eq 0) { - wk-error "No suitable backup servers were detected." - return $false - } - $menu_imaging_server = "Where are we saving the backup image(s)?`r`n`r`n" - $valid_answers = @("M", "m") - for ($i=0; $i -lt $avail_servers.length; $i++) { - $valid_answers += ($i + 1) - $menu_imaging_server += ("{0}: {1} ({2:N2} Gb free)`r`n" -f ($i + 1), $avail_servers[$i].Description, ($avail_servers[$i].Free / 1Gb)) - } - $menu_imaging_server += "`r`n" - $menu_imaging_server += "M: Main Menu`r`n" - $menu_imaging_server += "`r`n" - $menu_imaging_server += "Please make a selection`r`n" - - # Select server - do { -# clear - $answer = read-host -prompt $menu_imaging_server - } until ($valid_answers -contains $answer) - - if ($answer -imatch '^\d+$') { - $answer -= 1 - return $avail_servers[$answer] - } - return $answer -} function wk-exit { - popd - if ($answer -match 'R') { - #pause "Press Enter to Reboot... " - wpeutil reboot - } elseif ($answer -match 'S') { - #pause "Press Enter to Shutdown... " - wpeutil shutdown + param([string]$action) + switch ($action) { + 'Q' {PowerShell -ExecutionPolicy Bypass; break} + 'R' {wpeutil reboot; break} + 'S' {wpeutil shutdown; break} + default {throw} } exit 0 } -function mount-servers { - # Mount servers - wk-write "Connecting to backup server(s)" - foreach ($_server in $backup_servers) { - if (test-connection $_server.ip -count 3 -quiet) { - try { - $_path = "\\{0}\{1}" -f $_server.ip, $_server.path - $_drive = "{0}:" -f $_server.letter - net use $_drive "$_path" /user:$backup_user $backup_pass | Out-Null - wk-write ("`t{0} server: mounted" -f $_server.name) - - # Add friendly description - $_regex = "^{0}$" -f $_server.letter - (gdr | Where-Object {$_.Name -imatch $_regex}).Description = $_server.name - } catch { - wk-warn ("`t{0} server: failed" -f $_server.name) - } - } else { - wk-warn ("`t{0} server: timed-out" -f $_server.name) - } - } -} -function unmount-servers { - # Unmount servers - wk-write "Disconnecting from backup server(s)" - $mounted_servers = @(gdr | Where-Object {$_.DisplayRoot -imatch '\\\\'}) - foreach ($_server in $mounted_servers) { - try { - $_drive = "{0}:" -f $_server.Name - net use $_drive /delete | Out-Null - #wk-warn ("`t{0} server: unmounted" -f $_server.name) - wk-warn "`tServer: unmounted" - } catch { - #wk-warn ("`t{0} server: failed" -f $_server.name) - wk-warn "`tServer: failed" - } - } -} -function menu-imaging { - wk-write "Drive Imaging" - wk-write "" - - ## WARNING - wk-warn "WARNING: This section is experimental" - pause - ## WARNING - - # Service Order - $menu_service_order += "Please enter the service order`r`n" - do { - clear - $service_order = read-host -prompt $menu_service_order - } until ($service_order -imatch '^\d[\w\-]+$') - - # Select Disk - $disk = select-disk - if (!($disk)) { - # No drives detected or user aborted - wk-warn "Drive Imaging aborted." - wk-write "" - pause "Press Enter to return to main menu... " - return $false - } elseif ($disk -imatch '^M$') { - # User selected to return to the menu - return - } - wk-write "" - - # Mount server(s) - mount-servers - - # Select Server - $server = select-server - if (!($server)) { - # No servers detected - wk-warn "Drive Imaging aborted." - wk-write "" - pause "Press Enter to return to main menu... " - return $false - } elseif ($server -imatch '^M$') { - # User selected to return to the menu - return - } - wk-write "" - wk-write ("Saving partition backups to: {0}" -f $server.Description) - wk-write "" - - # Backup partitions - $partitions = Get-Partition -DiskNumber $disk.DiskNumber - foreach ($_p in $partitions) { - $_v = Get-Volume -Partition $_p - - $_name = "{0}" -f $_p.PartitionNumber - if ($_v -and $_v.FileSystemLabel -ne "") { - $_name += "_{0}" -f $_v.FileSystemLabel - } else { - $_name += "_{0}" -f $_p.Type - } - - $_imagepath = "{0}{1}" -f $server.Root, $service_order - $_imagefile = "{0}{1}\{2}.wim" -f $server.Root, $service_order, $_name - - if ($_p.AccessPaths -ne $null) { - $_capturepath = $_p.AccessPaths | Where-Object {$_ -imatch '^\w:\\$'} - - # Take image - wk-write (" Imaging partition {0} --> `"{1}`"" -f $_p.PartitionNumber, $_imagefile) - if (!(Test-Path "$_imagepath")) { - mkdir "$_imagepath" | out-null - } - New-WindowsImage -ImagePath "$_imagefile" -CapturePath "$_capturepath" -Name "$_name" -CompressionType "fast" | out-null - - # Verify image - ## Code borrowed from: https://stackoverflow/a/10262275 - $pinfo = New-Object System.Diagnostics.ProcessStartInfo - $pinfo.FileName = "$WKPath\7z.exe" - $pinfo.RedirectStandardError = $true - $pinfo.RedirectStandardOutput = $true - $pinfo.UseShellExecute = $false - $pinfo.Arguments = 't "{0}"' -f $_imagefile - $p = New-Object System.Diagnostics.Process - $p.StartInfo = $pinfo - $p.Start() | Out-Null - write-host " Verifying . . . " -NoNewline - $p.WaitForExit() - if ($p.ExitCode -eq 0) { - write-host "Complete." -foreground "green" - } else { - write-host "Failed." -foreground "red" - } - } - } - - # Unmount server(s) - unmount-servers - pause "Press Enter to return to main menu... " -} -function menu-setup { - wk-write "Windows Setup" - wk-write "" - - # Check if any destination drives were detected - $disks = Get-Disk | Where-Object {$_.Size -ne 0 -and $_.BusType -inotmatch 'USB'} - if ($disks.count -eq 0) { - wk-error "No suitable destination drives were detected." - pause "Press Enter to return to main menu... " $True - return $false - } - - # Build windows menu - $windows_versions = @( - @{Name="Windows 7 Home Premium"; ImageFile="Win7"; Index=1}, - @{Name="Windows 7 Professional"; ImageFile="Win7"; Index=2}, - @{Name="Windows 7 Ultimate"; ImageFile="Win7"; Index=3}, - @{Name="Windows 8.1"; ImageFile="Win8"; Index=1}, - @{Name="Windows 8.1 Pro"; ImageFile="Win8"; Index=2}, - @{Name="Windows 10 Home"; ImageFile="Win10"; Index=1}, - @{Name="Windows 10 Pro"; ImageFile="Win10"; Index=2} - ) - $menu_setup_windows = "Which version of Windows are we installing?`r`n`r`n" - $valid_answers = @("M", "m") - for ($i=0; $i -lt $windows_versions.length; $i++) { - $valid_answers += ($i + 1) - $menu_setup_windows += "{0}: {1}`r`n" -f ($i + 1), $windows_versions[$i].Name - } - $menu_setup_windows += "`r`n" - $menu_setup_windows += "M: Main Menu`r`n" - $menu_setup_windows += "`r`n" - $menu_setup_windows += "Please make a selection`r`n" - - # Select Windows version - do { - clear - $answer = read-host -prompt $menu_setup_windows - } until ($valid_answers -contains $answer) - - if ($answer -imatch '^M$') { - # Exit if requested - return - } else { - $answer -= 1 - $dest_windows_version = $windows_versions[$answer] - } - - # Build disk menu - $menu_setup_disk = "To which drive are we installing {0}?`r`n`r`n" -f $dest_windows_version.Name - $valid_answers = @("M", "m") - foreach ($_ in $disks) { - $valid_answers += $_.DiskNumber - $menu_setup_disk += "{0}: {1:N0} Gb`t({2}) {3}`r`n" -f $_.DiskNumber, ($_.Size / 1GB), $_.PartitionStyle, $_.FriendlyName - } - $menu_setup_disk += "`r`n" - $menu_setup_disk += "M: Main Menu`r`n" - $menu_setup_disk += "`r`n" - $menu_setup_disk += "Please make a selection`r`n" - - # Select disk - do { - clear - $answer = read-host -prompt $menu_setup_disk - } until ($valid_answers -contains $answer) - - if ($answer -imatch '^M$') { - # Exit if requested - return - } else { - # Double check before deleting data - $dest_disk = $disks | Where-Object {$_.DiskNumber -eq $answer} - wk-warn "All data will be deleted from the following drive:" - wk-warn ("`t{0:N0} Gb`t({1}) {2}`r`n" -f ($dest_disk.Size / 1GB), $dest_disk.PartitionStyle, $dest_disk.FriendlyName) - if (ask ("Proceed and install {0}?" -f $dest_windows_version.Name)) { - wk-warn "SAFTEY CHECK:" - wk-write (" Installing:`t{0}" -f $dest_windows_version.Name) - wk-error (" And ERASING:`tDisk {0} - {1:N0} Gb`t({2}) {3}`r`n" -f $dest_disk.DiskNumber, ($dest_disk.Size / 1GB), $dest_disk.PartitionStyle, $dest_disk.FriendlyName) - if (ask "Is this correct?") { - ## WARNING - wk-warn "WARNING: This section is experimental" - ## WARNING - ## Here be dragons - - try { - # Select UEFI or BIOS - if ($dest_windows_version.Name -match '^Windows 7') { - if (ask "Setup drive using MBR (legacy) layout?") { - format-mbr $dest_disk - } else { - format-gpt $dest_disk - } - } elseif ($dest_windows_version.Name -match '^Windows (8|10)') { - if (ask "Setup drive using GPT (UEFI) layout?") { - format-gpt $dest_disk - } else { - format-mbr $dest_disk - } - } - - # Apply image - apply-image $dest_windows_version.ImageFile $dest_windows_version.Index - - # Create boot files (copies files for both Legacy and UEFI) - bcdboot W:\Windows /s S: /f ALL - if ($dest_windows_version.Name -match '^Windows (8|10)') { - W:\Windows\System32\reagentc /setreimage /path T:\Recovery\WindowsRE /target W:\Windows - } - - # Reboot - wk-write "Windows Setup complete." - wk-write "" - return 0 - } catch { - wk-error "$Error" - wk-error "Windows Setup aborted." - wk-write "" - pause "Press Enter to return to main menu... " - return $false - } - } else { - wk-error "Windows Setup aborted." - wk-write "" - pause "Press Enter to return to main menu... " - } - } else { - wk-error "Windows Setup aborted." - wk-write "" - pause "Press Enter to return to main menu... " - } - } -} function menu-tools { - wk-write "Misc Tools" - wk-write "" - wk-warn "Be careful." - Start-Process "$WKPath\explorer++" -argumentlist @("$WKPath") - wk-exit + # Avail tools + $tools = @( + @{Name="Blue Screen View"; Folder="BlueScreenView"; File="BlueScreenView64.exe"}, + @{Name="Explorer++"; Folder="Explorer++"; File="Explorer++64.exe"}, + @{Name="Fast Copy"; Folder="FastCopy"; File="FastCopy64.exe"; Args=@('/cmd=noexist_only', '/utf8', '/skip_empty_dir', '/linkdest', '/exclude="desktop.ini;Thumbs.db"')}, + @{Name="HW Monitor"; Folder="HWMonitor"; File="HWMonitor64.exe"}, + @{Name="NT Password Editor"; Folder="NT Password Editor"; File="ntpwedit64.exe"}, + @{Name="Notepad2"; Folder="Notepad2"; File="Notepad2-Mod64.exe"}, + @{Name="Prime95"; Folder="Prime95"; File="prime95.exe"}, + @{Name="PhotoRec (GUI)"; Folder="TestDisk"; File="qphotorec_win.exe"}, + @{Name="PhotoRec (CLI)"; Folder="TestDisk"; File="photorec_win.exe"}, + @{Name="TestDisk (CLI)"; Folder="TestDisk"; File="testdisk_win.exe"} + ) + + # Build menu + $selection = $null + $actions = @(@{Name="Main Menu"; Letter="M"}) + + # Run Loop + $_done = $false + do { + $selection = (menu-select "Tools Menu" $tools $actions) + + if ($selection -imatch '^M$') { + # User selected to return to the menu + return $false + } elseif ($selection -inotmatch '^\d+$') { + # This shouldn't happen? + throw + } else { + $selection -= 1 + $path = "{0}\{1}" -f $WKPath, $tools[$selection].Folder + if ($tools[$selection].ContainsKey("Args")) { + Start-Process $tools[$selection].File -ArgumentList $tools[$selection].Args -WorkingDirectory $path + } else { + Start-Process $tools[$selection].File -WorkingDirectory $path + } + } + } until ($_done) } function menu-main { - $answered = $false - $menu_main = @" -WK WinPE Tools - -1: Drive Imaging -2: Windows Setup -3: Misc Tools - -Q: Quit -R: Reboot -S: Shutdown - -Please make a selection -"@ - - do { - clear - $answer = read-host -prompt $menu_main - } until ($answer -imatch '^[123QRS]$') - clear - - if ($answer.GetType().Name -match "String") { - $answer = $answer.ToUpper() - } - switch ($answer) { - 1 {menu-imaging; break} - 2 {menu-setup; break} - 3 {menu-tools; break} - default {wk-exit} + # Build menu + $selection = $null + $menus = @( + @{Name="Drive Imaging"; Menu="menu-imaging"} + @{Name="Windows Setup"; Menu="menu-setup"} + @{Name="Misc Tools"; Menu="menu-tools"} + ) + $actions = @( + @{Name="Command Prompt"; Letter="C"} + @{Name="PowerShell"; Letter="P"} + @{Name="Reboot"; Letter="R"} + @{Name="Shutdown"; Letter="S"} + ) + + # Show Menu + $selection = (menu-select "Main Menu" $menus $actions -SecretExit $true) + + if ($selection -imatch '^C$') { + Start-Process "$windir\System32\cmd.exe" -argumentlist @("-new_console:n") -WorkingDirectory "$WKPath" + return + } elseif ($selection -imatch '^P$') { + Start-Process "$windir\System32\WindowsPowerShell\v1.0\powershell.exe" -argumentlist @("-ExecutionPolicy", "Bypass", "-new_console:n") -WorkingDirectory "$WKPath" + return + } elseif ($selection -imatch '^[QRS]$') { + wk-exit $selection + return + } elseif ($selection -inotmatch '^\d+$') { + # This shouldn't happen? + throw + } else { + # Launch sub-menu + $selection -= 1 + & $menus[$selection].Menu } } diff --git a/WK/Scripts/imaging.ps1 b/WK/Scripts/imaging.ps1 new file mode 100644 index 0000000..a1033dc --- /dev/null +++ b/WK/Scripts/imaging.ps1 @@ -0,0 +1,463 @@ +# WK imaging functions + +## Init ## +$wd = $(Split-Path $MyInvocation.MyCommand.Path) +pushd "$wd" +. .\init.ps1 + +# Functions +function apply-image { + # Apply a Windows image to W:\ + Param([string]$image, [string]$name) + $path = "" + $split_image = $false + $split_image_pattern = "" + + # Check for local source + $volumes = @(Get-Volume | Where-Object {$_.Size -ne 0 -and $_.DriveLetter -imatch '^[C-Z]$'}) + foreach ($v in $volumes) { + $letter = $v.DriveLetter + ":" + if (Test-Path "$letter\sources\$image.wim") { + $path = "$letter\sources\$image.wim" + } elseif (Test-Path "$letter\sources\$image.esd") { + $path = "$letter\sources\$image.wim" + } elseif (Test-Path "$letter\sources\$image.swm") { + $path = "$letter\sources\$image.swm" + $split_image = $true + $split_image_pattern = "$letter\sources\$image*.swm" + } + } + + # Check for remote source (if necessary) + if ($path -imatch '^$') { + # Temporarily set path to network source + $path = "\\$source_server\Windows\$image" + wk-warn "Searching for network source" + if (Test-Path "$path.wim") { + $path = "$path.wim" + } elseif (Test-Path "$path.esd") { + $path = "$path.swm" + } elseif (Test-Path "$path.swm") { + $path = "$path.swm" + $split_image = $true + $split_image_pattern = "$path*.swm" + } else { + # Revert to empty path if nothing found. + $path = "" + } + } + + # Expand Image + if ($path -imatch 'Win\d+\.(esd|wim)$') { + wk-write " Applying image..." + Expand-WindowsImage -ImagePath "$path" -Name "$name" -ApplyPath 'W:\' | out-null + } elseif ($path -imatch 'Win\d+\.swm$') { + wk-write " Applying split-image..." + Expand-WindowsImage -ImagePath "$path" -Name "$name" -ApplyPath 'W:\' -SplitImageFilePattern "$split_image_pattern" | out-null + } else { + wk-error "Image not found." + throw + } +} +function format-gpt { + Param($dest_disk) + wk-write "Drive will use a GPT (UEFI) layout." + + # Double-check we have the right drive + ## I don't trust the order will be the same for diskpart & PS Storage Cmdlets + $_sel_uid = $dest_disk.Guid + if ($dest_disk.PartitionStyle -imatch "MBR") { + # MBR disks don't have GUIDs and use the signature in hex instead + $_sel_uid = "{0:x}" -f $dest_disk.Signature + } + $diskpart_script = "select disk {0}`r`n" -f $dest_disk.DiskNumber + $diskpart_script += "uniqueid disk" + Out-File -encoding 'UTF8' -filepath "$wd\diskpart.script" -inputobject $diskpart_script + Start-Process "diskpart" -argumentlist @("/s", "$wd\diskpart.script") -wait -nonewwindow -PassThru -RedirectStandardOutput "$wd\drive_uid" | Out-Null + if (!(Get-Content "$wd\drive_uid" | Where-Object {$_ -imatch $_sel_uid})) { + # GUIDs do not match + wk-error "Diskpart failed to select the same disk for formatting, aborting setup." + wk-warn "This system requires manual formatting & setup" + wk-write "" + throw "Failed to format disk" + } else { + wk-write ("Selecting Disk {0} ({1})" -f $dest_disk.DiskNumber, $_sel_uid) + } + + # Generate Diskpart script and execute + ## NOTE 1: PS Storage Cmdlets can't be used; See Keith Garner's response here: + ## https://social.technet.microsoft.com/Forums/en-US/9d78da31-557f-4408-89e0-a1603f7ebe0d + ## + ## NOTE 2: This overwrites existing diskpart.script file without confirmation. + $diskpart_script = "select disk {0}`r`n" -f $dest_disk.DiskNumber + $diskpart_script += "clean`r`n" + $diskpart_script += "convert gpt`r`n" + + # 1. Windows RE tools partition (Windows 8+) + if ($dest_windows_version.Name -imatch '^Windows (8|10)') { + $diskpart_script += "create partition primary size=300`r`n" + $diskpart_script += "format quick fs=ntfs label='Windows RE tools'`r`n" + $diskpart_script += "assign letter='T'`r`n" + $diskpart_script += "set id='de94bba4-06d1-4d40-a16a-bfd50179d6ac'`r`n" + $diskpart_script += "gpt attributes=0x8000000000000001`r`n" + } + + # 2. System partition + $diskpart_script += "create partition efi size=260`r`n" + ## NOTE: Allows for Advanced Format 4Kn drives + $diskpart_script += "format quick fs=fat32 label='System'`r`n" + $diskpart_script += "assign letter='S'`r`n" + + # 3. Microsoft Reserved (MSR) partition + $diskpart_script += "create partition msr size=128`r`n" + + # 4. Windows partition + $diskpart_script += "create partition primary`r`n" + $diskpart_script += "format quick fs=ntfs label='Windows'`r`n" + $diskpart_script += "assign letter='W'`r`n" + + # Run script + Out-File -encoding 'UTF8' -filepath "$wd\diskpart.script" -inputobject $diskpart_script + Start-Process "diskpart" -argumentlist @("/s", "$wd\diskpart.script") -wait -nonewwindow | out-null +} +function format-mbr { + Param($dest_disk) + wk-write "Drive will use a MBR (legacy) layout." + + if ($dest_disk.PartitionStyle -inotmatch '^RAW$') { + # Only clean if necessary + clear-Disk $dest_disk.DiskNumber -RemoveData -RemoveOEM -Confirm:$false | out-null + } + Initialize-Disk $dest_disk.DiskNumber -PartitionStyle 'MBR' | out-null + New-Partition -DiskNumber $dest_disk.DiskNumber -Size 100Mb -DriveLetter 'S' -IsActive:$true | out-null + New-Partition -DiskNumber $dest_disk.DiskNumber -UseMaximumSize -DriveLetter 'W' -IsActive:$false | out-null + Format-Volume -DriveLetter 'S' -FileSystem 'NTFS' -NewFileSystemLabel 'System Reserved' | out-null + Format-Volume -DriveLetter 'W' -FileSystem 'NTFS' -NewFileSystemLabel 'Windows' | out-null +} +function select-disk { + param([string]$title, [bool]$skip_usb=$false) + $_skipped_parts = 0 + + # Get Disk(s) + if ($skip_usb) { + $disks = @(Get-Disk | Where-Object {$_.Size -ne 0 -and $_.BusType -inotmatch 'USB'} | Sort-Object -Property "Number") + } else { + $disks = @(Get-Disk | Where-Object {$_.Size -ne 0} | Sort-Object -Property "Number") + } + + # Check if any drives were detected + if ($disks.count -eq 0) { + wk-error "No suitable drives were detected." + return $false + } + + # Get selection + $selection = $null + $main_set = @() + if ($disks.count -eq 1) { + # Only one disk is available + $selection = $disks[0] + } else { + # Multiple options. Build and use menu + foreach ($_ in $disks) { + $_entry = "{0}`t[{1}] ({2}) {3}" -f (human-size $_.Size 0), $_.PartitionStyle, $_.BusType, $_.FriendlyName + $main_set += @{Name=$_entry} + } + $actions = @(@{Name="Main Menu"; Letter="M"}) + $selection = (menu-select $title $main_set $actions) + } + + if ($selection -imatch '^\d+$') { + $selection -= 1 + return $disks[$selection] + } else { + return $selection + } +} +function menu-imaging { + wk-write "Drive Imaging" + wk-write "" + + ## WARNING + wk-warn "WARNING: This section is experimental" + pause + ## WARNING + + # Service Order + $menu_service_order += "Please enter the service order`r`n" + do { + clear + $service_order = read-host -prompt $menu_service_order + } until ($service_order -imatch '^\d[\w\-]+$') + + # Select Disk + $disk = (select-disk "For which drive are we creating backup image(s)?") + + if (!($disk)) { + # No drives detected or user aborted + wk-warn "Drive Imaging aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } elseif ($disk -imatch '^M$') { + # User selected to return to the menu + return $false + } elseif ($disk.DiskNumber -imatch '^\d+$') { + # Valid disk selected + clear + wk-write ("Disk:`t{0}`t[{1}] ({2}) {3}" -f (human-size $disk.Size 0), $disk.PartitionStyle, $disk.BusType, $disk.FriendlyName) + wk-write "Partition(s):" + + # Print partition info + $partitions = Get-Partition -DiskNumber $disk.DiskNumber + $_skipped_parts = 0 + foreach ($_p in $partitions) { + # Assign letter + Add-PartitionAccessPath -DiskNumber $disk.DiskNumber -PartitionNumber $_p.PartitionNumber -AssignDriveLetter 2>&1 | Out-Null + + # Update partition info + $_p = Get-Partition -DiskNumber $disk.DiskNumber -PartitionNumber $_p.PartitionNumber + $_v = Get-Volume -Partition $_p + + # Set size label + $_size = (human-size $_p.size 0) + $_used = "" + if ($_v) { + $_used = "({0} used)" -f (human-size ($_v.Size - $_v.SizeRemaining) 0) + } + + # Print partition info + if ($_p.AccessPaths) { + # Has drive letter + $_path = $_p.AccessPaths | Where-Object {$_ -imatch '^\w:\\$'} + $_label = " {0}" -f $_p.Type + if ($_v -and $_v.FileSystemLabel -ne "") { + $_label = '"{0}"' -f $_v.FileSystemLabel + } + $_msg = " {0:N0}:`t{1} ({2,6}) {3} {4}" -f $_p.PartitionNumber, $_path, $_size, $_label, $_used + wk-write "$_msg" + } else { + # No drive letter + $_msg = " *{0:N0}:`t ({1}) {2}" -f $_p.PartitionNumber, $_size, $_p.Type + wk-error "$_msg" + $_skipped_parts += 1 + } + } + if ($_skipped_parts -gt 0) { + wk-warn " *`tUnable to backup these partition(s)" + } + wk-write "" + if (!(ask " Backup these partition(s)?")) { + wk-warn "Drive Imaging aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } + } + wk-write "" + + # Mount server(s) + mount-servers + + # Select Server + $server = (select-server) + if (!($server)) { + # No servers detected + wk-warn "Drive Imaging aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } elseif ($server -imatch '^M$') { + # User selected to return to the menu + return + } + wk-write "" + wk-write ("Saving partition backups to: {0}" -f $server.Description) + wk-write "" + + # Backup partitions + $partitions = Get-Partition -DiskNumber $disk.DiskNumber + foreach ($_p in $partitions) { + $_v = Get-Volume -Partition $_p + + $_name = "{0}" -f $_p.PartitionNumber + if ($_v -and $_v.FileSystemLabel -ne "") { + $_name += "_{0}" -f $_v.FileSystemLabel + } else { + $_name += "_{0}" -f $_p.Type + } + # Sanitize the name + $_name = $_name -replace '\s', '_' + + $_imagepath = "{0}{1}" -f $server.Root, $service_order + $_imagefile = "{0}{1}\{2}.wim" -f $server.Root, $service_order, $_name + + if ($_p.AccessPaths -ne $null) { + # Avoid unwanted clobbering + if (Test-Path "$_imagefile") { + if (!(ask ("Overwrite backup image: {0}" -f $_imagefile))) { + wk-warn "Drive Imaging aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } + } + $_capturedir = $_p.AccessPaths | Where-Object {$_ -imatch '^\w:\\$'} + + # Take image + wk-write (" Imaging partition {0} --> `"{1}`"" -f $_p.PartitionNumber, $_imagefile) + if (!(Test-Path "$_imagepath")) { + mkdir "$_imagepath" | out-null + } + $_dism_args = @( + '/Capture-Image', + '/ImageFile:$_imagefile', + '/CaptureDir:$_capturedir', + '/Name:$_name', + '/Compress:fast', + '/Quiet') + Start-Process "$windir\System32\Dism.exe" -ArgumentList $_dism_args -NoNewWindow -Wait | out-null + + ## The following command fails to capture OS partitions consitantly. Until this is fixed I will use DISM directly (as above). + #New-WindowsImage -ImagePath "$_imagefile" -CapturePath "$_capturedir" -Name "$_name" -CompressionType "fast" | out-null + + # Verify image + ## Code borrowed from: https://stackoverflow/a/10262275 + $pinfo = New-Object System.Diagnostics.ProcessStartInfo + $pinfo.FileName = "$WKPath\7-Zip\7z.exe" + $pinfo.RedirectStandardError = $true + $pinfo.RedirectStandardOutput = $true + $pinfo.UseShellExecute = $false + $pinfo.Arguments = 't "{0}"' -f $_imagefile + $p = New-Object System.Diagnostics.Process + $p.StartInfo = $pinfo + $p.Start() | Out-Null + write-host " Verifying . . . " -NoNewline + $p.WaitForExit() + if ($p.ExitCode -eq 0) { + write-host "Complete." -foreground "green" + } else { + write-host "Failed." -foreground "red" + } + } + } + + # Unmount server(s) + unmount-servers + pause "Press Enter to return to main menu... " +} +function menu-setup { + # Select Disk + $dest_disk = (select-disk "To which drive are we installing Windows?" -skip_usb=$true) + + if (!($dest_disk)) { + # No drives detected or user aborted + wk-warn "Windows Setup aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } elseif ($dest_disk -imatch '^M$') { + # User selected to return to the menu + return $false + } elseif ($dest_disk.DiskNumber -inotmatch '^\d+$') { + # This shouldn't happen? + throw + } else { + wk-warn "All data will be deleted from the following drive:" + wk-warn ("`t{0}`t({1}) {2}`r`n" -f (human-size $dest_disk.Size 0), $dest_disk.PartitionStyle, $dest_disk.FriendlyName) + if (!(ask "Proceed and install Windows?")) { + wk-warn "Windows Setup aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } + } + + # Set available Windows versions + $windows_versions = @( + @{Name="Windows 7 Home Basic"; ImageFile="Win7"; ImageName="Windows 7 HOMEBASIC"} + @{Name="Windows 7 Home Premium"; ImageFile="Win7"; ImageName="Windows 7 HOMEPREMIUM"} + @{Name="Windows 7 Professional"; ImageFile="Win7"; ImageName="Windows 7 PROFESSIONAL"} + @{Name="Windows 7 Ultimate"; ImageFile="Win7"; ImageName="Windows 7 ULTIMATE"} + + @{Name="Windows 8.1"; ImageFile="Win8"; ImageName="Windows 8.1"; CRLF=$true} + @{Name="Windows 8.1 Pro"; ImageFile="Win8"; ImageName="Windows 8.1 Pro"} + + # The ISOs from the MediaCreationTool are apparently Technical Previews + @{Name="Windows 10 Home"; ImageFile="Win10"; ImageName="Windows 10 Technical Preview"; CRLF=$true} + @{Name="Windows 10 Pro"; ImageFile="Win10"; ImageName="Windows 10 Pro Technical Preview"} + ) + + # Build menu and get selection + $dest_windows_version = $null + $selection = $null + $actions = @(@{Name="Main Menu"; Letter="M"}) + $selection = (menu-select "Which version of Windows are we installing?" $windows_versions $actions) + + if ($selection -imatch '^M$') { + # User selected to return to the menu + return $false + } elseif ($selection -inotmatch '^\d+$') { + # This shouldn't happen? + throw + } else { + $selection -= 1 + $dest_windows_version = $windows_versions[$selection] + } + + # Double check before deleting data + wk-warn "SAFTEY CHECK:" + wk-write (" Installing:`t{0}" -f $dest_windows_version.Name) + wk-error (" And ERASING:`tDisk: {0}`t({1}) {2}`r`n" -f (human-size $dest_disk.Size 0), $dest_disk.PartitionStyle, $dest_disk.FriendlyName) + if (!(ask "Is this correct?")) { + wk-warn "Windows Setup aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } + + ## WARNING + wk-warn "WARNING: This section is experimental" + ## WARNING + + ## Here be dragons + try { + # Select UEFI or BIOS partition layout + if ($UEFI) { + # System booted via UEFI so assume new layout should be GPT + if (ask "Setup drive using GPT (UEFI) layout?") { + format-gpt $dest_disk + } else { + format-mbr $dest_disk + } + } else{ + if (ask "Setup drive using MBR (legacy) layout?") { + format-mbr $dest_disk + } else { + format-gpt $dest_disk + } + } + + # Apply image + apply-image $dest_windows_version.ImageFile $dest_windows_version.ImageName + + # Create boot files (copies files for both Legacy and UEFI) + wk-write " Copying boot files..." + bcdboot W:\Windows /s S: /f ALL 2>&1 | out-null + if ($dest_windows_version.Name -imatch '^Windows (8|10)') { + W:\Windows\System32\reagentc /setreimage /path T:\Recovery\WindowsRE /target W:\Windows 2>&1 | out-null + } + + # Done + wk-write "Windows Setup complete." + wk-write "" + pause "Press Enter to return to main menu... " + } catch { + # Error(s) + wk-error "$Error" + wk-error "Windows Setup aborted." + wk-write "" + pause "Press Enter to return to main menu... " -warning=$true + return $false + } +} diff --git a/WK/Scripts/init.ps1 b/WK/Scripts/init.ps1 index 009b756..b198fdd 100644 --- a/WK/Scripts/init.ps1 +++ b/WK/Scripts/init.ps1 @@ -4,9 +4,26 @@ $host.UI.RawUI.BackgroundColor = "black" $host.UI.RawUI.ForegroundColor = "cyan" +#$appdata = (gci env:appdata).value +#$localappdata = (gci env:localappdata).value +#$username = (gci env:username).value +#$userprofile = (gci env:userprofile).value $systemdrive = (gci env:systemdrive).value +$windir = (gci env:windir).value +#$programfiles = (gci env:programfiles).value +#$programfiles86 = $programfiles +#if (test-path env:"programfiles(x86)") { +# $programfiles86 = (gci env:"programfiles(x86)").value +#} $WKPath = "$systemdrive\WK" $date = get-date -uformat "%Y-%m-%d" +#$logpath = "$WKPath\Info\$date" + +# Check if booted via UEFI +$UEFI = $false +if ((Get-ItemProperty -path "HKLM:\System\CurrentControlSet\Control").PEFirmwareType -eq 2) { + $UEFI = $true +} function ask { param([string]$text = "Kotaero", [string]$log = "WK.log") @@ -56,6 +73,80 @@ function human-size { } return $size } +function menu-select { + ## $MainEntries should be an "AoH" object (with at least the key "Name" for each item) + ## NOTE: if the CRLF=$true; then a spacer is added before that entry. + ## Example: + ## $MainEntries = @( + ## @{Name="Windows 10 Home"; ImageFile="Win10"; ImageName="Windows 10 Home"} + ## @{Name="Windows 10 Pro"; ImageFile="Win10"; ImageName="Windows 10 Pro"} + ##) + + ## $ActionEntries should be an "AoH" object (with at least the keys "Name" and "Letter" for each item) + ## NOTE: if the CRLF=$true; then a spacer is added before that entry. + ## Example: + ## $ActionEntries = @( + ## @{Name="Reboot"; Letter="R"} + ## @{Name="Shutdown"; Letter="S"} + ##) + + param( + [string]$Title = "## Untitled Menu ##", + $MainEntries = @(), + $ActionEntries = @(), + [string]$Prompt = "Please make a selection", + [bool]$SecretExit = $false + ) + + # Bail early if no items given + if ($MainEntries.length -eq 0 -and $ActionEntries.length -eq 0) { + throw "MenuError: No items given." + } + + # Build menu + $menu_splash = "{0}`r`n`r`n" -f $title + $valid_answers = @() + if ($SecretExit) { + $valid_answers += "Q" + } + + # Add main items to splash + if ($MainEntries.length -gt 0) { + for ($i=0; $i -lt $MainEntries.length; $i++) { + if ($MainEntries[$i].CRLF) { + # Add spacer + $menu_splash += "`r`n" + } + $valid_answers += ($i + 1) + $menu_splash += "{0,2:N0}: {1}`r`n" -f ($i + 1), $MainEntries[$i].Name + } + $menu_splash += "`r`n" + } + + # Add action items to splash + if ($ActionEntries.length -gt 0) { + foreach ($_item in $ActionEntries) { + if ($_item.CRLF) { + # Add spacer + $menu_splash += "`r`n" + } + $menu_splash += " {0}: {1}`r`n" -f $_item.Letter.ToUpper(), $_item.Name + $valid_answers += $_item.Letter.ToLower(), $_item.Letter.ToUpper() + } + $menu_splash += "`r`n" + } + + # Add prompt to splash + $menu_splash += "{0}`r`n" -f $prompt + + # Select Windows version + do { + clear + $answer = read-host -prompt $menu_splash + } until ($valid_answers -contains $answer) + + return $answer.ToUpper() +} function pause { param([string]$message = "Press Enter to continue... ", [bool]$warning = $False) if ($warning) { diff --git a/WK/Scripts/servers.ps1 b/WK/Scripts/servers.ps1 new file mode 100644 index 0000000..2d47e19 --- /dev/null +++ b/WK/Scripts/servers.ps1 @@ -0,0 +1,85 @@ +# WK server functions + +## Init ## +$wd = $(Split-Path $MyInvocation.MyCommand.Path) +pushd "$wd" +. .\init.ps1 + +# Variables +$source_server = "10.0.0.10" +$backup_servers = @( + @{ "ip"="10.0.0.10"; + "letter"="Z"; + "name"="ServerOne"; + "path"="Backups"}, + @{ "ip"="10.0.0.11"; + "name"="ServerTwo"; + "letter"="Y"; + "path"="Backups"} + ) +$backup_user = "backup" +$backup_pass = "Abracadabra" + +# Functions +function select-server { + # Check for available servers + $avail_servers = @(Get-PSDrive | Where-Object {$_.DisplayRoot -imatch '\\\\'}) + if ($avail_servers.count -eq 0) { + wk-error "No suitable backup servers were detected." + return $false + } + + # Build menu and get selection + $selection = $null + $main_set = @() + foreach ($server in $avail_servers) { + $_entry = "{0} ({1} free)" -f $server.Description, (human-size $server.Free) + $main_set += @{Name=$_entry} + } + $actions = @(@{Name="Main Menu"; Letter="M"}) + $selection = (menu-select "Where are we saving the backup image(s)?" $main_set $actions) + + if ($selection -imatch '^\d+$') { + $selection -= 1 + return $avail_servers[$selection] + } + return $selection +} +function mount-servers { + # Mount servers + wk-write "Connecting to backup server(s)" + foreach ($_server in $backup_servers) { + if (test-connection $_server.ip -count 3 -quiet) { + try { + $_path = "\\{0}\{1}" -f $_server.ip, $_server.path + $_drive = "{0}:" -f $_server.letter + net use $_drive "$_path" /user:$backup_user $backup_pass | Out-Null + wk-write ("`t{0} server: mounted" -f $_server.name) + + # Add friendly description + $_regex = "^{0}$" -f $_server.letter + (Get-PSDrive | Where-Object {$_.Name -imatch $_regex}).Description = $_server.name + } catch { + wk-warn ("`t{0} server: failed" -f $_server.name) + } + } else { + wk-warn ("`t{0} server: timed-out" -f $_server.name) + } + } +} +function unmount-servers { + # Unmount servers + wk-write "Disconnecting from backup server(s)" + $mounted_servers = @(Get-PSDrive | Where-Object {$_.DisplayRoot -imatch '\\\\'}) + foreach ($_server in $mounted_servers) { + try { + $_drive = "{0}:" -f $_server.Name + net use $_drive /delete | Out-Null + #wk-warn ("`t{0} server: unmounted" -f $_server.name) + wk-warn "`tServer: unmounted" + } catch { + #wk-warn ("`t{0} server: failed" -f $_server.name) + wk-warn "`tServer: failed" + } + } +} diff --git a/make-cd.cmd b/make-cd.cmd index 885ba63..2f7bf21 100644 --- a/make-cd.cmd +++ b/make-cd.cmd @@ -5,6 +5,8 @@ setlocal EnableDelayedExpansion title WinPE 10 creation tool color 1b pushd %~dp0 +set "wd=%cd%" +set "pe_iso=WinPE-2016-02d.iso" :Flags for %%f in (%*) do ( @@ -12,8 +14,9 @@ for %%f in (%*) do ( ) :CreateISO -del winpe10-test.iso -makewinpemedia.cmd /iso pe_files winpe10-test.iso +del "!pe_iso!" +makewinpemedia.cmd /iso "%wd%\pe_files" "!pe_iso!" +goto Done :Abort echo. diff --git a/make.cmd b/make.cmd index 01dc542..5868691 100644 --- a/make.cmd +++ b/make.cmd @@ -7,7 +7,7 @@ color 1b pushd %~dp0 set "wd=%cd%" set "winpe_ocs=%programfiles(x86)%\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs" - +set "pe_iso=WinPE-2016-02d.iso" :Flags for %%f in (%*) do ( @@ -62,29 +62,38 @@ dism /add-package /image:"%wd%\mount" /packagepath:"%winpe_ocs%\en-us\WinPE-Enha del "%wd%\WK\Scripts\WK.log" mkdir "%wd%\mount\WK" robocopy /e "%wd%\WK" "%wd%\mount\WK" -mklink "%wd%\mount\System32\explorer.exe" "%wd%\mount\WK\Explorer++.exe" +del "%wd%\mount\Windows\explorer.exe" +mklink /h "%wd%\mount\Windows\explorer.exe" "%wd%\mount\WK\Explorer++\Explorer++64.exe" -:MenuLauncher +:System32Stuff copy /y "%wd%\System32\menu.cmd" "%wd%\mount\Windows\System32\menu.cmd" +copy /y "%wd%\System32\Winpeshl.ini" "%wd%\mount\Windows\System32\Winpeshl.ini" -:ReplaceStartnet -copy /y "%wd%\System32\startnet.cmd" "%wd%\mount\Windows\System32\startnet.cmd" - -:ReplaceNotepad +:RegistryEdits reg load HKLM\WinPE-SW mount\Windows\System32\config\SOFTWARE -reg add "HKLM\WinPE-SW\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v Debugger /t REG_SZ /d "X:\WK\Notepad2.exe /z" /f +reg load HKLM\WinPE-SYS mount\Windows\System32\config\SYSTEM + +rem Add 7-Zip to path +reg add "HKLM\WinPE-SYS\ControlSet001\Control\Session Manager\Environment" /v Path /t REG_EXPAND_SZ /d "%%SystemRoot%%\system32;%%SystemRoot%%;%%SystemRoot%%\System32\Wbem;%%SYSTEMROOT%%\System32\WindowsPowerShell\v1.0\;%%SystemDrive%%\WK\7-Zip" /f + +rem Replace Notepad +reg add "HKLM\WinPE-SW\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v Debugger /t REG_SZ /d "X:\WK\Notepad2\Notepad2-Mod64.exe /z" /f + +rem Unload registry hives reg unload HKLM\WinPE-SW +reg unload HKLM\WinPE-SYS :Background takeown /f "%wd%\mount\Windows\System32\winpe.jpg" /a icacls "%wd%\mount\Windows\System32\winpe.jpg" /grant administrators:F copy /y "%wd%\System32\winpe.jpg" "%wd%\mount\Windows\System32\winpe.jpg" +copy /y "%wd%\System32\winpe.jpg" "%wd%\mount\WK\ConEmu\winpe.jpg" :ManualStuff -echo Now is the time to add stuff (optional). -echo. -echo Press any key to commit changes... -pause>nul +REM echo Now is the time to add stuff (optional). +REM echo. +REM echo Press any key to commit changes... +REM pause>nul :Set-ScratchSpace rem Force RamDisk size to try and avoid capture-image errors @@ -94,8 +103,8 @@ dism /image:"%wd%\mount" /set-scratchspace:512 dism /unmount-image /mountdir:"%wd%\mount" /commit :CreateISO -del winpe10-2016.iso -makewinpemedia.cmd /iso "%wd%\pe_files" winpe10-2016.iso +del "!pe_iso!" +makewinpemedia.cmd /iso "%wd%\pe_files" "!pe_iso!" goto Done :Abort