<aside> š” The following snippet may be incomplete since I copied and pasted it from a large project. I should have created a snippet!
</aside>
Iād recommend this excellent resource https://github.com/secur30nly/netuser-rs. It is a rust-based cli/library to manage Windows users and groups.
My solution is built on top of it but is slightly different: I used USER_INFO_2
instead of USER_INFO_1
, which allows for the full name of the user, and LOCALGROUP_MEMBER_INFO_3
, instead of LOCALGROUP_MEMBER_INFO_1
which is a simpler API.
#[cfg(windows)]
fn create_account_win32<S: AsRef<str> + std::fmt::Display>(
user_name: S,
full_name: S,
password: S,
group: &str
) -> anyhow::Result<()> {
use windows::Win32::NetworkManagement::NetManagement::{
NetUserAdd,
UF_NORMAL_ACCOUNT,
UF_SCRIPT,
USER_ACCOUNT_FLAGS,
USER_INFO_2,
USER_PRIV_USER,
};
// use level 2 i.e. USER_INFO_2 in NetUserAdd
// <https://learn.microsoft.com/en-us/windows/win32/api/lmaccess/ns-lmaccess-user_info_2>
let mut user = USER_INFO_2::default();
let (user_name_w, _v1) = str_to_pwstr(user_name.as_ref());
user.usri2_name = user_name_w;
let (password_w, _v2) = str_to_pwstr(password.as_ref());
user.usri2_password = password_w;
let (full_name_w, _v3) = str_to_pwstr(full_name.as_ref());
user.usri2_full_name = full_name_w;
user.usri2_priv = USER_PRIV_USER;
user.usri2_flags = UF_SCRIPT | USER_ACCOUNT_FLAGS(UF_NORMAL_ACCOUNT);
user.usri2_acct_expires = u32::MAX;
tracing::debug!("Creating user {user:?}");
let status: u32 = unsafe { NetUserAdd(None, 2, &user as *const _ as _, None) };
if status == 0 {
println!("ā Successfully created account.");
} else {
eprintln!("ā Failed to create account!");
}
// Now add user to a localgroup. This group must already exist in the system.
manage_group_users_win32(user_name.as_ref(), group, false)?;
Ok(())
}
/// Add user to a specified local group.
/// If the `deletion` param is `true` - delete the specified user account from the local
/// group.
#[cfg(windows)]
unsafe fn manage_group_users_win32(
username: &str,
groupname: &str,
deletion: bool
) -> anyhow::Result<()> {
use windows::Win32::NetworkManagement::NetManagement::{
NetLocalGroupAddMembers,
NetLocalGroupDelMembers,
LOCALGROUP_MEMBERS_INFO_3,
};
let (wide_groupname_nul, _buf) = str_to_pwstr(groupname);
let (wide_username_nul, _buf) = str_to_pwstr(username);
tracing::debug!("Mangaing groups of user {username}: group={groupname} , delete={deletion}.");
let group_members = LOCALGROUP_MEMBERS_INFO_3 {
lgrmi3_domainandname: wide_username_nul,
};
let rc = if deletion {
NetLocalGroupDelMembers(
None,
wide_groupname_nul,
3,
&group_members as *const _ as *const u8,
1
)
} else {
NetLocalGroupAddMembers(
None,
wide_groupname_nul,
3,
&group_members as *const _ as *const u8,
1
)
};
if rc != 0 {
anyhow::bail!("Failed to manage user group. Error {rc}.");
}
Ok(())
}
/// Convert &str into PWSTR (null terminated).
pub fn str_to_pwstr(s: &str) -> (PWSTR, Vec<u16>) {
let mut encoded = s.encode_utf16().chain([0u16]).collect::<Vec<u16>>();
(PWSTR(encoded.as_mut_ptr()), encoded)
}