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}