Add win-installer partitioning/formatting logic
This commit is contained in:
parent
cb7ba1a285
commit
a75911cb32
3 changed files with 242 additions and 24 deletions
|
|
@ -32,6 +32,12 @@ use crate::system::disk::{
|
||||||
|
|
||||||
static DEFAULT_MAX_DISKS: usize = 8;
|
static DEFAULT_MAX_DISKS: usize = 8;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum FormatUseCase {
|
||||||
|
ApplyWimImage,
|
||||||
|
Clone,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RegexList {
|
pub struct RegexList {
|
||||||
detail_all_disks: OnceLock<Regex>,
|
detail_all_disks: OnceLock<Regex>,
|
||||||
detail_disk: OnceLock<Regex>,
|
detail_disk: OnceLock<Regex>,
|
||||||
|
|
@ -205,7 +211,11 @@ pub fn get_partitions(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn build_dest_format_script(disk_id: usize, part_type: &PartitionTableType) -> String {
|
pub fn build_dest_format_script(
|
||||||
|
disk_id: usize,
|
||||||
|
part_type: &PartitionTableType,
|
||||||
|
format_use_case: FormatUseCase,
|
||||||
|
) -> String {
|
||||||
let disk_id = format!("{disk_id}");
|
let disk_id = format!("{disk_id}");
|
||||||
let mut script = vec!["automount enable noerr", "select disk {disk_id}", "clean"];
|
let mut script = vec!["automount enable noerr", "select disk {disk_id}", "clean"];
|
||||||
match part_type {
|
match part_type {
|
||||||
|
|
@ -221,6 +231,10 @@ pub fn build_dest_format_script(disk_id: usize, part_type: &PartitionTableType)
|
||||||
script.push("format fs=ntfs quick label=System");
|
script.push("format fs=ntfs quick label=System");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if format_use_case == FormatUseCase::ApplyWimImage {
|
||||||
|
script.push("create partition primary");
|
||||||
|
script.push("format fs=ntfs quick label=Windows");
|
||||||
|
}
|
||||||
script.join("\r\n").replace("{disk_id}", &disk_id)
|
script.join("\r\n").replace("{disk_id}", &disk_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,10 @@ use core::{
|
||||||
line::{DVLine, get_disk_description_right, get_part_description},
|
line::{DVLine, get_disk_description_right, get_part_description},
|
||||||
state::Mode,
|
state::Mode,
|
||||||
system::{
|
system::{
|
||||||
boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script,
|
boot,
|
||||||
|
cpu::get_cpu_name,
|
||||||
|
disk::PartitionTableType,
|
||||||
|
diskpart::{FormatUseCase, build_dest_format_script},
|
||||||
drivers,
|
drivers,
|
||||||
},
|
},
|
||||||
tasks::{Task, TaskResult, TaskType, Tasks},
|
tasks::{Task, TaskResult, TaskType, Tasks},
|
||||||
|
|
@ -209,7 +212,8 @@ impl App {
|
||||||
&& let Some(disk) = disk_list.get(disk_index)
|
&& let Some(disk) = disk_list.get(disk_index)
|
||||||
{
|
{
|
||||||
let table_type = self.state.table_type.clone().unwrap();
|
let table_type = self.state.table_type.clone().unwrap();
|
||||||
let diskpart_script = build_dest_format_script(disk.id, &table_type);
|
let diskpart_script =
|
||||||
|
build_dest_format_script(disk.id, &table_type, FormatUseCase::Clone);
|
||||||
self.tasks.add(TaskType::Diskpart(diskpart_script));
|
self.tasks.add(TaskType::Diskpart(diskpart_script));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,20 @@ use core::{
|
||||||
config::Config,
|
config::Config,
|
||||||
line::{DVLine, get_disk_description_right},
|
line::{DVLine, get_disk_description_right},
|
||||||
state::Mode,
|
state::Mode,
|
||||||
system::{cpu::get_cpu_name, disk::PartitionTableType, drivers},
|
system::{
|
||||||
tasks::{TaskType, Tasks},
|
boot,
|
||||||
|
cpu::get_cpu_name,
|
||||||
|
disk::PartitionTableType,
|
||||||
|
diskpart::{FormatUseCase, build_dest_format_script},
|
||||||
|
drivers,
|
||||||
|
},
|
||||||
|
tasks::{Task, TaskResult, TaskType, Tasks},
|
||||||
tui::{Event, Tui},
|
tui::{Event, Tui},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
|
env,
|
||||||
iter::zip,
|
iter::zip,
|
||||||
|
path::PathBuf,
|
||||||
sync::{Arc, Mutex},
|
sync::{Arc, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -111,20 +119,20 @@ impl App {
|
||||||
Mode::SelectWinSource => Mode::SelectWinImage,
|
Mode::SelectWinSource => Mode::SelectWinImage,
|
||||||
Mode::SelectWinImage => Mode::SetUserName,
|
Mode::SelectWinImage => Mode::SetUserName,
|
||||||
Mode::SetUserName => Mode::Confirm,
|
Mode::SetUserName => Mode::Confirm,
|
||||||
Mode::Confirm => Mode::Process, // i.e. format, apply, etc
|
Mode::Confirm => Mode::PreClone,
|
||||||
Mode::Process | Mode::Done => Mode::Done,
|
Mode::PreClone => Mode::Clone,
|
||||||
|
Mode::Clone => Mode::PostClone,
|
||||||
|
Mode::PostClone | Mode::Done => Mode::Done,
|
||||||
Mode::Failed => Mode::Failed,
|
Mode::Failed => Mode::Failed,
|
||||||
// Invalid States
|
// Invalid States
|
||||||
Mode::BootDiags
|
Mode::BootDiags
|
||||||
| Mode::BootScan
|
| Mode::BootScan
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::Clone
|
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::PreClone
|
| Mode::Process
|
||||||
| Mode::PostClone
|
|
||||||
| Mode::SelectParts
|
| Mode::SelectParts
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +147,126 @@ impl App {
|
||||||
// self.action_tx
|
// self.action_tx
|
||||||
// .send(Action::DisplayPopup(popup::Type::Info, String::from("...")))?;
|
// .send(Action::DisplayPopup(popup::Type::Info, String::from("...")))?;
|
||||||
// }
|
// }
|
||||||
|
Mode::PreClone => {
|
||||||
|
self.action_tx.send(Action::DisplayPopup(
|
||||||
|
popup::Type::Info,
|
||||||
|
String::from("Formatting destination disk"),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Get System32 path
|
||||||
|
let system32 = get_system32_path(&self.action_tx);
|
||||||
|
|
||||||
|
// (Re)Enable volume mounting
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
PathBuf::from(format!("{system32}/mountvol.exe")),
|
||||||
|
vec![String::from("/e")],
|
||||||
|
));
|
||||||
|
|
||||||
|
// Build Diskpart script to format destination disk
|
||||||
|
let disk_list = self.state.disk_list.lock().unwrap();
|
||||||
|
if let Some(disk_index) = self.state.disk_index_dest
|
||||||
|
&& let Some(disk) = disk_list.get(disk_index)
|
||||||
|
{
|
||||||
|
let table_type = self.state.table_type.clone().unwrap();
|
||||||
|
let diskpart_script = build_dest_format_script(
|
||||||
|
disk.id,
|
||||||
|
&table_type,
|
||||||
|
FormatUseCase::ApplyWimImage,
|
||||||
|
);
|
||||||
|
self.tasks.add(TaskType::Diskpart(diskpart_script));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update drive letters
|
||||||
|
self.tasks.add(TaskType::Sleep);
|
||||||
|
if let Some(dest_index) = self.state.disk_index_dest {
|
||||||
|
self.tasks.add(TaskType::UpdateDestDisk(dest_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::Clone => {
|
||||||
|
self.action_tx.send(Action::DisplayPopup(
|
||||||
|
popup::Type::Info,
|
||||||
|
String::from("Applying Image"),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Get wimlib-imagex path
|
||||||
|
let program_files = PathBuf::from(get_program_files_path(&self.action_tx));
|
||||||
|
let wimlib_imagex = program_files.join("wimlib\\wimlib-imagex.exe");
|
||||||
|
|
||||||
|
// Get image info
|
||||||
|
let wim_sources = self.state.wim_sources.lock().unwrap();
|
||||||
|
let wim_file = wim_sources.get_file(self.state.wim_file_index.unwrap());
|
||||||
|
let wim_index = format!("{}", self.state.wim_image_index.unwrap());
|
||||||
|
|
||||||
|
// Add actions
|
||||||
|
let disk_list = self.state.disk_list.lock().unwrap();
|
||||||
|
if let Some(disk_index) = self.state.disk_index_dest
|
||||||
|
&& let Some(disk) = disk_list.get(disk_index)
|
||||||
|
{
|
||||||
|
let num_parts = disk.parts.len();
|
||||||
|
let dest_path = format!("{}:\\", disk.get_part_letter(num_parts - 1));
|
||||||
|
self.tasks.add(TaskType::CommandWait(
|
||||||
|
wimlib_imagex,
|
||||||
|
vec![String::from("apply"), wim_file.path, wim_index, dest_path],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Mode::PostClone => {
|
||||||
|
self.action_tx.send(Action::DisplayPopup(
|
||||||
|
popup::Type::Info,
|
||||||
|
String::from("Updating boot configuration"),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
// Get System32 path
|
||||||
|
let system32 = get_system32_path(&self.action_tx);
|
||||||
|
|
||||||
|
// Add actions
|
||||||
|
let disk_list = self.state.disk_list.lock().unwrap();
|
||||||
|
if let Some(disk_index) = self.state.disk_index_dest
|
||||||
|
&& let Some(disk) = disk_list.get(disk_index)
|
||||||
|
{
|
||||||
|
let table_type = self.state.table_type.clone().unwrap();
|
||||||
|
let letter_boot = disk.get_part_letter(0);
|
||||||
|
let letter_os =
|
||||||
|
disk.get_part_letter(match self.state.table_type.clone().unwrap() {
|
||||||
|
PartitionTableType::Guid => 2,
|
||||||
|
PartitionTableType::Legacy => 1,
|
||||||
|
});
|
||||||
|
info!("PostClone // Disk: {disk:?}");
|
||||||
|
info!("\t\tBoot letter: {letter_boot}");
|
||||||
|
info!("\t\tOS letter: {letter_os}");
|
||||||
|
|
||||||
|
// Safety check
|
||||||
|
if letter_boot.is_empty() || letter_os.is_empty() {
|
||||||
|
self.action_tx.send(Action::Error(String::from(
|
||||||
|
"ERROR\n\n\nFailed to get drive letters for the destination",
|
||||||
|
)))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create boot files
|
||||||
|
for task in boot::configure_disk(
|
||||||
|
&letter_boot,
|
||||||
|
&letter_os,
|
||||||
|
boot::SafeMode::Enable,
|
||||||
|
&system32,
|
||||||
|
&table_type,
|
||||||
|
) {
|
||||||
|
self.tasks.add(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject driver(s) (if selected)
|
||||||
|
if let Some(driver) = &self.state.driver {
|
||||||
|
if let Ok(task) = boot::inject_driver(driver, &letter_os, &system32) {
|
||||||
|
self.tasks.add(task);
|
||||||
|
} else {
|
||||||
|
self.action_tx.send(Action::Error(format!(
|
||||||
|
"Failed to inject driver:\n{}",
|
||||||
|
driver.name
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Mode::ScanDisks => {
|
Mode::ScanDisks => {
|
||||||
self.state.reset_all();
|
self.state.reset_all();
|
||||||
if self.tasks.idle() {
|
if self.tasks.idle() {
|
||||||
|
|
@ -261,7 +389,9 @@ impl App {
|
||||||
Action::Tick => {
|
Action::Tick => {
|
||||||
self.last_tick_key_events.drain(..);
|
self.last_tick_key_events.drain(..);
|
||||||
// Check background task(s)
|
// Check background task(s)
|
||||||
self.tasks.poll()?;
|
if let Some(task) = self.tasks.poll()? {
|
||||||
|
self.handle_task(&task)?;
|
||||||
|
}
|
||||||
if let Ok(mut wim_sources) = self.state.wim_sources.lock() {
|
if let Ok(mut wim_sources) = self.state.wim_sources.lock() {
|
||||||
wim_sources.poll();
|
wim_sources.poll();
|
||||||
}
|
}
|
||||||
|
|
@ -317,6 +447,11 @@ impl App {
|
||||||
self.action_tx.send(Action::DismissPopup)?;
|
self.action_tx.send(Action::DismissPopup)?;
|
||||||
self.action_tx.send(Action::SetMode(next_mode))?;
|
self.action_tx.send(Action::SetMode(next_mode))?;
|
||||||
}
|
}
|
||||||
|
Action::DisplayPopup(ref popup_type, ref _popup_text) => {
|
||||||
|
if *popup_type == popup::Type::Error {
|
||||||
|
self.action_tx.send(Action::SetMode(Mode::Failed))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::PrevScreen => match self.cur_mode {
|
Action::PrevScreen => match self.cur_mode {
|
||||||
Mode::SelectTableType => {
|
Mode::SelectTableType => {
|
||||||
self.action_tx.send(Action::SetMode(Mode::SelectDisks))?;
|
self.action_tx.send(Action::SetMode(Mode::SelectDisks))?;
|
||||||
|
|
@ -405,6 +540,39 @@ impl App {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_task(&mut self, task: &Task) -> Result<()> {
|
||||||
|
match task.task_type {
|
||||||
|
TaskType::CommandWait(_, _) | TaskType::Diskpart(_) => {
|
||||||
|
// Check result
|
||||||
|
if let Some(result) = &task.result {
|
||||||
|
match result {
|
||||||
|
TaskResult::Error(msg) => {
|
||||||
|
self.action_tx
|
||||||
|
.send(Action::Error(format!("{} Failed: {msg}", task.task_type)))?;
|
||||||
|
}
|
||||||
|
TaskResult::Output(stdout, stderr, success) => {
|
||||||
|
if !success {
|
||||||
|
let msg = if !stdout.is_empty() {
|
||||||
|
stdout.clone()
|
||||||
|
} else if !stderr.is_empty() {
|
||||||
|
stderr.clone()
|
||||||
|
} else {
|
||||||
|
String::from("Unknown Error")
|
||||||
|
};
|
||||||
|
self.action_tx.send(Action::Error(format!(
|
||||||
|
"{} Failed: {msg}",
|
||||||
|
task.task_type
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
||||||
tui.draw(|frame| {
|
tui.draw(|frame| {
|
||||||
if let [header, _body, footer, center, left, right, username, popup] =
|
if let [header, _body, footer, center, left, right, username, popup] =
|
||||||
|
|
@ -426,7 +594,9 @@ impl App {
|
||||||
|
|
||||||
fn build_footer_string(cur_mode: Mode) -> String {
|
fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
match cur_mode {
|
match cur_mode {
|
||||||
Mode::Home | Mode::ScanDisks => String::from("(q) to quit"),
|
Mode::Home | Mode::ScanDisks | Mode::PreClone | Mode::Clone | Mode::PostClone => {
|
||||||
|
String::from("(q) to quit")
|
||||||
|
}
|
||||||
Mode::InstallDrivers => String::from("(Enter) to select / (q) to quit"),
|
Mode::InstallDrivers => String::from("(Enter) to select / (q) to quit"),
|
||||||
Mode::SelectDisks => String::from(
|
Mode::SelectDisks => String::from(
|
||||||
"(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit",
|
"(Enter) to select / / (i) to install driver / (r) to rescan / (q) to quit",
|
||||||
|
|
@ -440,18 +610,16 @@ fn build_footer_string(cur_mode: Mode) -> String {
|
||||||
),
|
),
|
||||||
Mode::SetUserName => String::from("(Enter) to continue / (Esc) to go back"),
|
Mode::SetUserName => String::from("(Enter) to continue / (Esc) to go back"),
|
||||||
Mode::Confirm => String::from("(Enter) to confirm / (b) to go back / (q) to quit"),
|
Mode::Confirm => String::from("(Enter) to confirm / (b) to go back / (q) to quit"),
|
||||||
Mode::Done | Mode::Failed | Mode::Process => String::from("(Enter) or (q) to quit"),
|
Mode::Done | Mode::Failed => String::from("(Enter) or (q) to quit"),
|
||||||
// Invalid States
|
// Invalid States
|
||||||
Mode::BootDiags
|
Mode::BootDiags
|
||||||
| Mode::BootScan
|
| Mode::BootScan
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::Clone
|
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
| Mode::PreClone
|
| Mode::Process
|
||||||
| Mode::PostClone
|
|
||||||
| Mode::SelectParts
|
| Mode::SelectParts
|
||||||
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
| Mode::SetBootMode => panic!("This shouldn't happen?"),
|
||||||
}
|
}
|
||||||
|
|
@ -475,15 +643,9 @@ fn build_left_items(app: &App) -> Action {
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|driver| items.push(driver.to_string()));
|
.for_each(|driver| items.push(driver.to_string()));
|
||||||
}
|
}
|
||||||
Mode::Process => {
|
|
||||||
select_type = SelectionType::Loop;
|
|
||||||
title = String::from("Processing");
|
|
||||||
// TODO: FIXME
|
|
||||||
}
|
|
||||||
Mode::ScanWinSources => {
|
Mode::ScanWinSources => {
|
||||||
select_type = SelectionType::Loop;
|
select_type = SelectionType::Loop;
|
||||||
title = String::from("Scanning");
|
title = String::from("Scanning");
|
||||||
// TODO: FIXME
|
|
||||||
}
|
}
|
||||||
Mode::SelectWinSource => {
|
Mode::SelectWinSource => {
|
||||||
select_type = SelectionType::One;
|
select_type = SelectionType::One;
|
||||||
|
|
@ -539,6 +701,7 @@ fn build_left_items(app: &App) -> Action {
|
||||||
| Mode::BootScan
|
| Mode::BootScan
|
||||||
| Mode::BootSetup
|
| Mode::BootSetup
|
||||||
| Mode::DiagMenu
|
| Mode::DiagMenu
|
||||||
|
| Mode::Process
|
||||||
| Mode::InjectDrivers
|
| Mode::InjectDrivers
|
||||||
| Mode::LogView
|
| Mode::LogView
|
||||||
| Mode::PEMenu
|
| Mode::PEMenu
|
||||||
|
|
@ -653,7 +816,12 @@ fn build_right_items(app: &App) -> Action {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::SelectWinImage | Mode::SetUserName | Mode::Confirm => {
|
Mode::SelectWinImage
|
||||||
|
| Mode::SetUserName
|
||||||
|
| Mode::Confirm
|
||||||
|
| Mode::PreClone
|
||||||
|
| Mode::Clone
|
||||||
|
| Mode::PostClone => {
|
||||||
info!("Building right items for: {:?}", &app.cur_mode);
|
info!("Building right items for: {:?}", &app.cur_mode);
|
||||||
let wim_file;
|
let wim_file;
|
||||||
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
if let Ok(wim_sources) = app.state.wim_sources.lock()
|
||||||
|
|
@ -717,7 +885,7 @@ fn build_right_items(app: &App) -> Action {
|
||||||
}])
|
}])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Mode::Confirm => {
|
Mode::Confirm | Mode::PreClone | Mode::Clone | Mode::PostClone => {
|
||||||
if let Some(index) = app.state.wim_image_index
|
if let Some(index) = app.state.wim_image_index
|
||||||
&& let Some(image) = wim_file.images.get(index)
|
&& let Some(image) = wim_file.images.get(index)
|
||||||
{
|
{
|
||||||
|
|
@ -842,3 +1010,35 @@ fn get_chunks(r: Rect) -> Vec<Rect> {
|
||||||
// Done
|
// Done
|
||||||
chunks
|
chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_program_files_path(action_tx: &mpsc::UnboundedSender<Action>) -> String {
|
||||||
|
let mut program_files_path = String::from(".");
|
||||||
|
if cfg!(windows) {
|
||||||
|
if let Ok(path) = env::var("PROGRAMFILES") {
|
||||||
|
program_files_path = path;
|
||||||
|
} else {
|
||||||
|
action_tx
|
||||||
|
.send(Action::Error(String::from(
|
||||||
|
"ERROR\n\n\nFailed to find PROGRAMFILES",
|
||||||
|
)))
|
||||||
|
.expect("Failed to find PROGRAMFILES and then failed to send action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
program_files_path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_system32_path(action_tx: &mpsc::UnboundedSender<Action>) -> String {
|
||||||
|
let mut system32_path = String::from(".");
|
||||||
|
if cfg!(windows) {
|
||||||
|
if let Ok(path) = env::var("SYSTEMROOT") {
|
||||||
|
system32_path = format!("{path}/System32");
|
||||||
|
} else {
|
||||||
|
action_tx
|
||||||
|
.send(Action::Error(String::from(
|
||||||
|
"ERROR\n\n\nFailed to find SYSTEMROOT",
|
||||||
|
)))
|
||||||
|
.expect("Failed to find SYSTEMROOT and then failed to send action");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
system32_path
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue