wdi_rs/installer.rs
1// Copyright (C) 2025 Piers Finlayson <piers@piers.rocks>
2//
3// MIT License
4
5//! High-level API for installing USB drivers on Windows using libwdi.
6//!
7//! This module provides a builder-pattern interface for installing drivers,
8//! with support for custom INF files, device selection strategies, and
9//! comprehensive error handling.
10//!
11//! # Examples
12//!
13//! ## Simple VID/PID installation with embedded INF
14//!
15//! ```no_run
16//! use wdi_rs::{DriverInstaller, InfSource};
17//!
18//! const MY_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
19//!
20//! DriverInstaller::for_device(0x1234, 0x5678)
21//! .with_inf_data(MY_INF, "my_device.inf")
22//! .install()?;
23//! # Ok::<(), wdi_rs::Error>(())
24//! ```
25//!
26//! ## Installation with libwdi-generated INF
27//!
28//! ```no_run
29//! use wdi_rs::DriverInstaller;
30//!
31//! // Uses libwdi to generate the INF automatically
32//! DriverInstaller::for_device(0x1234, 0x5678)
33//! .install()?;
34//! # Ok::<(), wdi_rs::Error>(())
35//! ```
36//!
37//! ## Enumerate devices then install
38//!
39//! ```no_run
40//! use wdi_rs::{create_list, CreateListOptions, DriverInstaller};
41//!
42//! let devices = create_list(CreateListOptions::default())?;
43//! let device = devices.iter()
44//! .find(|d| d.vid == 0x1234 && d.pid == 0x5678)
45//! .expect("Device not found");
46//!
47//! DriverInstaller::for_specific_device(device.clone())
48//! .install()?;
49//! # Ok::<(), Box<dyn std::error::Error>>(())
50//! ```
51//!
52//! ## Custom device selection
53//!
54//! ```no_run
55//! use wdi_rs::{DriverInstaller, DeviceSelector};
56//!
57//! DriverInstaller::new(DeviceSelector::First(Box::new(|dev| {
58//! dev.vid == 0x1234 && dev.desc.as_ref().expect("Device description not found").contains("My Device")
59//! })))
60//! .install()?;
61//! # Ok::<(), wdi_rs::Error>(())
62//! ```
63
64use std::fmt;
65use std::fs;
66use std::path::PathBuf;
67use log::{debug, error, info, trace, warn};
68use tempfile::TempDir;
69
70// Import the low-level wdi types
71use crate::{
72 create_list, prepare_driver, install_driver,
73 CreateListOptions, PrepareDriverOptions, InstallDriverOptions,
74 Device, DriverType, Error as WdiError,
75};
76
77/// Strategy for selecting which USB device to install a driver for.
78pub enum DeviceSelector {
79 /// Select a device by USB Vendor ID and Product ID.
80 ///
81 /// If multiple devices match, the first one found will be used.
82 VidPid {
83 /// USB Vendor ID
84 vid: u16,
85 /// USB Product ID
86 pid: u16
87 },
88
89 /// Select the first device matching a predicate function.
90 ///
91 /// The predicate receives a reference to each device and returns `true`
92 /// if it should be selected.
93 First(Box<dyn Fn(&Device) -> bool>),
94
95 /// Use a specific device that was previously enumerated.
96 ///
97 /// This is useful when you've already called [`create_list`] and want
98 /// to install a driver for a specific device from that list.
99 Specific(Device),
100}
101
102impl fmt::Debug for DeviceSelector {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 match self {
105 Self::VidPid { vid, pid } => write!(f, "VidPid({:04x}:{:04x})", vid, pid),
106 Self::First(_) => write!(f, "First(<predicate>)"),
107 Self::Specific(dev) => write!(f, "Specific({})", dev),
108 }
109 }
110}
111
112/// Source for the INF file used during driver installation.
113#[derive(Clone)]
114pub enum InfSource {
115 /// Use an embedded INF file from memory.
116 ///
117 /// The data will be written to a temporary directory during installation.
118 Embedded {
119 /// Raw INF file contents
120 data: Vec<u8>,
121 /// Filename to use when writing the INF file
122 filename: String
123 },
124
125 /// Use an existing INF file from the filesystem.
126 External {
127 /// Path to the INF file
128 path: PathBuf
129 },
130
131 /// Let libwdi generate the INF file automatically.
132 ///
133 /// This is the default and simplest option if you don't need
134 /// custom INF file contents.
135 Generated,
136}
137
138impl fmt::Debug for InfSource {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::Embedded { data, filename } =>
142 write!(f, "Embedded({} bytes, {})", data.len(), filename),
143 Self::External { path } =>
144 write!(f, "External({})", path.display()),
145 Self::Generated =>
146 write!(f, "Generated"),
147 }
148 }
149}
150
151impl Default for InfSource {
152 fn default() -> Self {
153 Self::Generated
154 }
155}
156
157/// Options for driver installation.
158///
159/// Wraps the low-level [`PrepareDriverOptions`] and [`InstallDriverOptions`]
160/// with sensible defaults.
161#[derive(Debug, Clone)]
162pub struct InstallOptions {
163 /// Options for driver preparation phase
164 pub prepare_opts: PrepareDriverOptions,
165 /// Options for driver installation phase
166 pub install_opts: InstallDriverOptions,
167}
168
169impl Default for InstallOptions {
170 fn default() -> Self {
171 Self {
172 prepare_opts: PrepareDriverOptions::default(),
173 install_opts: InstallDriverOptions::default(),
174 }
175 }
176}
177
178/// High-level builder for installing USB drivers.
179///
180/// This provides a fluent interface for configuring and executing driver
181/// installation operations. Use one of the constructor methods to create
182/// an instance, configure it with the builder methods, then call [`install`]
183/// to perform the installation.
184///
185/// [`install`]: DriverInstaller::install
186///
187/// # Examples
188///
189/// ```no_run
190/// use wdi_rs::{DriverInstaller, DriverType};
191///
192/// DriverInstaller::for_device(0x1234, 0x5678)
193/// .with_driver_type(DriverType::WinUsb)
194/// .install()?;
195/// # Ok::<(), wdi_rs::Error>(())
196/// ```
197pub struct DriverInstaller {
198 device_selector: DeviceSelector,
199 driver_type: DriverType,
200 inf_source: InfSource,
201 options: InstallOptions,
202}
203
204impl DriverInstaller {
205 /// Create a new installer with a custom device selector.
206 ///
207 /// For most cases, prefer [`for_device`] or [`for_specific_device`].
208 ///
209 /// [`for_device`]: DriverInstaller::for_device
210 /// [`for_specific_device`]: DriverInstaller::for_specific_device
211 ///
212 /// # Examples
213 ///
214 /// ```no_run
215 /// use wdi_rs::{DriverInstaller, DeviceSelector};
216 ///
217 /// let installer = DriverInstaller::new(
218 /// DeviceSelector::First(Box::new(|dev| {
219 /// dev.vid == 0x1234 && dev.desc.as_ref()
220 /// .map_or(false, |m| m.contains("ACME"))
221 /// }))
222 /// );
223 /// ```
224 pub fn new(device_selector: DeviceSelector) -> Self {
225 debug!("Creating DriverInstaller with selector: {:?}", device_selector);
226 Self {
227 device_selector,
228 driver_type: DriverType::WinUsb,
229 inf_source: InfSource::default(),
230 options: InstallOptions::default(),
231 }
232 }
233
234 /// Create an installer for a device with the specified VID and PID.
235 ///
236 /// If multiple devices match, the first one found will be used.
237 ///
238 /// # Examples
239 ///
240 /// ```no_run
241 /// use wdi_rs::DriverInstaller;
242 ///
243 /// let installer = DriverInstaller::for_device(0x1234, 0x5678);
244 /// ```
245 pub fn for_device(vid: u16, pid: u16) -> Self {
246 info!("Creating installer for VID:PID {:04x}:{:04x}", vid, pid);
247 Self::new(DeviceSelector::VidPid { vid, pid })
248 }
249
250 /// Create an installer for a specific device.
251 ///
252 /// This is useful when you've already enumerated devices with [`create_list`]
253 /// and want to install a driver for a specific one.
254 ///
255 /// # Examples
256 ///
257 /// ```no_run
258 /// use wdi_rs::{create_list, CreateListOptions, DriverInstaller};
259 ///
260 /// let devices = create_list(CreateListOptions::default()).expect("Failed to list devices");
261 /// let device = &devices.get(0).expect("No devices found");
262 /// let installer = DriverInstaller::for_specific_device(device.clone());
263 /// ```
264 pub fn for_specific_device(device: Device) -> Self {
265 info!("Creating installer for specific device: {}", device);
266 Self::new(DeviceSelector::Specific(device))
267 }
268
269 /// Set the INF source to embedded data.
270 ///
271 /// The provided data will be written to a temporary file during installation.
272 ///
273 /// # Examples
274 ///
275 /// ```no_run
276 /// use wdi_rs::DriverInstaller;
277 ///
278 /// const INF_DATA: &[u8] = include_bytes!("..\\inf\\sample.inf");
279 ///
280 /// let installer = DriverInstaller::for_device(0x1234, 0x5678)
281 /// .with_inf_data(INF_DATA, "my_device.inf");
282 /// ```
283 pub fn with_inf_data(mut self, data: &[u8], filename: impl Into<String>) -> Self {
284 let filename = filename.into();
285 debug!("Setting INF source to embedded data: {} ({} bytes)", filename, data.len());
286 self.inf_source = InfSource::Embedded {
287 data: data.to_vec(),
288 filename,
289 };
290 self
291 }
292
293 /// Set the INF source to an external file.
294 ///
295 /// The file must exist and be readable at installation time.
296 ///
297 /// # Examples
298 ///
299 /// ```no_run
300 /// use wdi_rs::DriverInstaller;
301 /// use std::path::PathBuf;
302 ///
303 /// let installer = DriverInstaller::for_device(0x1234, 0x5678)
304 /// .with_inf_file(PathBuf::from("C:\\drivers\\my_device.inf"));
305 /// ```
306 pub fn with_inf_file(mut self, path: PathBuf) -> Self {
307 debug!("Setting INF source to external file: {}", path.display());
308 self.inf_source = InfSource::External { path };
309 self
310 }
311
312 /// Set the driver type to install.
313 ///
314 /// Defaults to [`DriverType::WinUsb`] if not specified.
315 ///
316 /// # Examples
317 ///
318 /// ```no_run
319 /// use wdi_rs::{DriverInstaller, DriverType};
320 ///
321 /// let installer = DriverInstaller::for_device(0x1234, 0x5678)
322 /// .with_driver_type(DriverType::LibUsb0);
323 /// ```
324 pub fn with_driver_type(mut self, driver_type: DriverType) -> Self {
325 debug!("Setting driver type to: {:?}", driver_type);
326 self.driver_type = driver_type;
327 self
328 }
329
330 /// Set custom options for the driver preparation phase.
331 ///
332 /// Note: The `external_inf` field will be automatically set based on
333 /// the [`InfSource`] and any value you set will be overridden. A warning
334 /// will be logged if you attempt to set it.
335 ///
336 /// # Examples
337 ///
338 /// ```no_run
339 /// use wdi_rs::{DriverInstaller, PrepareDriverOptions};
340 ///
341 /// let mut opts = PrepareDriverOptions::default();
342 /// // Configure opts as needed...
343 ///
344 /// let installer = DriverInstaller::for_device(0x1234, 0x5678)
345 /// .with_prepare_options(opts);
346 /// ```
347 pub fn with_prepare_options(mut self, opts: PrepareDriverOptions) -> Self {
348 debug!("Setting custom prepare options");
349 self.options.prepare_opts = opts;
350 self
351 }
352
353 /// Set custom options for the driver installation phase.
354 ///
355 /// # Examples
356 ///
357 /// ```no_run
358 /// use wdi_rs::{DriverInstaller, InstallDriverOptions};
359 ///
360 /// let mut opts = InstallDriverOptions::default();
361 /// // Configure opts as needed...
362 ///
363 /// let installer = DriverInstaller::for_device(0x1234, 0x5678)
364 /// .with_install_options(opts);
365 /// ```
366 pub fn with_install_options(mut self, opts: InstallDriverOptions) -> Self {
367 debug!("Setting custom install options");
368 self.options.install_opts = opts;
369 self
370 }
371
372 /// Perform the driver installation.
373 ///
374 /// This will:
375 /// 1. Find the target device (if not already specified)
376 /// 2. Check if a driver is already installed
377 /// 3. Prepare the driver files
378 /// 4. Install the driver
379 ///
380 /// Returns the [`Device`] for the device that was installed.
381 ///
382 /// # Errors
383 ///
384 /// Returns an error if:
385 /// - The device cannot be found
386 /// - A non-WinUSB driver is already installed
387 /// - Driver preparation fails
388 /// - Driver installation fails
389 /// - File I/O operations fail
390 ///
391 /// # Examples
392 ///
393 /// ```no_run
394 /// use wdi_rs::DriverInstaller;
395 ///
396 /// let device = DriverInstaller::for_device(0x1234, 0x5678)
397 /// .install()?;
398 ///
399 /// println!("Installed driver for device: {}", device);
400 /// # Ok::<(), wdi_rs::Error>(())
401 /// ```
402 pub fn install(self) -> Result<Device, WdiError> {
403 info!("Starting driver installation");
404 debug!("Configuration: selector={:?}, driver_type={:?}, inf_source={:?}",
405 self.device_selector, self.driver_type, self.inf_source);
406
407 let device = self.find_device()?;
408 self.check_existing_driver(&device)?;
409 self.prepare_and_install(device)
410 }
411
412 /// Find the target device based on the selector.
413 fn find_device(&self) -> Result<Device, WdiError> {
414 debug!("Finding target device");
415
416 match &self.device_selector {
417 DeviceSelector::Specific(device) => {
418 debug!("Using pre-selected device: {}", device);
419 Ok(device.clone())
420 }
421
422 DeviceSelector::VidPid { vid, pid } => {
423 debug!("Enumerating USB devices");
424 let opts = CreateListOptions {
425 list_all: true,
426 list_hubs: false,
427 trim_whitespaces: true,
428 };
429
430 let devices = create_list(opts)?;
431 trace!("Found {} USB devices", devices.len());
432
433 if devices.is_empty() {
434 error!("No USB devices found on the system");
435 return Err(WdiError::NotFound);
436 }
437
438 let matching: Vec<_> = devices.iter()
439 .filter(|d| d.vid == *vid && d.pid == *pid)
440 .collect();
441
442 if matching.is_empty() {
443 error!("No USB devices found with VID:PID {:04x}:{:04x}", vid, pid);
444 return Err(WdiError::NotFound);
445 }
446
447 if matching.len() > 1 {
448 warn!("Multiple USB devices found with VID:PID {:04x}:{:04x}", vid, pid);
449 info!("Using first device found");
450 }
451
452 let device = matching[0].clone();
453 info!("Found target device: {}", device);
454 Ok(device)
455 }
456
457 DeviceSelector::First(predicate) => {
458 debug!("Enumerating USB devices with predicate filter");
459 let opts = CreateListOptions {
460 list_all: true,
461 list_hubs: false,
462 trim_whitespaces: true,
463 };
464
465 let devices = create_list(opts)?;
466 trace!("Found {} USB devices", devices.len());
467
468 if devices.is_empty() {
469 error!("No USB devices found on the system");
470 return Err(WdiError::NotFound);
471 }
472
473 let device = devices.iter()
474 .find(|d| predicate(d))
475 .ok_or_else(|| {
476 error!("No device matched the predicate");
477 WdiError::NotFound
478 })?
479 .clone();
480
481 info!("Found target device: {}", device);
482 Ok(device)
483 }
484 }
485 }
486
487 /// Check if the device already has a driver installed.
488 fn check_existing_driver(&self, device: &Device) -> Result<(), WdiError> {
489 debug!("Checking existing driver for device: {}", device);
490
491 if let Some(driver) = &device.driver {
492 if driver.starts_with("WinUSB") {
493 info!("Device already has WinUSB driver installed - nothing to do");
494 return Err(WdiError::Exists);
495 } else {
496 error!("Device already has a non-WinUSB driver installed: {}", driver);
497 error!("Cannot replace existing driver - manual uninstall required");
498 return Err(WdiError::Exists);
499 }
500 }
501
502 debug!("Device has no driver installed - proceeding");
503 Ok(())
504 }
505
506 /// Prepare and install the driver.
507 fn prepare_and_install(mut self, device: Device) -> Result<Device, WdiError> {
508 info!("Preparing and installing driver for device: {}", device);
509
510 // Determine if we need external INF and set up paths
511 let (driver_path, inf_path, _temp_dir) = match &self.inf_source {
512 InfSource::Embedded { data, filename } => {
513 debug!("Setting up embedded INF file");
514 let temp_dir = TempDir::new()
515 .map_err(|e| {
516 error!("Failed to create temporary directory: {}", e);
517 WdiError::Resource
518 })?;
519
520 let driver_path = temp_dir.path().to_str()
521 .ok_or_else(|| {
522 error!("Failed to get temporary directory path");
523 WdiError::InvalidParam
524 })?
525 .to_string();
526
527 let inf_file_path = temp_dir.path().join(filename);
528 debug!("Writing INF file to: {}", inf_file_path.display());
529
530 fs::write(&inf_file_path, data)
531 .map_err(|e| {
532 error!("Failed to write INF file: {}", e);
533 WdiError::Resource
534 })?;
535
536 let inf_path = inf_file_path.to_str()
537 .ok_or_else(|| {
538 error!("Failed to convert INF path to string");
539 WdiError::InvalidParam
540 })?
541 .to_string();
542
543 info!("INF file written successfully");
544 (driver_path, inf_path, Some(temp_dir))
545 }
546
547 InfSource::External { path } => {
548 debug!("Using external INF file: {}", path.display());
549
550 if !path.exists() {
551 error!("External INF file does not exist: {}", path.display());
552 return Err(WdiError::NotFound);
553 }
554
555 let driver_path = path.parent()
556 .ok_or_else(|| {
557 error!("Invalid external INF path - no parent directory");
558 WdiError::InvalidParam
559 })?
560 .to_str()
561 .ok_or_else(|| {
562 error!("Failed to convert driver path to string");
563 WdiError::InvalidParam
564 })?
565 .to_string();
566
567 let inf_path = path.to_str()
568 .ok_or_else(|| {
569 error!("Failed to convert INF path to string");
570 WdiError::InvalidParam
571 })?
572 .to_string();
573
574 (driver_path, inf_path, None)
575 }
576
577 InfSource::Generated => {
578 debug!("Using libwdi-generated INF file");
579 let temp_dir = TempDir::new()
580 .map_err(|e| {
581 error!("Failed to create temporary directory: {}", e);
582 WdiError::Resource
583 })?;
584
585 let driver_path = temp_dir.path().to_str()
586 .ok_or_else(|| {
587 error!("Failed to get temporary directory path");
588 WdiError::InvalidParam
589 })?
590 .to_string();
591
592 // For generated INF, libwdi will create it
593 let inf_path = format!("{}\\generated.inf", driver_path);
594
595 (driver_path, inf_path, Some(temp_dir))
596 }
597 };
598
599 // Set external_inf based on INF source, warning if user tried to set it
600 let should_use_external_inf = !matches!(self.inf_source, InfSource::Generated);
601
602 if self.options.prepare_opts.external_inf != should_use_external_inf {
603 warn!("Overriding prepare_opts.external_inf (was {}, setting to {}) based on InF source",
604 self.options.prepare_opts.external_inf, should_use_external_inf);
605 }
606
607 self.options.prepare_opts.external_inf = should_use_external_inf;
608 self.options.prepare_opts.driver_type = self.driver_type;
609
610 // Prepare the driver
611 debug!("Preparing driver in: {}", driver_path);
612 debug!("INF path: {}", inf_path);
613
614 prepare_driver(
615 &device,
616 &driver_path,
617 &inf_path,
618 &self.options.prepare_opts,
619 ).map_err(|e| {
620 error!("Failed to prepare driver: {}", e);
621 e
622 })?;
623
624 info!("Driver prepared successfully");
625
626 // Install the driver
627 debug!("Installing driver");
628
629 install_driver(
630 &device,
631 &driver_path,
632 &inf_path,
633 &self.options.install_opts,
634 ).map_err(|e| {
635 error!("Failed to install driver: {}", e);
636 e
637 })?;
638
639 info!("Driver installed successfully");
640
641 // Keep temp_dir alive until here so it doesn't get cleaned up prematurely
642 drop(_temp_dir);
643
644 Ok(device)
645 }
646}
647
648impl fmt::Debug for DriverInstaller {
649 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
650 f.debug_struct("DriverInstaller")
651 .field("device_selector", &self.device_selector)
652 .field("driver_type", &self.driver_type)
653 .field("inf_source", &self.inf_source)
654 .finish()
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661
662 #[test]
663 fn test_device_selector_vid_pid() {
664 let installer = DriverInstaller::for_device(0x1234, 0x5678);
665 match installer.device_selector {
666 DeviceSelector::VidPid { vid, pid } => {
667 assert_eq!(vid, 0x1234);
668 assert_eq!(pid, 0x5678);
669 }
670 _ => panic!("Wrong selector type"),
671 }
672 }
673
674 #[test]
675 fn test_builder_pattern() {
676 let installer = DriverInstaller::for_device(0x1234, 0x5678)
677 .with_driver_type(DriverType::LibUsb0)
678 .with_inf_data(b"test data", "test.inf");
679
680 assert!(matches!(installer.driver_type, DriverType::LibUsb0));
681 assert!(matches!(installer.inf_source, InfSource::Embedded { .. }));
682 }
683
684 #[test]
685 fn test_default_inf_source() {
686 let installer = DriverInstaller::for_device(0x1234, 0x5678);
687 assert!(matches!(installer.inf_source, InfSource::Generated));
688 }
689}