diff --git a/apps/desktop/src-tauri/src/agent.rs b/apps/desktop/src-tauri/src/agent.rs index 0ae55bb..c469b58 100644 --- a/apps/desktop/src-tauri/src/agent.rs +++ b/apps/desktop/src-tauri/src/agent.rs @@ -303,9 +303,15 @@ fn build_inventory_metadata(system: &System) -> serde_json::Value { }; if needs_os_info { let mut osmap = serde_json::Map::new(); - if let Some(name) = System::name() { osmap.insert("ProductName".into(), json!(name)); } - if let Some(ver) = System::os_version() { osmap.insert("Version".into(), json!(ver)); } - if let Some(build) = System::kernel_version() { osmap.insert("BuildNumber".into(), json!(build)); } + if let Some(name) = System::name() { + osmap.insert("ProductName".into(), json!(name)); + } + if let Some(ver) = System::os_version() { + osmap.insert("Version".into(), json!(ver)); + } + if let Some(build) = System::kernel_version() { + osmap.insert("BuildNumber".into(), json!(build)); + } win.insert("osInfo".into(), serde_json::Value::Object(osmap)); } } @@ -324,10 +330,7 @@ fn build_inventory_metadata(system: &System) -> serde_json::Value { // Normalização de software/serviços no topo do inventário if let Some(obj) = inventory.as_object_mut() { - let extended_snapshot = obj - .get("extended") - .and_then(|v| v.as_object()) - .cloned(); + let extended_snapshot = obj.get("extended").and_then(|v| v.as_object()).cloned(); // Merge software let mut software: Vec = Vec::new(); if let Some(existing) = obj.get("software").and_then(|v| v.as_array()) { @@ -676,6 +679,28 @@ fn collect_windows_extended() -> serde_json::Value { use std::os::windows::process::CommandExt; use std::process::Command; const CREATE_NO_WINDOW: u32 = 0x08000000; + + fn parse_powershell_json(bytes: &[u8]) -> Option { + if bytes.is_empty() { + return None; + } + if bytes.starts_with(&[0xFF, 0xFE]) { + // PowerShell usa UTF-16LE por padrão no Windows; converter para UTF-8. + let data = &bytes[2..]; + if data.len() % 2 != 0 { + return None; + } + let utf16: Vec = data + .chunks_exact(2) + .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) + .collect(); + let text = String::from_utf16(&utf16).ok()?; + serde_json::from_str(&text).ok() + } else { + serde_json::from_slice(bytes).ok() + } + } + fn ps(cmd: &str) -> Option { let ps_cmd = format!( "$ErrorActionPreference='SilentlyContinue'; {} | ConvertTo-Json -Depth 4 -Compress", @@ -694,7 +719,7 @@ fn collect_windows_extended() -> serde_json::Value { if out.stdout.is_empty() { return None; } - serde_json::from_slice::(&out.stdout).ok() + parse_powershell_json(&out.stdout) } let software = ps(r#"@(Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'; Get-ItemProperty 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') | Where-Object { $_.DisplayName } | Select-Object DisplayName, DisplayVersion, Publisher"#) @@ -1067,9 +1092,7 @@ mod windows_tests { let os_info_obj = expect_object(os_info, "windows.osInfo"); let is_activated = os_info_obj.get("IsActivated").unwrap_or_else(|| { - panic!( - "campo IsActivated ausente em windows.osInfo: {os_info_obj:?}" - ) + panic!("campo IsActivated ausente em windows.osInfo: {os_info_obj:?}") }); assert!( is_activated.as_bool().is_some(), @@ -1077,9 +1100,7 @@ mod windows_tests { ); let license_status = os_info_obj.get("LicenseStatus").unwrap_or_else(|| { - panic!( - "campo LicenseStatus ausente em windows.osInfo: {os_info_obj:?}" - ) + panic!("campo LicenseStatus ausente em windows.osInfo: {os_info_obj:?}") }); assert!( license_status.as_i64().is_some(),