bmputil 1.1.0

Black Magic Probe companion utility
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-FileCopyrightText: 2022-2026 1BitSquared <info@1bitsquared.com>
// SPDX-FileContributor: Written by Mikaela Szekely <mikaela.szekely@qyriad.me>
// SPDX-FileContributor: Modified by Rachel Mant <git@dragonmux.network>
//! This module handles Windows-specific code, mostly the installation of drivers for Black Magic Probe USB interfaces.
//!
//! "Installation" is a somewhat overloaded term, in Windows. Behind the scenes this module, using
//! [libwdi](https://github.com/pbatard/libwdi) (with [wdi-rs](https://github.com/Qyriad/wdi-rs) as the Rust interface),
//! adds an [INF file](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/overview-of-inf-files)
//! that binds [WinUSB.sys](https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/winusb-installation) to
//! the Windows device nodes for the app-mode Black Magic Probe VID/PID DFU interface (as interfaces get their own
//! devnodes in Windows) and the DFU-mode VID/PID (which has no interfaces). The INF file, as far as I can tell, gets
//! placed to `C:\Windows\INF`, with a name like `oem10.inf`. The Windows Registry gets a value that corresponds to
//! this at `HKLM:\SYSTEM\DriverDatabase\DeviceIds\USB\{hwid}`, with `hwid` being `VID_1D50&PID_6018&MI_04` for the
//! the normal mode BMP device (meaning "USB VID 0x1d50, USB PID 0x6018, interface index 4"), and `VID_1D50&6017` for
//! the DFU mode BMP device (meaning "VID 0x1d50, PID 0x6017, no interface"). That key gets a value of the same name as
//! the INF file, so something like `oem10.inf`. Note that this works *even if the BMP has never been plugged in*, but
//! will *not* show up in Device Manager until you *do* plug it in, even if you enable "Show hidden devices".
//! We also use this registry key to detect if the driver has already been installed. In the future, we should probably
//! add some command that attempts to repair a possibly broken driver installation by checking the INF file the Registry
//! key refers to.
//!
//! [ensure_access] is the main driving function for this module. It checks the above registry key to determine if the
//! drivers have already been installed, and orchestrates the installation itself. libwdi is where most of the magic
//! happens though — it handles generating the INF file and calling the relevant Windows
//! [SetupAPI](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/setupapi) functions to actually move
//! the INF to the right directory and create the right Registry keys.

use std::ffi::{CString, OsStr, OsString, c_void};
use std::io::Error as IoError;
use std::os::windows::ffi::OsStrExt;
use std::str::FromStr;
use std::time::Duration;
use std::{env, iter, mem, ptr, thread};

use bstr::ByteSlice;
use color_eyre::eyre::Result;
use lazy_static::lazy_static;
use libc::{FILE, c_char, c_int, c_long, c_uint, intptr_t};
use log::{debug, error, info, trace, warn};
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Security::{PSID, TOKEN_ELEVATION_TYPE, TOKEN_MANDATORY_LABEL};
use windows::Win32::System::Console::{AllocConsole, AttachConsole, FreeConsole};
use windows_registry::LOCAL_MACHINE;

/// From fnctl.h
/// ```c
/// #define _O_TEXT        0x4000  // file mode is text (translated)
/// ```
const _O_TEXT: c_int = 0x4000;

#[allow(dead_code)]
const STDIN_FILENO: c_int = 0;
#[allow(dead_code)]
const STDOUT_FILENO: c_int = 1;
#[allow(dead_code)]
const STDERR_FILENO: c_int = 2;

unsafe extern "C" {
	/// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/open-osfhandle?view=msvc-170
	pub fn _open_osfhandle(osfhandle: intptr_t, flags: c_int) -> c_int;

	/// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fdopen-wfdopen?view=msvc-170
	pub fn _fdopen(fd: c_int, mode: *const c_char) -> *mut FILE;

	/// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/dup-dup2?view=msvc-170
	pub fn _dup2(fd1: c_int, fd2: c_int) -> c_int;

	/// An internal CRT function that Windows uses to define stdout, stderr, and stdin.
	/// ```c
	/// _ACRTIMP_ALT FILE* __cdecl __acrt_iob_func(unsigned _Ix);
	/// ```
	pub fn __acrt_iob_func(_Ix: c_uint) -> *mut FILE;
}

