Repository: GuillaumeGomez/sysinfo (language: rust)

Issue:
Unable to Retrieve User Groups on Windows
win11
sysinfo = "0.30.7"

        let mut groups = Groups::new();
		groups.refresh_list();
        for group in groups.list() {
            println!("{}", group.name());
        }



Discussion / hints:
Can you enable debug logs with `--features=debug` and see what's failing?
## Here is what is shown after enable the debug feature.
### It seems that sysinfo_debug macro is never called?
![image](https://github.com/user-attachments/assets/115a053d-18bd-4a31-a801-a2a6f8bee8ba)

![image](https://github.com/user-attachments/assets/ac480ac4-d28d-4d25-bb76-7905de8b1865)

Well, the code is [here](https://github.com/GuillaumeGomez/sysinfo/blob/0.30/src/windows/groups.rs#L61) (branch `0.30`, not `master`!). Don't hesitate to add more debug to see if you can figure out what's wrong...
I am working on a new repo and the branch name is set to `master` by default.

![image](https://github.com/user-attachments/assets/761ef850-9e0a-470e-85ac-310f604cfc0e)

The parse result of **to_str(entry.grpi0_name)** is "None"
> I am working on a new repo and the branch name is set to `master` by default.

I meant the `sysinfo` branch. In case you wanted to work with the code I linked. But working on `master` is fine as well.

> The parse result of **to_str(entry.grpi0_name)** is "None"

`to_str` never returns `None` since it returns a `String`, not an `Option`.
But the parse result is the `string literal` "None", not the Option None, which makes me confused
Oh. What the hell XD
We also ran into similar problems. First, we are not getting local groups. Only global groups. I tried to do some debugging.

```
---- common::user::tests::check_groups stdout ----
... GROUP_INFO_1 { grpi1_name: PWSTR(0x2025bea70e8), grpi1_comment: PWSTR(0x2025bea70f2) } ...
Searching for group Group { inner: GroupInner { id: Gid(0), name: "None" } }
```

Here is output of `net user` 

```powershell
$ net user dilawar 
.
.
.
Logon hours allowed          All                                                                                                                                                                                                 
Local Group Memberships      *Administrators       *IIS_IUSRS       *Users
Global Group memberships     *None
The command completed successfully.  
```


So `Ok("None")` output in function seems to point that we are fetching Global group memberships only and not the local group memberships.

A more weired behaviour that calling the program multiple times doesn't return the same output (at least on machines we tested).

```rust
#[allow(unused_imports)]
use sysinfo::Users;
use std::collections::HashMap;

fn main() {    
    let mut groups: HashMap<String,String> = HashMap::new();
        let user_info = sysinfo::Users::new_with_refreshed_list();
        // println!("user_info: {:?}", user_info);
        for user in user_info.list() {
            groups.insert(
                user.name().to_string(),
                user.groups().iter().map(|g| <&str as Into<String>>::into(g.name())).collect(),
            );
        }
        println!("{groups:?}");
}
```

We get the following output

```
╭─[dora@DoraDesk]─[E:\..\..\..\rs-win-sysinfo]─(#)                               (1.80.0)()─[ 9,17:25]
╰─[ master ● ?4]- cargo run
   Compiling rs-win-sysinfo v0.1.0 (E:\WorkSpace\CodeRepo\Rust_Repo\rs-win-sysinfo)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.29s
     Running `target\debug\rs-win-sysinfo.exe`
{"Administrator": "", "admin": "", "LOCAL SERVICE": "", "DefaultAccount": "", "subcom": "UsersAdministrators", "testadmin": "Administrators", "Guest": "Guests", "WDAGUtilityAccount": "", "DWM-1": "", "UMFD-0": "", "DORADESK$": "", "UMFD-1": "", "dora": ""}
╭─[dora@DoraDesk]─[E:\..\..\..\rs-win-sysinfo]─(#)                               (1.80.0)()─[ 9,17:25]
╰─[ master ● ?4]- cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running `target\debug\rs-win-sysinfo.exe`
{"admin": "AdministratorsUsers", "DWM-1": "", "dora": "", "WDAGUtilityAccount": "", "UMFD-1": "", "testadmin": "Administrators", "DefaultAccount": "", "LOCAL SERVICE": "", "DORADESK$": "", "subcom": "", "Administrator": "", "UMFD-0": "", "Guest": ""}
╭─[dora@DoraDesk]─[E:\..\..\..\rs-win-sysinfo]─(#)                               (1.80.0)()─[ 9,17:25]
╰─[ master ● ?4]- cargo run
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
     Running `target\debug\rs-win-sysinfo.exe`
{"admin": "", "DefaultAccount": "", "Administrator": "Administrators", "subcom": "", "WDAGUtilityAccount": "", "DORADESK$": "", "dora": "", "testadmin": "Administrators", "UMFD-1": "", "Guest": "Guests", "LOCAL SERVICE": "", "UMFD-0": "", "DWM-1": ""}
╭─[dora@DoraDesk]─[E:\..\..\..\rs-win-sysinfo]─(#)                               (1.80.0)()─[ 9,17:27]
╰─[ master ● ?4]-
```
FWIW, this create https://github.com/secur30nly/netuser-rs) has very similar implementation and seems to return the right groups.

```
warning: `netuser-rs` (lib) generated 4 warnings (run `cargo fix --lib -p netuser-rs` to apply 3 suggestions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
     Running `target\debug\netuser-rs.exe get-user-groups --username dilaw`
2024-08-11T09:29:13.912Z INFO  [netuser_rs] Local groups of "dilaw":
  Administrators
  Users
  IIS_IUSRS
2024-08-11T09:29:13.913Z INFO  [netuser_rs] Global groups of "dilaw":
  None
[dilaw@ookie netuser-rs (main)]$
```
Since I can't reproduce the bug locally, someone with the issue will need to send a PR to fix it.

Fix patch:
diff --git a/src/windows/users.rs b/src/windows/users.rs
index bc25dc817..ef88a8329 100644
--- a/src/windows/users.rs
+++ b/src/windows/users.rs
@@ -4,7 +4,7 @@ use crate::sys::utils::to_utf8_str;
 use crate::{windows::sid::Sid, Gid, Group, GroupInner, Uid, User};
 
 use std::ptr::null_mut;
-use windows::core::{w, PCWSTR};
+use windows::core::PCWSTR;
 use windows::Win32::Foundation::{ERROR_MORE_DATA, LUID};
 use windows::Win32::NetworkManagement::NetManagement::{
     NERR_Success, NetApiBufferFree, NetUserEnum, NetUserGetInfo, NetUserGetLocalGroups,
@@ -16,8 +16,6 @@ use windows::Win32::Security::Authentication::Identity::{
     SECURITY_LOGON_SESSION_DATA, SECURITY_LOGON_TYPE,
 };
 
-use super::utils::to_pcwstr;
-
 pub(crate) struct UserInner {
     pub(crate) uid: Uid,
     pub(crate) gid: Gid,
@@ -56,7 +54,16 @@ impl UserInner {
 
     pub(crate) fn groups(&self) -> Vec<Group> {
         if let (Some(c_user_name), true) = (&self.c_user_name, self.is_local) {
-            unsafe { get_groups_for_user(to_pcwstr(c_user_name.to_vec())) }
+            // Convert the wide string to a PCWSTR, and ensure it has a null terminator.
+            // Since the Vec is created here, we can ensure it will not be dropped prematurely.
+            let username = {
+                let mut null_terminated = c_user_name.to_vec();
+                if null_terminated.last().is_some_and(|v| *v != 0) {
+                    null_terminated.push(0);
+                }
+                null_terminated
+            };
+            unsafe { get_groups_for_user(PCWSTR::from_raw(username.as_ptr())) }
         } else {
             Vec::new()
         }
@@ -115,14 +122,18 @@ impl<T> LsaBuffer<T> {
     }
 }
 
+/// Get the groups for a user.
+///
+/// # Safety
+/// The caller must ensure that the `username` is a valid wide Unicode string with a null terminator.
 unsafe fn get_groups_for_user(username: PCWSTR) -> Vec<Group> {
     let mut buf: NetApiBuffer<LOCALGROUP_USERS_INFO_0> = Default::default();
     let mut nb_entries = 0;
     let mut total_entries = 0;
-    let mut groups;
+    let mut groups: Vec<Group>;
 
     let status = NetUserGetLocalGroups(
-        w!(""),
+        PCWSTR::null(),
         username,
         0,
         LG_INCLUDE_INDIRECT,
diff --git a/src/windows/utils.rs b/src/windows/utils.rs
index 762df1e5a..3d42babb2 100644
--- a/src/windows/utils.rs
+++ b/src/windows/utils.rs
@@ -17,15 +17,6 @@ pub(crate) unsafe fn to_utf8_str(p: windows::core::PWSTR) -> String {
     })
 }
 
-#[cfg(any(feature = "user", feature = "system"))]
-pub(crate) unsafe fn to_pcwstr(v: Vec<u16>) -> windows::core::PCWSTR {
-    if v.is_empty() {
-        return windows::core::PCWSTR::null();
-    }
-    let string = String::from_utf16(&v).unwrap_or_default();
-    windows::core::PCWSTR(windows::core::HSTRING::from(&format!("{}\0", string)).as_ptr())
-}
-
 cfg_if! {
     if #[cfg(any(feature = "disk", feature = "system"))] {
         use windows::Win32::Foundation::{CloseHandle, HANDLE};


Test patch: