# wdi-rs - Windows Driver Installer for Rust
This crate provides a Rust API to [libwdi](https://github.com/pbatard/libwdi), the library behind [Zadiq](https://zadig.akeo.ie/), which provides simple Windows device driver installation. It is particularly useful for installing the WinUSB drivers for USB devices, allowing user-mode applications to communicate with them without needing to write a custom driver.
As well as exposing the libwdi primitives, this crate exposes a higher level `DriverInstaller` builder API which simplifies common use cases such as:
- installing a driver for a specific device by VID/PID
- enumerating devices and selecting one based on custom criteria
- using a custom INF file for driver installation, allowing more flexibility than the stock libwdi APIs.
This is a Windows specific crate, and currently only targets x86 64-bit.
## Features
- 🚀 High-level builder API for driver installation
- 🔍 USB device enumeration and discovery
- 📝 Custom INF file support (embedded or external)
- 🛡️ Type-safe bindings to libwdi
- 📋 Comprehensive logging support
- ✅ x86 64-bit support
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
wdi-rs = "0.1"
```
## Quick Start
### Install WinUSB driver for a specific device
The simplest use case - install WinUSB for a device by its VID/PID:
```rust
use wdi_rs::DriverInstaller;
fn main() -> Result<(), wdi_rs::Error> {
// Install WinUSB driver for device 1234:5678
// libwdi will automatically generate an appropriate INF file
DriverInstaller::for_device(0x1234, 0x5678)
.install()?;
println!("Driver installed successfully!");
Ok(())
}
```
### Using a custom INF file
For production use, you'll typically want to provide your own INF file, allowing you to custom more details of the driver installation:
```rust
use wdi_rs::DriverInstaller;
// Embed your INF file at compile time
const MY_DEVICE_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
fn main() -> Result<(), wdi_rs::Error> {
DriverInstaller::for_device(0x1234, 0x5678)
.with_inf_data(MY_DEVICE_INF, "..\\inf\\sample.inf")
.install()?;
Ok(())
}
```
### Enumerate devices before installation
If you need to discover or select devices interactively:
```rust
use wdi_rs::{create_list, CreateListOptions, DriverInstaller};
fn main() -> Result<(), wdi_rs::Error> {
// Enumerate all USB devices
let devices = create_list(CreateListOptions::default())?;
// Find your specific device
let device = devices.iter()
.find(|d| d.vid == 0x1234 && d.pid == 0x5678)
.ok_or(wdi_rs::Error::NotFound)?;
println!("Found device: {}", device);
// Install driver for this specific device
DriverInstaller::for_specific_device(device.clone())
.install()?;
Ok(())
}
```
### Advanced: Custom device selection
For more complex device selection logic:
```rust
use wdi_rs::{DriverInstaller, DeviceSelector};
fn main() -> Result<(), wdi_rs::Error> {
// Install driver for the first device matching a custom predicate
DriverInstaller::new(DeviceSelector::First(Box::new(|dev| {
dev.vid == 0x1234 &&
dev.desc.as_ref()
.map_or(false, |desc| desc.contains("My Custom Device"))
})))
.install()?;
Ok(())
}
```
### Complete example with error handling and logging
```rust
use wdi_rs::{DriverInstaller, DriverType, Error};
use log::{info, error};
const MY_DEVICE_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
fn install_driver(vid: u16, pid: u16) -> Result<(), Error> {
info!("Starting driver installation for {:04x}:{:04x}", vid, pid);
match DriverInstaller::for_device(vid, pid)
.with_inf_data(MY_DEVICE_INF, "..\\inf\\sample.inf")
.with_driver_type(DriverType::WinUsb)
.install()
{
Ok(device) => {
info!("Successfully installed driver for: {}", device);
Ok(())
}
Err(Error::Exists) => {
info!("Driver already installed");
Ok(())
}
Err(Error::NotFound) => {
error!("Device {:04x}:{:04x} not found", vid, pid);
Err(Error::NotFound)
}
Err(e) => {
error!("Failed to install driver: {}", e);
Err(e)
}
}
}
fn main() {
env_logger::init();
if let Err(e) = install_driver(0x1234, 0x5678) {
eprintln!("Installation failed: {}", e);
std::process::exit(1);
}
}
```
## Low-Level API
For cases where you need direct control, wdi-rs also exposes the low-level libwdi functions:
```rust
use wdi_rs::{create_list, prepare_driver, install_driver, CreateListOptions,
PrepareDriverOptions, InstallDriverOptions, DriverType};
fn main() -> Result<(), wdi_rs::Error> {
// Get device list
let devices = create_list(CreateListOptions {
list_all: true,
list_hubs: false,
trim_whitespaces: true,
})?;
let device = &devices.get(0).ok_or(wdi_rs::Error::NotFound)?;
// Prepare driver
let mut prepare_opts = PrepareDriverOptions::default();
prepare_opts.driver_type = DriverType::WinUsb;
prepare_driver(
device,
"C:\\drivers",
"C:\\drivers\\device.inf",
&prepare_opts,
)?;
// Install driver
install_driver(
device,
"C:\\drivers",
"C:\\drivers\\device.inf",
&InstallDriverOptions::default(),
)?;
Ok(())
}
```
## Platform Support
- **Windows 7+** (x64)
- Requires administrator privileges for driver installation
- This crate only compiles on Windows
## Architecture
wdi-rs consists of two layers:
1. **Low-level FFI bindings** to libwdi (`prepare_driver`, `install_driver`, etc.)
2. **High-level API** with `DriverInstaller` builder pattern
The high-level API is recommended for most use cases as it handles:
- Device enumeration and selection
- Temporary file management
- INF file handling
- Error propagation
- Safe cleanup
## Common Use Cases
### Command-line tool for driver installation
```rust
use wdi_rs::DriverInstaller;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
eprintln!("Usage: {} <VID> <PID>", args[0]);
std::process::exit(1);
}
let vid = u16::from_str_radix(&args[1], 16).unwrap();
let pid = u16::from_str_radix(&args[2], 16).unwrap();
DriverInstaller::for_device(vid, pid)
.install()
.expect("Failed to install driver");
}
```
### GUI application with device selection
```rust
use wdi_rs::{create_list, CreateListOptions, DriverInstaller};
fn list_devices() -> Result<Vec<String>, wdi_rs::Error> {
let devices = create_list(CreateListOptions::default())?;
Ok(devices.iter()
.map(|d| format!("{:04x}:{:04x} - {}", d.vid, d.pid,
d.desc.as_deref().unwrap_or("Unknown")))
.collect())
}
fn install_for_selected(vid: u16, pid: u16) -> Result<(), wdi_rs::Error> {
DriverInstaller::for_device(vid, pid).install()?;
Ok(())
}
```
### Installer package
Embed wdi-rs in your application's installer to automatically set up USB drivers:
```rust
use wdi_rs::DriverInstaller;
const DEVICE_INF: &[u8] = include_bytes!("..\\inf\\sample.inf");
fn setup_usb_driver() -> Result<(), Box<dyn std::error::Error>> {
println!("Setting up USB driver...");
DriverInstaller::for_device(0x1234, 0x5678)
.with_inf_data(DEVICE_INF, "device.inf")
.install()?;
println!("USB driver installed successfully!");
Ok(())
}
```
## Logging
wdi-rs uses the `log` crate for logging. Enable logging in your application:
```rust
use log::LevelFilter;
fn main() {
env_logger::Builder::from_default_env()
.filter_level(LevelFilter::Info)
.init();
// Also set libwdi's log level
wdi_rs::set_log_level(log::max_level().into()).ok();
// Your code here...
}
```
## Safety
This crate uses unsafe code to interface with the libwdi C library. All unsafe code is carefully reviewed and encapsulated behind safe APIs. The high-level `DriverInstaller` API is entirely safe Rust.
## License
MIT or Apache 2.0 License, at your option - see LICENSE file for details.
## Contributing
Contributions are welcome! Please open an issue or PR on GitHub.
## Credits
- [libwdi](https://github.com/pbatard/libwdi) by Pete Batard - the underlying C library
## See Also
- [libwdi documentation](https://github.com/pbatard/libwdi/wiki)
- [rusb](https://crates.io/crates/rusb) - USB library for Rust
- [nusb](https://crates.io/crates/nusb) - Modern USB library for Rust