#[allow(dead_code)]
pub fn stdinf() -> *mut FILE
{
	unsafe { __acrt_iob_func(0) }
}

pub fn stdoutf() -> *mut FILE
{
	unsafe { __acrt_iob_func(1) }
}

pub fn stderrf() -> *mut FILE
{
	unsafe { __acrt_iob_func(2) }
}

/// Internal struct for FILE* on Windows. See [restore_cstdio]'s implementation for details.
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct UcrtStdioStreamData
{
	_ptr: *mut FILE,
	_base: *mut i8,
	_cnt: c_int,
	_flags: c_long,
	/// Note: this is what is returned by _fileno()
	_file: c_long,
	_charbuf: c_int,
	_bufsiz: c_int,
	_tmpfname: *mut i8,
	_lock: *mut c_void,
}

/// When our admin process is created, it does not inherit stdin, stdout, and stderr from the parent process.
/// AttachConsole(parent_pid) easily connects the admin process to the parent console, but, surprisingly
/// enough, that only restores stdio for *Rust*, and not C. How could this possibly be the case?
/// Well Rust's e.g. println!() eventually calls WinAPI's `WriteConsoleW()` using the console handle.
/// C's printf() on the other hand goes through whatever file descriptor the `stdout` global is set to,
/// and that is *not* updated when you call AttachConsole(). So, we need to resynchronize the
/// Microsoft C Runtime's stdio global state with the Windows console subsystem state.
pub fn restore_cstdio(parent_pid: u32) -> Result<()>
{
	// First, free whatever console Windows gave us by default, and attach to the parent's console.
	unsafe {
		FreeConsole()?;
		// Why not ATTACH_PARENT_PROCESS? Well it worked on my machine, but didn't work in a VM.
		// This seems to work better, for some reason.
		if let Err(_e) = AttachConsole(parent_pid) {
			// If we can't attach the previous console, then allocate a new console instead.
			// This will pop up a new window for the user, but that's better than no output
			// at all.
			AllocConsole()?;
		}
	}

	// Resync for each of stdin, stdout, and stderr.

	let res = unsafe { libc::freopen(b"CONIN$\0".as_ptr() as *const i8, b"w\0".as_ptr() as *const i8, stdinf()) };
	if res.is_null() {
		Err::<(), _>(IoError::last_os_error()).expect("Failed to resynchronize stdin");
	}

	let res = unsafe { libc::freopen(b"CONOUT$\0".as_ptr() as *const i8, b"wt\0".as_ptr() as *const i8, stdoutf()) };
	if res.is_null() {
		Err::<(), _>(IoError::last_os_error()).expect("Failed to resynchronize stdout");
	}

	// HACK: on some¹ systems, using the same technique for stderr seems to break both stderr and stdout, for some
	// reason. So instead we'll copy the internal FILE* structure used for stdout to the stderr global.
	//
	// ¹It worked just fine on my personal dev machine and one other personal Windows machine, but didn't work in a
	// fresh VM, so who knows.
	let out = stdoutf() as *mut UcrtStdioStreamData;
	let err = stderrf() as *mut UcrtStdioStreamData;
	unsafe {
		*err = *out;
	}

	Ok(())
}

fn os_str_to_null_terminated_vec(s: &OsStr) -> Vec<u16>
{
	s.encode_wide().chain(iter::once(0)).collect()
}

/// Install drivers for each libwdi [wdi::DeviceInfo] in `devices`. Must be called from admin.
fn admin_install_drivers(devices: &mut [wdi::DeviceInfo])
{
	// TODO: cd into a tempdir so libwdi doesn't spill files into the user's cwd?

	// NOTE: the sleeps in this function are a mitigation for inconsistent errors I've had when
	// testing this. Windows doesn't always seem to like doing all of these operations in quick
	// succession, and sometimes, somehow, the process seems to lock itself(?) out of accessing the
	// intermediate files libwdi creates for this.

	for dev in devices.into_iter() {
		let hwid_str = dev
			.hardware_id
			.as_ref()
			.expect("BMP WDI DeviceInfo always have hardware_id set")
			.to_str_lossy()
			.to_string();

		println!("Installing for {}", &hwid_str);

		thread::sleep(Duration::from_secs(1));

		println!("Preparing driver for installation...");

		wdi::prepare_driver(dev, "usb_driver", "usb_device.inf", &mut Default::default()).unwrap();

		println!("Driver prepared.");
		println!("About to install driver. This may take multiple minutes and there will be NO PROGRESS REPORTING!");
		println!("Installing driver...");

		thread::sleep(Duration::from_secs(1));

		wdi::install_driver(dev, "usb_driver", "usb_device.inf", &mut Default::default()).unwrap();

		println!("Driver successfully installed for {}", &hwid_str);
	}
}

