一、 项目目标
编写一个 Windows 客户端程序,双击运行后静默生成 设备信息登记表.xlsx,自动提取硬件信息,并通过红色底纹提示使用者需要人工补充或排查自动识别失败的数据。
技术栈: Rust (2021 Edition) + sysinfo + wmi + rust_xlsxwriter
兼容性: Win10 / Win11 (64位)
二、 项目目录结构
text
system_info_excel/
├── Cargo.toml # 项目依赖配置文件
├── src/
│ └── main.rs # 核心逻辑代码
└── target/ # (编译后自动生成,包含 release 版本的 .exe)
三、 依赖配置文件 (Cargo.toml)
toml
[package]
name = "system_info_excel"
version = "1.0.0"
edition = "2021"
[dependencies]
sysinfo = "0.30" # 获取 CPU、内存、网络、操作系统
wmi = "0.13" # 获取底层硬件信息(如硬盘序列号、显卡、SN)
serde = { version = "1.0", features = ["derive"] } # wmi 的反序列化依赖
rust_xlsxwriter = "0.68" # 生成 Excel 表格
四、 核心逻辑代码 (src/main.rs)
rust
// 隐藏控制台黑框,双击直接运行
#![windows_subsystem = "windows"]
use rust_xlsxwriter::{Color, Format, FormatAlign, Workbook};
use serde::Deserialize;
use std::fs;
use sysinfo::{CpuExt, Networks, System, SystemExt};
// ================= WMI 数据结构定义 =================
#[derive(Deserialize)]
struct Win32_ComputerSystem {
Manufacturer: String,
Model: String,
}
#[derive(Deserialize)]
struct Win32_BIOS {
SerialNumber: Option<String>,
}
#[derive(Deserialize)]
struct Win32_PhysicalMedia {
SerialNumber: Option<String>,
}
#[derive(Deserialize)]
struct Win32_DiskDrive {
Size: Option<u64>,
}
#[derive(Deserialize)]
struct Win32_VideoController {
Name: String,
}
// ================= 错误处理辅助函数 =================
fn save_error_to_file(msg: &str) {
let _ = fs::write("System_Info_Error.log", msg);
}
// ================= 核心主函数 =================
fn main() {
let mut sys = System::new_all();
sys.refresh_all();
let networks = Networks::new_with_refreshed_list();
let wmi_con = match WMIConnection::new() {
Ok(con) => con,
Err(e) => {
save_error_to_file(&format!("WMI 连接失败: {:?}", e));
return;
}
};
// 1. 获取品牌及型号
let brand_model = match wmi_con.query::<Vec<Win32_ComputerSystem>>() {
Ok(data) if !data.is_empty() => format!("{} {}", data[0].Manufacturer, data[0].Model),
_ => "未知".to_string(),
};
// 2. 获取设备SN(放最后一列)
let device_sn = match wmi_con.query::<Vec<Win32_BIOS>>() {
Ok(data) if !data.is_empty() => {
data[0].SerialNumber.clone().unwrap_or_else(|| "未知".to_string())
}
_ => "未知".to_string(),
};
// 3. 获取 CPU
let cpu = sys.cpus().first().map(|c| c.brand().trim().to_string()).unwrap_or_else(|| "未知".to_string());
// 4. 获取内存 (GB)
let total_ram_gb = (sys.total_memory() as f64) / 1024.0 / 1024.0;
let ram = format!("{:.1} GB", total_ram_gb);
// 5. 获取显卡 (多个显卡用 + 隔离)
let gpu = match wmi_con.query::<Vec<Win32_VideoController>>() {
Ok(gpus) if !gpus.is_empty() => {
gpus.iter().map(|g| g.Name.clone()).collect::<Vec<String>>().join(" + ")
}
_ => "未知".to_string(),
};
// 6. 获取硬盘容量 (多个用 + 隔离)
let disk_cap = match wmi_con.query::<Vec<Win32_DiskDrive>>() {
Ok(disks) if !disks.is_empty() => {
disks.iter()
.map(|d| {
let size_gb = d.Size.unwrap_or(0) as f64 / 1_073_741_824.0;
format!("{:.1} GB", size_gb)
})
.collect::<Vec<String>>()
.join(" + ")
}
_ => "未知".to_string(),
};
// 7. 获取硬盘序列号 (多个用 换行符 隔离)
let disk_serial = match wmi_con.query::<Vec<Win32_PhysicalMedia>>() {
Ok(media) if !media.is_empty() => {
let serials: Vec<String> = media
.iter()
.filter_map(|m| m.SerialNumber.clone())
.map(|s| s.replace('*', "").trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if serials.is_empty() {
"未知".to_string()
} else {
serials.join("\n")
}
}
_ => "未知".to_string(),
};
// 8. 获取 MAC 地址
let mut mac_list: Vec<String> = Vec::new();
for (name, data) in &networks {
let mac = data.mac_address().to_string();
if mac == "00:00:00:00:00:00" || name.to_lowercase().contains("loopback") {
continue;
}
let formatted_mac = mac.replace(':', "-").to_uppercase();
let lower_name = name.to_lowercase();
// 判断网卡类型加中文后缀
let entry = if lower_name.contains("wi-fi") || lower_name.contains("wlan") || lower_name.contains("wireless") {
format!("{} 无线", formatted_mac)
} else if lower_name.contains("bluetooth") || lower_name.contains("蓝牙") {
format!("{} 蓝牙", formatted_mac)
} else if lower_name.contains("tap") || lower_name.contains("virtual") || lower_name.contains("tunnel") {
continue; // 排除虚拟网卡
} else {
formatted_mac
};
mac_list.push(entry);
}
let mac_addr = if mac_list.is_empty() { "未知".to_string() } else { mac_list.join("\n") };
// 9. 获取操作系统及版本
let os_version = sys.long_os_version().unwrap_or_else(|| "未知".to_string());
// ================= 生成 Excel =================
let mut workbook = Workbook::new();
// 普通表头格式
let header_format = Format::new().set_bold().set_align(FormatAlign::Center);
// 红色提醒格式(用于人工填写列,或者自动识别失败的值)
let red_alert_format = Format::new()
.set_align(FormatAlign::Center)
.set_background_color(Color::RGB(0xFFC7CE)) // 浅红色背景
.set_font_color(Color::RGB(0x9C0006)); // 深红色字体
let worksheet = workbook.add_worksheet();
// 完整表头(20列)
let headers = [
"序号", "设备编号", "设备名称", "部门", "责任人",
"品牌及型号", "CPU", "内存", "显卡", "硬盘容量", "硬盘序列号",
"MAC地址", "操作系统及版本", "操作系统安装时间", "密级", "用途",
"放置地点", "使用情况", "购机日期(启用日期)", "设备SN"
];
// 写入表头
for (col, header) in headers.iter().enumerate() {
let _ = worksheet.write_with_format(0, col as u16, *header, &header_format);
}
// 辅助函数:判断如果数据是"未知"或空,则标红提示
fn write_with_red_if_fail(
worksheet: &rust_xlsxwriter::Worksheet,
row: u32,
col: u16,
value: &str,
red_format: &Format,
) {
if value.is_empty() || value == "未知" {
let _ = worksheet.write_with_format(row, col, value, red_format);
} else {
let _ = worksheet.write(row, col, value);
}
}
// 填写数据行
let _ = worksheet.write(1, 0, 1); // 序号默认填1
// 自动填入列(识别失败则套用红色格式)
write_with_red_if_fail(&worksheet, 1, 5, &brand_model, &red_alert_format); // 品牌及型号
write_with_red_if_fail(&worksheet, 1, 6, &cpu, &red_alert_format); // CPU
write_with_red_if_fail(&worksheet, 1, 7, &ram, &red_alert_format); // 内存
write_with_red_if_fail(&worksheet, 1, 8, &gpu, &red_alert_format); // 显卡
write_with_red_if_fail(&worksheet, 1, 9, &disk_cap, &red_alert_format); // 硬盘容量
write_with_red_if_fail(&worksheet, 1, 10, &disk_serial, &red_alert_format); // 硬盘序列号
write_with_red_if_fail(&worksheet, 1, 11, &mac_addr, &red_alert_format); // MAC地址
write_with_red_if_fail(&worksheet, 1, 12, &os_version, &red_alert_format); // 操作系统及版本
write_with_red_if_fail(&worksheet, 1, 19, &device_sn, &red_alert_format); // 设备SN放最后
// 默认填入固定值的字段
let _ = worksheet.write(1, 13, "-"); // 操作系统安装时间
let _ = worksheet.write(1, 14, "互联网"); // 密级
let _ = worksheet.write(1, 15, "办公"); // 用途
let _ = worksheet.write(1, 17, "在用"); // 使用情况
// 必须人工填写的列,保持空字符串,并强制加红底提醒(设备编号、设备名称、部门、责任人、放置地点、购机日期)
for col in [1, 2, 3, 4, 16, 18] {
let _ = worksheet.write_with_format(1, col, "", &red_alert_format);
}
// 设置列宽,优化阅读体验
let _ = worksheet.set_column_width(0, 6); // 序号
let _ = worksheet.set_column_width(1, 12); // 设备编号
let _ = worksheet.set_column_width(2, 15); // 设备名称
let _ = worksheet.set_column_width(3, 12); // 部门
let _ = worksheet.set_column_width(4, 12); // 责任人
let _ = worksheet.set_column_width(5, 25); // 品牌及型号
let _ = worksheet.set_column_width(6, 30); // CPU
let _ = worksheet.set_column_width(7, 15); // 内存
let _ = worksheet.set_column_width(8, 35); // 显卡
let _ = worksheet.set_column_width(9, 25); // 硬盘容量
let _ = worksheet.set_column_width(10, 35); // 硬盘序列号
let _ = worksheet.set_column_width(11, 35); // MAC地址
let _ = worksheet.set_column_width(12, 30); // 操作系统及版本
let _ = worksheet.set_column_width(13, 20); // 操作系统安装时间
let _ = worksheet.set_column_width(14, 10); // 密级
let _ = worksheet.set_column_width(15, 15); // 用途
let _ = worksheet.set_column_width(16, 20); // 放置地点
let _ = worksheet.set_column_width(17, 15); // 使用情况
let _ = worksheet.set_column_width(18, 25); // 购机日期
let _ = worksheet.set_column_width(19, 25); // 设备SN
// 保存文件
if let Err(e) = workbook.save("设备信息登记表.xlsx") {
save_error_to_file(&format!("保存 Excel 失败: {:?}", e));
}
}
五、 编译与使用说明
- 环境准备 :确保电脑安装了 Rust 开发环境(
rustup),并且安装了 Visual Studio 2022 的 C++ 桌面开发环境(用于 MSVC 链接器)。 - 创建项目 :在终端运行
cargo new system_info_excel并进入目录。 - 替换文件 :将上面的代码分别填入
Cargo.toml和src/main.rs。 - 编译 :运行
cargo build --release。 - 分发 :在
target/release/目录下找到system_info_excel.exe,把这个文件发给使用 Win10/11 的同事。同事双击后,同一个目录下会直接生成填好硬件信息的设备信息登记表.xlsx。如果运行中遇到 WMI 权限问题或提取失败,会自动生成System_Info_Error.log日志文件供排查。