lazy_static! {
	pub static ref APP_MODE_WDI_INFO: wdi::DeviceInfo = wdi::DeviceInfo {
		vid: 0x1d50,
		pid: 0x6018,
		is_composite: true,
		mi: 4,
		//desc: String::from("Black Magic DFU (Interface 4)").into(),
		desc: CString::new("Black Magic DFU (Interface 4)").unwrap().into_bytes_with_nul().to_vec(),
		driver: None,
		device_id: None,
		hardware_id: Some(CString::new(r"USB\VID_1D50&PID_6018&REV_0100&MI_04").unwrap().to_bytes_with_nul().to_vec()),
		compatible_id: Some(CString::new(r"USB\Class_fe&SubClass_01&Prot_01").unwrap().to_bytes_with_nul().to_vec()),
		upper_filter: None,
		driver_version: 0,
	};

	pub static ref DFU_MODE_WDI_INFO: wdi::DeviceInfo = wdi::DeviceInfo {
		vid: 0x1d50,
		pid: 0x6017,
		is_composite: false,
		mi: 0,
		desc: CString::new("Black Magic Probe DFU").unwrap().to_bytes_with_nul().to_vec(),
		driver: None,
		device_id: None,
		hardware_id: Some(CString::new(r"USB\VID_1D50&PID_6017&REV_0100").unwrap().to_bytes_with_nul().to_vec()),
		compatible_id: Some(CString::new(r"USB\Cass_FE&SubClass_01&Prot_02").unwrap().to_bytes_with_nul().to_vec()),
		upper_filter: None,
		driver_version: 0,
	};
}

/// Checks what drivers a device with a given [enumerator] and [HardwareId] is bound to, if any, via the Windows
/// registry. Returns the INF names of any bound drivers.
///
/// `hardware_id` should *not* include the enumerator name. e.g. no leading `USB\`.
///
/// This function checks `HKLM:\SYSTEM\DriverDatabase\DeviceIds\{enumerator}\{hardware_id}`.
/// The Windows registry is case in-sensitive.
///
/// [enumerator]: (https://learn.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdevsw)
/// [HardwareId]: (https://learn.microsoft.com/en-us/windows-hardware/drivers/install/hardware-ids)
pub fn hwid_bound_to_driver(hardware_id: &str, enumerator: &str) -> Result<Vec<String>>
{
	debug!(
		"Checking what drivers device {} under enumerator {} is bound to",
		hardware_id, enumerator
	);

	let mut driver_names: Vec<String> = Vec::new();
	let hwid_lower = hardware_id.to_lowercase();

	// The registry key name for a driver database for `enumerator` is
	// `HKLM:\SYSTEM\DriverDatabase\DeviceIds\{enumerator}`.
	let driver_db_subkey_path = format!(r"SYSTEM\DriverDatabase\DeviceIds\{}", enumerator);

	// Open the driver database for the given enumerator.
	trace!(r"Opening HKLM:\{}", driver_db_subkey_path);
	let driver_db_subkey_handle = LOCAL_MACHINE.open(&driver_db_subkey_path).map_err(|e| {
		error!("Error opening USB driver database in registry: {}", &e);
		e
	})?;

	// Subkey of the driver DB are Windows HardwareId strings, without the leading enumerator name.
	let driver_db_subkeys = driver_db_subkey_handle.keys().map_err(|e| {
		warn!("Error enumerating registry subkeys of {}: {}", driver_db_subkey_path, &e);
		e
	})?;
	for dev_key_name in driver_db_subkeys {
		let dev_key_lower = dev_key_name.to_lowercase();

		// If this subkey has the same name as the hardware ID we're looking for...
		if dev_key_lower == hwid_lower {
			// Then check if it has any drivers listed, or if it's empty.
			// Usually, however, unbound devices will not have a subkey of their enumerator at all.

			trace!(r"Opening HKLM:\{}\{}", driver_db_subkey_path, &dev_key_name);
			let dev_key = driver_db_subkey_handle.open(&dev_key_name).map_err(|e| {
				warn!(
					"Error opening know-to-exist subkey {} when checking bound drivers: {}",
					dev_key_name, &e
				);
				e
			})?;

			let driver_values = dev_key.values().map_err(|e| {
				warn!(
					"Error enumerating values of key {:?} when checking bound drivers: {}",
					dev_key, &e
				);
				e
			})?;
			for (driver_name, _driver_value) in driver_values {
				// Values of `HKLM:\SYSTEM\DriverDatabase\DeviceIds\{enumerator}\{hwid}` are of type
				// REG_BINARY, but I have no idea what the format is. The name, however, is the name of
				// the INF file bound to the device, e.g. `oem15.inf`, and that's what we want.
				driver_names.push(driver_name);
			}
		}
	}

	Ok(driver_names)
}

enum PrivilegeLevel
{
	NotPrivileged,
	Elevated,
	HighIntegrityAdmin,
}

struct ProcessToken
{
	token: HANDLE,
}

impl ProcessToken
{
	pub fn for_current_process() -> Result<Self>
	{
		use windows::Win32::Foundation::INVALID_HANDLE_VALUE;
		use windows::Win32::Security::{TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY};
		use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcessToken};

		let mut token = INVALID_HANDLE_VALUE;
		unsafe {
			OpenProcessToken(
				GetCurrentProcess(),
				TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE,
				&mut token,
			)?
		};

		Ok(Self {
			token,
		})
	}

	fn elevation_type(&self) -> Result<TOKEN_ELEVATION_TYPE>
	{
		use windows::Win32::Security::{GetTokenInformation, TokenElevationType};
		let mut elevation_type = TOKEN_ELEVATION_TYPE::default();
		let mut size = 0;
		unsafe {
			GetTokenInformation(
				self.token,
				TokenElevationType,
				Some(&mut elevation_type as *mut TOKEN_ELEVATION_TYPE as *mut _),
				std::mem::size_of_val(&elevation_type) as u32,
				&mut size,
			)?
		};

		Ok(elevation_type)
	}

	fn integrity_level(&self) -> Result<TOKEN_MANDATORY_LABEL>
	{
		use windows::Win32::Security::{GetTokenInformation, TokenIntegrityLevel};
		let mut integrity_level = TOKEN_MANDATORY_LABEL::default();
		let mut size = 0;
		unsafe {
			GetTokenInformation(
				self.token,
				TokenIntegrityLevel,
				Some(&mut integrity_level as *mut TOKEN_MANDATORY_LABEL as *mut _),
				std::mem::size_of_val(&integrity_level) as u32,
				&mut size,
			)?
		};

		Ok(integrity_level)
	}

	fn as_impersonation_token(&self) -> Result<Self>
	{
		use windows::Win32::Foundation::INVALID_HANDLE_VALUE;
		use windows::Win32::Security::{
			DuplicateTokenEx, SecurityImpersonation, TOKEN_ADJUST_DEFAULT, TOKEN_ADJUST_SESSIONID,
			TOKEN_ASSIGN_PRIMARY, TOKEN_DUPLICATE, TOKEN_IMPERSONATE, TOKEN_QUERY, TokenImpersonation,
		};
		let mut token = INVALID_HANDLE_VALUE;
		unsafe {
			DuplicateTokenEx(
				self.token,
				TOKEN_ADJUST_SESSIONID |
					TOKEN_ADJUST_DEFAULT |
					TOKEN_ASSIGN_PRIMARY |
					TOKEN_IMPERSONATE |
					TOKEN_DUPLICATE | TOKEN_QUERY,
				None,
				SecurityImpersonation,
				TokenImpersonation,
				&mut token,
			)?
		};

		Ok(Self {
			token,
		})
	}

	fn check_membership(&self, sid: PSID) -> Result<bool>
	{
		use windows::Win32::Foundation::{FALSE, TRUE};
		use windows::Win32::Security::CheckTokenMembership;
		let mut is_member = FALSE;
		unsafe { CheckTokenMembership(Some(self.token), sid, &mut is_member)? };

		Ok(is_member == TRUE)
	}

	pub fn privilege_level(&self) -> Result<PrivilegeLevel>
	{
		use windows::Win32::Foundation::TRUE;
		use windows::Win32::Security::{
			CreateWellKnownSid, IsWellKnownSid, SID, TokenElevationTypeFull, WinBuiltinAdministratorsSid,
			WinHighLabelSid,
		};

		let elevation_type = self.elevation_type()?;
		if elevation_type == TokenElevationTypeFull {
			return Ok(PrivilegeLevel::Elevated);
		}

		let integrity_level = self.integrity_level()?;
		if unsafe { IsWellKnownSid(integrity_level.Label.Sid, WinHighLabelSid) } == TRUE {
			return Ok(PrivilegeLevel::NotPrivileged);
		}

		let impersonation_token = self.as_impersonation_token()?;
		let mut size = 0;
		let mut administrators_sid = SID::default();
		let administrators_psid = PSID(&mut administrators_sid as *mut SID as *mut _);
		unsafe { CreateWellKnownSid(WinBuiltinAdministratorsSid, None, Some(administrators_psid), &mut size)? };

		if impersonation_token.check_membership(administrators_psid)? {
			Ok(PrivilegeLevel::HighIntegrityAdmin)
		} else {
			Ok(PrivilegeLevel::NotPrivileged)
		}
	}
}

impl Drop for ProcessToken
{
	fn drop(&mut self)
	{
		use windows::Win32::Foundation::CloseHandle;
		unsafe { CloseHandle(self.token).expect("token should have been valid to close") }
	}
}

/// This function ensures that all connected Black Magic Probe devices have the necessary drivers installed, via libwdi.
/// If `explicitly_requested` is true, then this will print if there is nothing to do.
/// If `force` is true, then this will install even if there is an existing driver.
// FIXME: This should return a Result, and should probably return what devices had drivers
pub fn ensure_access(parent_pid: Option<u32>, explicitly_requested: bool, force: bool)
{
	// Check if the WinUSB driver has been installed for BMP devices yet.

	debug!("Checking Windows registry driver database to determine if WinUSB is bound to BMP device nodes");

	let mut devices_needing_driver: Vec<wdi::DeviceInfo> = Vec::with_capacity(2);

	if force {
		info!("Force installing WinUSB driver for app mode and DFU mode BMP devices...");
		devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
		devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
	} else {
		match hwid_bound_to_driver("VID_1D50&PID_6018&MI_04", "USB") {
			Ok(driver_names) if driver_names.len() == 0 => {
				devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
				info!("Scheduling WinUSB driver installation for app mode BMP device...");
			},

			// If an error occurred checking, then install the driver just in case.
			Err(_e) => {
				devices_needing_driver.push(APP_MODE_WDI_INFO.clone());
				info!("Scheduling WinUSB driver installation for app mode BMP device...");
			},

			Ok(driver_names) => {
				trace!("App mode BMP bound to drivers: {:?}", driver_names);
			},
		}

		match hwid_bound_to_driver("VID_1D50&PID_6017", "USB") {
			Ok(driver_names) if driver_names.len() == 0 => {
				devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
				info!("Scheduling WinUSB driver installation for DFU mode BMP device...");
			},

			// If an error occurred checking, then install the driver just in case.
			Err(_e) => {
				devices_needing_driver.push(DFU_MODE_WDI_INFO.clone());
				info!("Scheduling WinUSB driver installation for DFU mode BMP device...");
			},

			Ok(driver_names) => {
				trace!("DFU mode BMP bound to drivers: {:?}", driver_names);
			},
		}
	}

	// If both drivers are installed already, there's nothing to do.
	if devices_needing_driver.len() == 0 {
		if explicitly_requested {
			println!("Drivers are already installed for BMP devices; nothing to do.");
		}
		return;
	}

	println!("The WinUSB driver needs to be installed for the Black Magic Probe device before continuing. Standby...");
	thread::sleep(Duration::from_secs(1));

	// If we're here, that means we're installing drivers.
	// So we need admin.
	let token = ProcessToken::for_current_process().expect("Unable to determine the current process's privilege level");
	let level = token
		.privilege_level()
		.expect("Unable to determine the current process's privilege level");

	let need_to_elevate = matches!(level, PrivilegeLevel::NotPrivileged | PrivilegeLevel::HighIntegrityAdmin);

	if !need_to_elevate {
		if let Some(pid) = parent_pid {
			match restore_cstdio(pid) {
				Ok(_) => (),
				Err(_e) => {
					// FIXME:
					todo!("Create a log file!");
				},
			}
		}

		admin_install_drivers(&mut devices_needing_driver);
		println!(
			"Successfully installed drivers for {} USB interfaces.",
			devices_needing_driver.len()
		);

		// TODO: use the Windows SetupAPI to get the device instance ID of the BMP so we can restart it and re-enumerate
		// it, if necessary. https://docs.microsoft.com/en-us/windows/win32/api/setupapi/nf-setupapi-setupdigetdeviceinstanceida

		// Now that we're done, nothing more to do in the admin process.
		std::process::exit(0);
	}

	// If we need to elevate, then we have to re-execute this process.

	// FIXME: this elevated-execution code should be cleaned up.

	let mut args: Vec<OsString> = Vec::with_capacity(env::args_os().len() + 1);
	args.extend(env::args_os().map(|s| s.to_owned()));
	args.push(OsString::from_str("--windows-wdi-install-mode").unwrap());

	use windows::Win32::Foundation::{HANDLE, HINSTANCE, HWND, WAIT_FAILED};
	use windows::Win32::System::Registry::HKEY;
	use windows::Win32::System::Threading::{GetExitCodeProcess, INFINITE, WaitForSingleObject};
	use windows::Win32::UI::Shell::{SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, SHELLEXECUTEINFOW_0, ShellExecuteExW};
	use windows::Win32::UI::WindowsAndMessaging::SW_HIDE;
	use windows_strings::PCWSTR;

	let verb: Vec<u16> = OsStr::new("runas").encode_wide().chain(iter::once(0)).collect();

	let mut args: Vec<OsString> = env::args_os().map(|s| s.to_owned()).collect();
	// Remove argv[0], as we're going to replace it with the full path to the process.
	let _ = args.remove(0);
	args.push(OsStr::new(&format!("--windows-wdi-install-mode={}", std::process::id())).to_owned());
	let file = os_str_to_null_terminated_vec(env::current_exe().unwrap().as_os_str());
	let parameters: OsString = args.join(OsStr::new(" "));
	let parameters = os_str_to_null_terminated_vec(&parameters);

	let cwd = os_str_to_null_terminated_vec(
		env::current_dir()
			.expect("Unable to get current working directory")
			.as_os_str(),
	);

	let mut info = SHELLEXECUTEINFOW {
		cbSize: mem::size_of::<SHELLEXECUTEINFOW>() as u32,
		fMask: SEE_MASK_NOCLOSEPROCESS,
		hwnd: HWND::default(),
		lpVerb: PCWSTR::from_raw(verb.as_ptr()),
		lpFile: PCWSTR::from_raw(file.as_ptr()),
		lpParameters: PCWSTR::from_raw(parameters.as_ptr()),
		lpDirectory: PCWSTR::from_raw(cwd.as_ptr()),
		nShow: SW_HIDE.0,
		hInstApp: HINSTANCE::default(),
		lpIDList: ptr::null_mut(),
		lpClass: PCWSTR::null(),
		hkeyClass: HKEY::default(),
		dwHotKey: 0,
		Anonymous: SHELLEXECUTEINFOW_0 {
			hMonitor: HANDLE::default(),
		},
		hProcess: HANDLE::default(),
	};

	unsafe { ShellExecuteExW(&mut info) }.expect("Error calling ShellExecuteExW()");

	if unsafe { WaitForSingleObject(info.hProcess, INFINITE) } == WAIT_FAILED {
		Err::<(), _>(IoError::last_os_error()).expect("Error calling WaitForSingleObject()");
	}
	std::thread::sleep(std::time::Duration::from_secs(5));

	let mut exit_code = 0;
	match unsafe { GetExitCodeProcess(info.hProcess, &mut exit_code) } {
		Ok(()) => {
			if exit_code != 0 {
				error!(
					"Elevated process exited with {}; driver installation probably failed",
					exit_code
				);
				std::process::exit(exit_code as i32);
			} else {
				info!("Exiting parent process. Elevated process exited successfully.");
			}
		},
		Err(error) => panic!("Error calling GetExitCodeProcess(): {}", error),
	}

	println!(
		"Driver installation should be complete. You may need to unplug the device and plug it back in before things \
		 work."
	);
}