From d648651be92946ba28fbf7e58b1542e82cfec73a Mon Sep 17 00:00:00 2001 From: dahall Date: Wed, 13 Oct 2021 20:08:14 -0600 Subject: [PATCH] Added LocalGroup and UserAccount classes to Vanara.SystemServices to retrieve local group and user account information from a server's SAM. Added access through Computer class. --- System/Computer/Computer.cs | 379 +++++++++++--------- System/Computer/LocalGroup.cs | 279 +++++++++++++++ System/Computer/UserAccount.cs | 737 ++++++++++++++++++++++++++++++++++++++ UnitTests/System/ComputerTests.cs | 53 +++ UnitTests/System/DeviceTests.cs | 2 +- 5 files changed, 1277 insertions(+), 173 deletions(-) create mode 100644 System/Computer/LocalGroup.cs create mode 100644 System/Computer/UserAccount.cs diff --git a/System/Computer/Computer.cs b/System/Computer/Computer.cs index cfc3c1e3..da082541 100644 --- a/System/Computer/Computer.cs +++ b/System/Computer/Computer.cs @@ -9,199 +9,234 @@ using static Vanara.PInvoke.NetApi32; namespace Vanara { - /// Represents a single connected (authenticated) computer. - /// - /// - /// - [Serializable, DefaultProperty(nameof(Target))] - public class Computer : Component, ISupportInitialize, ISerializable - { - /// The local computer connected by the current account. - public static readonly Computer Local = new Computer(); + /// Represents a single connected (authenticated) computer. + /// + /// + /// + [Serializable, DefaultProperty(nameof(Target))] + public class Computer : Component, ISupportInitialize, ISerializable + { + /// The local computer connected by the current account. + public static readonly Computer Local = new(); - private SharedDevices devices; - private bool initializing; - private NetworkDeviceConnectionCollection networkConnections; - private string targetServer; - private bool targetServerSet; - private string userName; - private bool userNameSet; - private string userPassword; - private bool userPasswordSet; + private UserAccounts accounts; + private SharedDevices devices; + private bool initializing; + private LocalGroups localGroups; + private NetworkDeviceConnectionCollection networkConnections; + private string targetServer; + private bool targetServerSet; + private string userName; + private bool userNameSet; + private string userPassword; + private bool userPasswordSet; - /// Initializes a new instance of the class connecting to the local machine as the current user. - public Computer() => Connect(); + /// Initializes a new instance of the class connecting to the local machine as the current user. + public Computer() => Connect(); - /// Initializes a new instance of the class. - /// - /// The name of the computer that you want to connect to. If the this parameter is , then this will connect to - /// the local computer. - /// - /// - /// The user name that is used during the connection to the computer. If the user is not specified, then the current user is used. - /// - /// - /// The password that is used to connect to the computer. If the user name and password are not specified, then the current token is used. - /// - public Computer(string target, string userName = null, string password = null) - { - BeginInit(); - Target = target; - UserName = userName; - UserPassword = password; - EndInit(); - } + /// Initializes a new instance of the class. + /// + /// The name of the computer that you want to connect to. If the this parameter is , then this will connect to + /// the local computer. + /// + /// + /// The user name that is used during the connection to the computer. If the user is not specified, then the current user is used. + /// + /// + /// The password that is used to connect to the computer. If the user name and password are not specified, then the current token is used. + /// + public Computer(string target, string userName = null, string password = null) + { + BeginInit(); + Target = target; + UserName = userName; + UserPassword = password; + EndInit(); + } - private Computer(SerializationInfo info, StreamingContext context) - { - BeginInit(); - Target = (string)info.GetValue(nameof(Target), typeof(string)); - UserName = (string)info.GetValue(nameof(UserName), typeof(string)); - UserPassword = (string)info.GetValue(nameof(UserPassword), typeof(string)); - EndInit(); - } + private Computer(SerializationInfo info, StreamingContext context) + { + BeginInit(); + Target = (string)info.GetValue(nameof(Target), typeof(string)); + UserName = (string)info.GetValue(nameof(UserName), typeof(string)); + UserPassword = (string)info.GetValue(nameof(UserPassword), typeof(string)); + EndInit(); + } - //public IEnumerable LocalUsers => LocalUser.GetEnum(Target, UserName, UserPassword); + /// The local groups on this Computer. + public LocalGroups LocalGroups => localGroups ??= new LocalGroups(Target); - //public IEnumerable LocalGroups => LocalGroup.GetEnum(Target, UserName, UserPassword); + /// Gets the remote resource collection for this computer. + /// The remote resources. + [Browsable(false)] + public NetworkDeviceConnectionCollection NetworkDeviceConnections => targetServer is null ? networkConnections ??= new NetworkDeviceConnectionCollection(Identity, null) : + throw new InvalidOperationException("Network connection information is only available for the local computer."); - /// Gets the remote resource collection for this computer. - /// The remote resources. - [Browsable(false)] - public NetworkDeviceConnectionCollection NetworkDeviceConnections => targetServer is null ? networkConnections ??= new NetworkDeviceConnectionCollection(Identity, null) : - throw new InvalidOperationException("Network connection information is only available for the local computer."); + /// Gets the open files associated with this device. + /// Returns a value. + [Browsable(false)] + public IEnumerable OpenFiles => Identity.Run(() => NetFileEnum(Target).Select(i => new OpenFile(i))); - /// Gets the open files associated with this device. - /// Returns a value. - [Browsable(false)] - public IEnumerable OpenFiles => Identity.Run(() => NetFileEnum(Target).Select(i => new OpenFile(i))); + /// Gets the shared devices defined for this computer. + /// Returns a value. + [Browsable(false)] + public SharedDevices SharedDevices => devices ??= new SharedDevices(this); - /// Gets the shared devices defined for this computer. - /// Returns a value. - [Browsable(false)] - public SharedDevices SharedDevices => devices ??= new SharedDevices(this); + /// Gets or sets the name of the computer that the user is connected to. + [Category("Data"), DefaultValue(null), Description("The name of the computer to connect to.")] + public string Target + { + get => ShouldSerializeTargetServer() ? targetServer : null; + set + { + if (value == null || value.Trim() == string.Empty) + { + value = null; + } - /// Gets or sets the name of the computer that the user is connected to. - [Category("Data"), DefaultValue(null), Description("The name of the computer to connect to.")] - public string Target - { - get => ShouldSerializeTargetServer() ? targetServer : null; - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.Compare(value, targetServer, StringComparison.OrdinalIgnoreCase) != 0) - { - targetServerSet = true; - targetServer = value; - Connect(); - } - } - } + if (string.Compare(value, targetServer, StringComparison.OrdinalIgnoreCase) != 0) + { + targetServerSet = true; + targetServer = value; + Connect(); + } + } + } - /// Gets or sets the user name to be used when connecting to the . - /// The user name. - [Category("Data"), DefaultValue(null), Description("The user name to be used when connecting.")] - public string UserName - { - get => ShouldSerializeUserName() ? userName : null; - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.Compare(value, userName, StringComparison.OrdinalIgnoreCase) != 0) - { - userNameSet = true; - userName = value; - Connect(); - } - } - } + /// The user accounts on this Computer. + public UserAccounts UserAccounts => accounts ??= new UserAccounts(Target); - /// Gets or sets the user password to be used when connecting to the . - /// The user password. - [Category("Data"), DefaultValue(null), Description("The user password to be used when connecting.")] - public string UserPassword - { - get => userPassword; - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.CompareOrdinal(value, userPassword) != 0) - { - userPasswordSet = true; - userPassword = value; - Connect(); - } - } - } + /// Gets or sets the user name to be used when connecting to the . + /// The user name. + [Category("Data"), DefaultValue(null), Description("The user name to be used when connecting.")] + public string UserName + { + get => ShouldSerializeUserName() ? userName : null; + set + { + if (value == null || value.Trim() == string.Empty) + { + value = null; + } - internal WindowsIdentity Identity - { - get - { - if (UserName is null) - return null; + if (string.Compare(value, userName, StringComparison.OrdinalIgnoreCase) != 0) + { + userNameSet = true; + userName = value; + Connect(); + } + } + } - var nonUpnIdx = UserName.IndexOf('\\'); - var un = UserName; - string dn = null; - if (nonUpnIdx >= 0) - { - dn = UserName.Substring(0, nonUpnIdx); - un = UserName.Substring(nonUpnIdx + 1); - } - return new Security.Principal.WindowsLoggedInIdentity(un, dn, UserPassword).AuthenticatedIdentity; - } - } + /// Gets or sets the user password to be used when connecting to the . + /// The user password. + [Category("Data"), DefaultValue(null), Description("The user password to be used when connecting.")] + public string UserPassword + { + get => userPassword; + set + { + if (value == null || value.Trim() == string.Empty) + { + value = null; + } - /// Signals the object that initialization is starting. - public void BeginInit() => initializing = true; + if (string.CompareOrdinal(value, userPassword) != 0) + { + userPasswordSet = true; + userPassword = value; + Connect(); + } + } + } - /// Signals the object that initialization is complete. - public void EndInit() - { - initializing = false; - Connect(); - } + internal WindowsIdentity Identity + { + get + { + if (UserName is null) + { + return null; + } - void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) - { - info.AddValue(nameof(Target), Target, typeof(string)); - info.AddValue(nameof(UserName), UserName, typeof(string)); - info.AddValue(nameof(UserPassword), UserPassword, typeof(string)); - } + int nonUpnIdx = UserName.IndexOf('\\'); + string un = UserName; + string dn = null; + if (nonUpnIdx >= 0) + { + dn = UserName.Substring(0, nonUpnIdx); + un = UserName.Substring(nonUpnIdx + 1); + } + return new Security.Principal.WindowsLoggedInIdentity(un, dn, UserPassword).AuthenticatedIdentity; + } + } - private void Connect() - { - ResetUnsetProperties(); + /// Signals the object that initialization is starting. + public void BeginInit() => initializing = true; - if (!initializing && !DesignMode) - { - // Clear stuff if already connected - Dispose(true); + /// Signals the object that initialization is complete. + public void EndInit() + { + initializing = false; + Connect(); + } - if (!string.IsNullOrEmpty(targetServer)) - { - // Check to ensure character only server name. (Suggested by bigsan) - if (targetServer.StartsWith(@"\")) - targetServer = targetServer.TrimStart('\\'); - // Make sure null is provided for local machine to compensate for a native library oddity (Found by ctrollen) - if (targetServer.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase)) - targetServer = null; - } - else - targetServer = null; - } - } + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + info.AddValue(nameof(Target), Target, typeof(string)); + info.AddValue(nameof(UserName), UserName, typeof(string)); + info.AddValue(nameof(UserPassword), UserPassword, typeof(string)); + } - private void ResetUnsetProperties() - { - if (!targetServerSet) targetServer = null; - if (!userNameSet) userName = null; - if (!userPasswordSet) userPassword = null; - } + private void Connect() + { + ResetUnsetProperties(); - private bool ShouldSerializeTargetServer() => targetServer != null && !targetServer.Trim('\\').Equals(Environment.MachineName.Trim('\\'), StringComparison.InvariantCultureIgnoreCase); + if (!initializing && !DesignMode) + { + // Clear stuff if already connected + Dispose(true); - private bool ShouldSerializeUserName() => userName != null && !userName.Equals(Environment.UserName, StringComparison.InvariantCultureIgnoreCase); - } + if (!string.IsNullOrEmpty(targetServer)) + { + // Check to ensure character only server name. (Suggested by bigsan) + if (targetServer.StartsWith(@"\")) + { + targetServer = targetServer.TrimStart('\\'); + } + // Make sure null is provided for local machine to compensate for a native library oddity (Found by ctrollen) + if (targetServer.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase)) + { + targetServer = null; + } + } + else + { + targetServer = null; + } + } + } + + private void ResetUnsetProperties() + { + if (!targetServerSet) + { + targetServer = null; + } + + if (!userNameSet) + { + userName = null; + } + + if (!userPasswordSet) + { + userPassword = null; + } + } + + private bool ShouldSerializeTargetServer() => targetServer != null && !targetServer.Trim('\\').Equals(Environment.MachineName.Trim('\\'), StringComparison.InvariantCultureIgnoreCase); + + private bool ShouldSerializeUserName() => userName != null && !userName.Equals(Environment.UserName, StringComparison.InvariantCultureIgnoreCase); + } } \ No newline at end of file diff --git a/System/Computer/LocalGroup.cs b/System/Computer/LocalGroup.cs new file mode 100644 index 00000000..7afa450e --- /dev/null +++ b/System/Computer/LocalGroup.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Vanara.Extensions; +using Vanara.PInvoke; +using static Vanara.PInvoke.NetApi32; + +namespace Vanara +{ + /// Represents a local group on a server. + public class LocalGroup : IEquatable + { + private LocalGroupMembers members; + + internal LocalGroup(string target, string name) + { + Target = target; + Name = name; + } + + /// + /// A remark associated with the local group. This member can be a string. The comment can have as many as + /// MAXCOMMENTSZ characters. + /// + public string Comment + { + get => NetLocalGroupGetInfo(Target, Name).lgrpi1_comment; + set => NetLocalGroupSetInfo(Target, Name, new LOCALGROUP_INFO_1002 { lgrpi1002_comment = value }, 1002); + } + + /// Gets the collection of members for this local group. + /// The collection of members for this local group. + public LocalGroupMembers Members => members ??= new LocalGroupMembers(this); + + /// The local group name. + public string Name { get; } + + /// + /// Gets a string that specifies the DNS or NetBIOS name of the remote server on which the local group resides. If this value is + /// , the local computer is assumed. + /// + /// The target server. + public string Target { get; } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// An object to compare with this object. + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + public bool Equals(LocalGroup other) => other?.Name == Name && other.Target == Target; + } + + /// Represents a colleciton of local group members. + public class LocalGroupMembers : ICollection + { + private readonly LocalGroup localGroup; + + internal LocalGroupMembers(LocalGroup localGroup) => this.localGroup = localGroup; + + /// Gets the number of elements contained in the collection. + /// The number of elements contained in the collection. + public int Count => GetMemberSids().Count(); + + /// Gets a value indicating whether the collection is read only. + /// if the collection is read only; otherwise, . + bool ICollection.IsReadOnly => false; + + /// + /// Adds membership of one user account or global group account to the local group. The function does not change the membership + /// status of users or global groups that are currently members of the local group. + /// + /// The SID of the new local group member. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the LocalGroup object is used to perform the access check for this function. + /// + public void Add(PSID member) => AddRange(new PSID[] { member }); + + /// + /// Adds membership of one or more existing user accounts or global group accounts to the local group. The function does not change + /// the membership status of users or global groups that are currently members of the local group. + /// + /// The SIDs of the new local group members. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the LocalGroup object is used to perform the access check for this function. + /// + public void AddRange(IEnumerable members) + { + LOCALGROUP_MEMBERS_INFO_0[] a = members.Select(p => new LOCALGROUP_MEMBERS_INFO_0 { lgrmi0_sid = p }).ToArray(); + NetLocalGroupAddMembers(localGroup.Target, localGroup.Name, a, 0); + } + + /// Removes all items from the collection. + public void Clear() => NetLocalGroupSetMembers(localGroup.Target, localGroup.Name, new LOCALGROUP_MEMBERS_INFO_0[0], 0); // Remove(GetMemberSids()); + + /// Determines whether the collection contains the specified SID. + /// The pointer to the SID to find. + /// if the collection contains the specified SID, otherwise, . + public bool Contains(PSID item) => GetMemberSids().Contains(item); + + /// Copies the elements of the collection to an Array, starting at a particular Array index. + /// + /// The one-dimensional Array that is the destination of the elements copied from the collection. The Array must have zero-based indexing. + /// + /// The zero-based index in at which copying begins. + public void CopyTo(PSID[] array, int arrayIndex) + { + PSID[] a = GetMemberSids().ToArray(); + Array.Copy(a, 0, array, arrayIndex, a.Length); + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() => GetMemberSids().GetEnumerator(); + + /// Removes one member from an existing local group. Local group members can be users or global groups. + /// The SID of the member to be removed. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the LocalGroup object is used to perform the access check for this function. + /// + public bool Remove(PSID member) + { + try { Remove(new[] { member }); return true; } + catch { return false; } + } + + /// Removes one or more members from an existing local group. Local group members can be users or global groups. + /// The SIDs of the members to be removed. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the LocalGroup object is used to perform the access check for this function. + /// + public void Remove(IEnumerable members) + { + LOCALGROUP_MEMBERS_INFO_0[] a = members.Select(p => new LOCALGROUP_MEMBERS_INFO_0 { lgrmi0_sid = p }).ToArray(); + NetLocalGroupDelMembers(localGroup.Target, localGroup.Name, a, 0); + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable GetMemberSids() => NetLocalGroupGetMembers(localGroup.Target, localGroup.Name, 0).Select(i => i.lgrmi0_sid); + } + + /// Represents the collection of local groups on a server. + public class LocalGroups : ICollection + { + /// Initializes a new instance of the class. + /// + /// The DNS or NetBIOS name of the remote server on which the user account resides. If this value is , the + /// local computer is assumed. + /// + public LocalGroups(string target = null) => Target = target; + + /// + /// Gets the DNS or NetBIOS name of the remote server on which the user account resides. If this value is , + /// the local computer is assumed. + /// + /// The target. + public string Target { get; } + + /// Gets the number of elements contained in the collection. + /// The number of elements contained in the collection. + public int Count => Enumerate().Count(); + + /// Gets a value indicating whether the collection is read only. + /// if the collection is read only; otherwise, . + bool ICollection.IsReadOnly => false; + + /// + /// Creates a local group in the security database, which is the security accounts manager (SAM) database or, in the case of domain + /// controllers, the Active Directory. + /// + /// The local group name. + /// On success, the instance representing the created local group. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// + /// The security descriptor of the user container is used to perform the access check for this function. The caller must be able to + /// create child objects of the group class. + /// + /// + public LocalGroup Add(string name) + { + NetLocalGroupAdd(Target, new LOCALGROUP_INFO_0 { lgrpi0_name = name }, 0); + return new LocalGroup(Target, name); + } + + /// Removes all items from the collection. + public void Clear() + { + foreach (LocalGroup a in Enumerate().ToArray()) + { + Remove(a); + } + } + + /// Determines whether the collection contains the specified user account. + /// The user account to find. + /// if the collection contains the specified user account, otherwise, . + public bool Contains(LocalGroup item) => Enumerate().Contains(item); + + /// Copies the elements of the collection to an Array, starting at a particular Array index. + /// + /// The one-dimensional Array that is the destination of the elements copied from the collection. The Array must have zero-based indexing. + /// + /// The zero-based index in at which copying begins. + public void CopyTo(LocalGroup[] array, int arrayIndex) + { + LocalGroup[] a = Enumerate().ToArray(); + Array.Copy(a, 0, array, arrayIndex, a.Length); + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); + + /// + /// Deletes this local group and all its members from the security database, which is the security accounts manager (SAM) database + /// or, in the case of domain controllers, the Active Directory. + /// + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the LocalGroup object is used to perform the access check for this function. + /// + public bool Remove(LocalGroup localGroup) => NetLocalGroupDel(Target, localGroup.Name).Succeeded; + + /// + void ICollection.Add(LocalGroup item) => throw new NotImplementedException(); + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable Enumerate() => NetLocalGroupEnum(Target).Select(i => new LocalGroup(Target, i.lgrpi0_name)); + } +} \ No newline at end of file diff --git a/System/Computer/UserAccount.cs b/System/Computer/UserAccount.cs new file mode 100644 index 00000000..e4541142 --- /dev/null +++ b/System/Computer/UserAccount.cs @@ -0,0 +1,737 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using Vanara.Extensions; +using Vanara.InteropServices; +using Vanara.PInvoke; +using static Vanara.PInvoke.NetApi32; + +namespace Vanara +{ + /// Represents a user account on a server. + [DefaultProperty(nameof(UserName))] + public sealed class UserAccount : IEquatable, INamedEntity + { + /// The base date + internal static readonly DateTime baseDate = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + /// Initializes a new instance of the class. + /// Name of the user. + /// The target. + internal UserAccount(string userName, string target) { UserName = userName; Target = target; } + + /// + /// This member can be one or more of the following values. + /// + /// Note that setting user account control flags may require certain privileges and control access rights. For more information, see + /// the Remarks section of the NetUserSetInfo function. + /// + /// + /// + /// Value + /// Meaning + /// + /// + /// UF_SCRIPT + /// The logon script executed. This value must be set. + /// + /// + /// UF_ACCOUNTDISABLE + /// The user's account is disabled. + /// + /// + /// UF_HOMEDIR_REQUIRED + /// The home directory is required. This value is ignored. + /// + /// + /// UF_PASSWD_NOTREQD + /// No password is required. + /// + /// + /// UF_PASSWD_CANT_CHANGE + /// The user cannot change the password. + /// + /// + /// UF_LOCKOUT + /// + /// The account is currently locked out. You can call the NetUserSetInfo function to clear this value and unlock a previously locked + /// account. You cannot use this value to lock a previously unlocked account. + /// + /// + /// + /// UF_DONT_EXPIRE_PASSWD + /// The password should never expire on the account. + /// + /// + /// UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED + /// The user's password is stored under reversible encryption in the Active Directory. + /// + /// + /// UF_NOT_DELEGATED + /// Marks the account as "sensitive"; other users cannot act as delegates of this user account. + /// + /// + /// UF_SMARTCARD_REQUIRED + /// Requires the user to log on to the user account with a smart card. + /// + /// + /// UF_USE_DES_KEY_ONLY + /// Restrict this principal to use only Data Encryption Standard (DES) encryption types for keys. + /// + /// + /// UF_DONT_REQUIRE_PREAUTH + /// This account does not require Kerberos preauthentication for logon. + /// + /// + /// UF_TRUSTED_FOR_DELEGATION + /// + /// The account is enabled for delegation. This is a security-sensitive setting; accounts with this option enabled should be tightly + /// controlled. This setting allows a service running under the account to assume a client's identity and authenticate as that user + /// to other remote servers on the network. + /// + /// + /// + /// UF_PASSWORD_EXPIRED + /// The user's password has expired. Windows 2000: This value is ignored. + /// + /// + /// UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION + /// + /// The account is trusted to authenticate a user outside of the Kerberos security package and delegate that user through + /// constrained delegation. This is a security-sensitive setting; accounts with this option enabled should be tightly controlled. + /// This setting allows a service running under the account to assert a client's identity and authenticate as that user to + /// specifically configured services on the network. Windows XP/2000: This value is ignored. + /// + /// + /// + /// + /// The following values describe the account type. Only one value can be set. You cannot change the account type using the + /// NetUserSetInfo function. + /// + /// + /// + /// Value + /// Meaning + /// + /// + /// UF_NORMAL_ACCOUNT + /// This is a default account type that represents a typical user. + /// + /// + /// UF_TEMP_DUPLICATE_ACCOUNT + /// + /// This is an account for users whose primary account is in another domain. This account provides user access to this domain, but + /// not to any domain that trusts this domain. The User Manager refers to this account type as a local user account. + /// + /// + /// + /// UF_WORKSTATION_TRUST_ACCOUNT + /// This is a computer account for a computer that is a member of this domain. + /// + /// + /// UF_SERVER_TRUST_ACCOUNT + /// This is a computer account for a backup domain controller that is a member of this domain. + /// + /// + /// UF_INTERDOMAIN_TRUST_ACCOUNT + /// This is a permit to trust account for a domain that trusts other domains. + /// + /// + /// + /// The account control flags. + public UserAcctCtrlFlags AccountControlFlags + { + get => NetUserGetInfo(Target, UserName).usri1_flags; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1008 { usri1008_flags = value }); + } + + /// The date and time when the account expires. A value of indicates that the account never expires. + /// The account expiration. + public DateTime? AccountExpiration + { + get => DWORDtoDT(NetUserGetInfo(Target, UserName).usri2_acct_expires); + set => NetUserSetInfo(Target, UserName, new USER_INFO_1017 { usri1017_acct_expires = DTtoDWORD(value) }); + } + + /// + /// A string that is reserved for use by applications. This string can be a string, or it can have any number + /// of characters. Microsoft products use this member to store user configuration information. Do not modify this information. + /// + /// The parms. + public string AppParams + { + get => NetUserGetInfo(Target, UserName).usri11_parms; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1013 { usri1013_parms = value }); + } + + /// + /// + /// The number of times the user tried to log on to the account using an incorrect password. A value of – 1 indicates that the value + /// is unknown. Calls to the NetUserAdd and NetUserSetInfo functions ignore this member. + /// + /// + /// This member is replicated from the primary domain controller (PDC); it is also maintained on each backup domain controller (BDC) + /// in the domain. To obtain an accurate value, you must query each BDC in the domain. The number of times the user tried to log on + /// using an incorrect password is the largest value retrieved. + /// + /// + /// The bad password count. + public uint BadPasswordCount => NetUserGetInfo(Target, UserName).usri11_bad_pw_count; + + /// The code page for the user's language of choice. + /// The code page. + public uint CodePage + { + get => NetUserGetInfo(Target, UserName).usri11_code_page; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1025 { usri1025_code_page = value }); + } + + /// + /// A string that contains a comment to associate with the user account. The string can be a string, or it + /// can have any number of characters. + /// + /// The comment. + public string Comment + { + get => NetUserGetInfo(Target, UserName).usri10_comment; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1007 { usri1007_comment = value }); + } + + /// The country/region code for the user's language of choice. + /// The country code. + public uint CountryCode + { + get => NetUserGetInfo(Target, UserName).usri11_country_code; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1024 { usri1024_country_code = value }); + } + + /// + /// A string that contains the full name of the user. This string can be a string, or it can have any number + /// of characters. + /// + /// The full name. + public string FullName + { + get => NetUserGetInfo(Target, UserName).usri10_full_name; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1011 { usri1011_full_name = value }); + } + + /// Retrieves a list of global groups to which a specified user belongs. + /// A sequence of global groups to which this user belongs. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits all authenticated users and members of the + /// "Pre-Windows 2000 compatible access" group to view the information. If you call this function on a member server or workstation, + /// all authenticated users can view the information. For information about anonymous access and restricting anonymous access on + /// these platforms, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the User object is used to perform the access check for this function. + /// + public IEnumerable GlobalGroups => NetUserGetGroups(Target, UserName).Select(i => i.grui0_name); + + /// A string that specifies the drive letter assigned to the user's home directory for logon purposes. + /// The home directory drive letter. + public string HomeDirectoryDriveLetter + { + get => NetUserGetInfo(Target, UserName).usri3_home_dir_drive; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1053 { usri1053_home_dir_drive = value }); + } + + /// + /// A string specifying the path of the home directory of the user specified by the UserName member. The string can be . + /// + /// The home folder. + public string HomeFolder + { + get => NetUserGetInfo(Target, UserName).usri1_home_dir; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1006 { usri1006_home_dir = value }); + } + + /// + /// This member is currently not used. + /// The date and time when the last logoff occurred. + /// + /// This member is maintained separately on each backup domain controller (BDC) in the domain. To obtain an accurate value, you must + /// query each BDC in the domain. The last logoff occurred at the time indicated by the largest retrieved value. + /// + /// + /// The last logoff. + public DateTime? LastLogoff => DWORDtoDT(NetUserGetInfo(Target, UserName).usri11_last_logoff); + + /// + /// The date and time when the last logon occurred. + /// + /// This member is maintained separately on each backup domain controller (BDC) in the domain. To obtain an accurate value, you must + /// query each BDC in the domain. The last logon occurred at the time indicated by the largest retrieved value. + /// + /// + /// The last logon. + public DateTime LastLogon => DWORDtoDT(NetUserGetInfo(Target, UserName).usri11_last_logon).GetValueOrDefault(); + + /// + /// Retrieves a list of local groups to which a specified user belongs, including local groups in which the user is indirectly a + /// member (that is, the user has membership in a global group that is itself a member of one or more local groups). + /// + /// A sequence of local groups to which this user belongs. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits all authenticated users and members of the + /// "Pre-Windows 2000 compatible access" group to view the information. If you call this function on a member server or workstation, + /// all authenticated users can view the information. For information about anonymous access and restricting anonymous access on + /// these platforms, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// + /// The security descriptor of the Domain object is used to perform the access check for this function. The caller must have Read + /// Property permission on the Domain object. + /// + /// + public IEnumerable LocalGroups => NetUserGetLocalGroups(Target, UserName, GetLocalGroupFlags.LG_INCLUDE_INDIRECT).Select(i => i.lgrui0_name); + + /// + /// + /// A pointer to a 168-bit array that specifies the times during which the user can log on. Each bit represents a unique hour in the + /// week, in Greenwich Mean Time (GMT). + /// + /// + /// The first bit (bit 0, word 0) is Sunday, 0:00 to 0:59; the second bit (bit 1, word 0) is Sunday, 1:00 to 1:59; and so on. Note + /// that bit 0 in word 0 represents Sunday from 0:00 to 0:59 only if you are in the GMT time zone. In all other cases you must + /// adjust the bits according to your time zone offset (for example, GMT minus 8 hours for Pacific Standard Time). + /// + /// + /// Specify in this member when calling the NetUserAdd function to indicate no time restriction. Specify a + /// pointer when calling the NetUserSetInfo function to indicate that no change is to be made to the times + /// during which the user can log on. + /// + /// + /// The logon hours. + /// LogonHours + public BitArray LogonHours + { + get + { + IntPtr ptr = NetUserGetInfo(Target, UserName).usri11_logon_hours; + return new BitArray(ptr.ToArray(21)); + } + set + { + if (value is null) + { + NetUserSetInfo(Target, UserName, new USER_INFO_1020 { usri1020_units_per_week = UnitsPerWeek }); + return; + } + if (value.Count != 168) + { + throw new ArgumentOutOfRangeException(nameof(LogonHours)); + } + + byte[] bytes = new byte[21]; + value.CopyTo(bytes, 0); + using SafeByteArray mem = new(bytes); + NetUserSetInfo(Target, UserName, new USER_INFO_1020 { usri1020_units_per_week = UnitsPerWeek, usri1020_logon_hours = mem.DangerousGetHandle() }); + } + } + + /// + /// + /// A string that contains the name of the server to which logon requests are sent. Server names should be preceded by two + /// backslashes (\). To indicate that the logon request can be handled by any logon server, specify an asterisk (\*) for the server + /// name. A string indicates that requests should be sent to the domain controller. + /// + /// For Windows servers, the NetUserGetInfo function returns \*. + /// + /// The logon server. + public string LogonServer + { + get => NetUserGetInfo(Target, UserName).usri11_logon_server; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1023 { usri1023_logon_server = value }); + } + + /// + /// The maximum amount of disk space the user can use. Specify to use all available disk space. + /// + /// The maximum storage. + public uint MaxStorage + { + get => NetUserGetInfo(Target, UserName).usri11_max_storage; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1018 { usri1018_max_storage = value }); + } + + /// + /// The user's operator privileges. + /// + /// For the NetUserGetInfo function, the appropriate value is returned based on the local group membership. If the user is a member + /// of Print Operators, AF_OP_PRINT is set. If the user is a member of Server Operators, AF_OP_SERVER is set. If the user is a + /// member of the Account Operators, AF_OP_ACCOUNTS is set. AF_OP_COMM is never set. + /// + /// This member can be one or more of the following values. + /// + /// + /// Value + /// Meaning + /// + /// + /// AF_OP_PRINT + /// The print operator privilege is enabled. + /// + /// + /// AF_OP_COMM + /// The communications operator privilege is enabled. + /// + /// + /// AF_OP_SERVER + /// The server operator privilege is enabled. + /// + /// + /// AF_OP_ACCOUNTS + /// The accounts operator privilege is enabled. + /// + /// + /// + /// The operator privileges. + public UserOpPriv OperatorPrivileges + { + get => NetUserGetInfo(Target, UserName).usri11_auth_flags; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1010 { usri1010_auth_flags = value }); + } + + /// + /// A string that specifies the password for the user identified by the UserName member. The length cannot exceed PWLEN bytes. + /// + /// The password. + public string Password + { + set => NetUserSetInfo(Target, UserName, new USER_INFO_1003 { usri1003_password = value }); + } + + /// The time elapsed since the Password member was last changed. + /// The password age. + public TimeSpan PasswordAge => TimeSpan.FromSeconds(NetUserGetInfo(Target, UserName).usri1_password_age); + + /// + /// The password expiration information. + /// The NetUserGetInfo function return zero if the password has not expired (and nonzero if it has). + /// + /// When you call NetUserAdd or NetUserSetInfo, specify a nonzero value in this member to inform users that they must change their + /// password at the next logon. To turn off this message, call NetUserSetInfo and specify zero in this member. Note that you + /// cannot specify zero to negate the expiration of a password that has already expired. + /// + /// + /// if [password expired]; otherwise, . + public bool PasswordExpired => NetUserGetInfo(Target, UserName).usri3_password_expired != 0; + + /// + /// The relative identifier (RID) of the Primary Global Group for the user. When you call the NetUserAdd function, this + /// member must be DOMAIN_GROUP_RID_USERS (defined in WinNT.h). When you call NetUserSetInfo, this member must be the RID of + /// a global group in which the user is enrolled. For more information, see Well-Known SIDs and SID Components. + /// + /// The primary group identifier. + public uint PrimaryGroupId + { + get => NetUserGetInfo(Target, UserName).usri3_primary_group_id; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1051 { usri1051_primary_group_id = value }); + } + + /// + /// + /// The level of privilege assigned to the UserName member. For more information about user and group account rights, see Privileges. + /// + /// + /// + /// Value + /// Meaning + /// + /// + /// USER_PRIV_GUEST + /// Guest + /// + /// + /// USER_PRIV_USER + /// User + /// + /// + /// USER_PRIV_ADMIN + /// Administrator + /// + /// + /// + /// The privilege. + public UserPrivilege Privilege => NetUserGetInfo(Target, UserName).usri1_priv; + + /// + /// A string that specifies a path to the user's profile. This value can be a string, a local absolute path, + /// or a UNC path. + /// + /// The profile path. + public string ProfilePath + { + get => NetUserGetInfo(Target, UserName).usri3_profile; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1052 { usri1052_profile = value }); + } + + /// + /// A string specifying the path for the user's logon script file. The script file can be a .CMD file, an .EXE file, or a .BAT file. + /// The string can also be . + /// + /// The script path. + public string ScriptPath + { + get => NetUserGetInfo(Target, UserName).usri1_script_path; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1009 { usri1009_script_path = value }); + } + + /// + /// The number of times the user logged on successfully to this account. A value of – 1 indicates that the value is unknown. + /// + /// This member is maintained separately on each backup domain controller (BDC) in the domain. To obtain an accurate value, you must + /// query each BDC in the domain. The number of times the user logged on successfully is the sum of the retrieved values. + /// + /// + /// The successful logons. + public uint SuccessfulLogons => NetUserGetInfo(Target, UserName).usri11_num_logons; + + /// + /// Gets a string that specifies the DNS or NetBIOS name of the remote server on which the user account resides. If this value is + /// , the local computer is assumed. + /// + /// The target. + public string Target { get; } + + /// + /// + /// The number of equal-length time units into which the week is divided. This value is required to compute the length of the bit + /// string in the logon_hours member. + /// + /// This value must be UNITS_PER_WEEK for LAN Manager 2.0. This element is ignored by the NetUserAdd and NetUserSetInfo functions. + /// For service applications, the units must be one of the following values: SAM_DAYS_PER_WEEK, SAM_HOURS_PER_WEEK, or SAM_MINUTES_PER_WEEK. + /// + /// The units per week. + public uint UnitsPerWeek + { + get => NetUserGetInfo(Target, UserName).usri11_units_per_week; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1020 { usri1020_units_per_week = value, usri1020_logon_hours = IntPtr.Zero }); + } + + /// + /// A string that contains a user comment. This string can be a string, or it can have any number of characters. + /// + /// The user comment. + public string UserComment + { + get => NetUserGetInfo(Target, UserName).usri10_usr_comment; + set => NetUserSetInfo(Target, UserName, new USER_INFO_1012 { usri1012_usr_comment = value }); + } + + /// A string that specifies the name of the user account. + /// The name of the user. + public string UserName { get; } + + /// A pointer to a SID structure that contains the security identifier (SID) that uniquely identifies the user. + /// The user sid. + public PSID UserSid => NetUserGetInfo(Target, UserName).usri23_user_sid; + + /// + /// An array of strings that contains the names of workstations from which the user can log on. As many as eight workstations can be + /// specified. If you do not want to restrict the number of workstations, use a value. To disable logons from + /// all workstations to this account, set the UF_ACCOUNTDISABLE value in the AccountControlFlags member. + /// + /// The workstations. + public string[] Workstations + { + get => NetUserGetInfo(Target, UserName).usri11_workstations?.Split(','); + set => NetUserSetInfo(Target, UserName, new USER_INFO_1014 { usri1014_workstations = value is null || value.Length == 0 ? null : string.Join(",", value) }); + } + + /// + string INamedEntity.Name => UserName; + + /// Changes the password for a specified network server or domain. + /// A string that specifies the user's old password. + /// A string that specifies the user's new password. + /// + /// A string that specifies the DNS or NetBIOS name of a remote server or domain on which the function is to execute. If this + /// parameter is NULL, the logon domain of the caller is used. + /// + /// + /// + /// If an application calls the NetUserChangePassword function on a domain controller that is running Active Directory, + /// access is allowed or denied based on the access control list (ACL) for the securable object. The default ACL permits only Domain + /// Admins and Account Operators to call this function. On a member server or workstation, only Administrators and Power Users can + /// call this function. A user can change his or her own password. For more information, see Security Requirements for the Network + /// Management Functions. For more information on ACLs, ACEs, and access tokens, see Access Control Model. + /// + /// + /// The security descriptor of the User object is used to perform the access check for this function. In addition, the caller must + /// have the "Change password" control access right on the User object. This right is granted to Anonymous Logon and Everyone by default. + /// + /// Note that for the function to succeed, the oldpassword parameter must match the password as it currently exists. + /// + /// In some cases, the process that calls the NetUserChangePassword function must also have the SE_CHANGE_NOTIFY_NAME + /// privilege enabled; otherwise, NetUserChangePassword fails and GetLastError returns ERROR_ACCESS_DENIED. This privilege is + /// not required for the LocalSystem account or for accounts that are members of the administrators group. By default, + /// SE_CHANGE_NOTIFY_NAME is enabled for all users, but some administrators may disable the privilege for everyone. For more + /// information about account privileges, see Privileges and Authorization Constants. + /// + /// + /// See Forcing a User to Change the Logon Password for a code sample that demonstrates how to force a user to change the logon + /// password on the next logon using the NetUserGetInfo and NetUserSetInfo functions. + /// + /// + /// The NetUserChangePassword function does not control how the oldpassword and newpassword parameters are secured when sent + /// over the network to a remote server. Any encryption of these parameters is handled by the Remote Procedure Call (RPC) mechanism + /// supported by the network redirector that provides the network transport. Encryption is also controlled by the security + /// mechanisms supported by the local computer and the security mechanisms supported by remote network server or domain specified in + /// the domainname parameter. For more details on security when the Microsoft network redirector is used and the remote network + /// server is running Microsoft Windows, see the protocol documentation for MS-RPCE, MS-SAMR, MS-SPNG, and MS-NLMP. + /// + /// + public void ChangePassword(string oldPassword, string newPassword, string domainName = null) => NetUserChangePassword(domainName, UserName, oldPassword, newPassword).ThrowIfFailed(); + + /// Equalses the specified other. + /// The other. + /// + public bool Equals(UserAccount other) => other?.UserName == UserName && other.Target == Target; + + internal static uint DTtoDWORD(DateTime? dt) => dt.HasValue ? (uint)(dt.Value.ToUniversalTime() - baseDate).TotalSeconds : uint.MaxValue; + + internal static DateTime? DWORDtoDT(uint baseSec) => baseSec == 0 || baseSec == uint.MaxValue ? null : (baseDate + TimeSpan.FromSeconds(baseSec)).ToLocalTime(); + } + + /// Represents the collection of user accounts on a server. + public class UserAccounts : ICollection + { + /// Initializes a new instance of the class. + /// + /// The DNS or NetBIOS name of the remote server on which the user account resides. If this value is , the + /// local computer is assumed. + /// + public UserAccounts(string target = null) => Target = target; + + /// + /// Gets the DNS or NetBIOS name of the remote server on which the user account resides. If this value is , + /// the local computer is assumed. + /// + /// The target. + public string Target { get; } + + /// Gets the number of elements contained in the collection. + /// The number of elements contained in the collection. + public int Count => Enumerate().Count(); + + /// Gets a value indicating whether the collection is read only. + /// if the collection is read only; otherwise, . + bool ICollection.IsReadOnly => false; + + /// Adds a user account and assigns a password and other key properties. + /// The name of the user account. + /// The password of the user. The length cannot exceed PWLEN bytes. + /// The optional path of the home directory for the user. + /// An optional comment to associate with the user account. + /// The script path. + /// Flags that describe the account type and options. + /// On success, returns an instance of for the specified user. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// + /// The security descriptor of the user container is used to perform the access check for this function. The caller must be able to + /// create child objects of the user class. + /// + /// + /// Server users must use a system in which the server creates a system account for the new user. The creation of this account is + /// controlled by several parameters in the server's LanMan.ini file. + /// + /// If the newly added user already exists as a system user, the parameter is ignored. + /// + /// When you call the AddNew function, the call initializes the additional members in the object to + /// their default values. You can change the default values by setting properties on the object. + /// + /// + /// User account names are limited to 20 characters and group names are limited to 256 characters. In addition, account names cannot + /// be terminated by a period and they cannot include commas or any of the following printable characters: ", /, , [, ], :, |, <, + /// >, +, =, ;, ?, *. Names also cannot include characters in the range 1-31, which are non-printable. + /// + /// + public UserAccount Add(string userName, string password, string homeFolder = null, string comment = null, + string scriptPath = null, UserAcctCtrlFlags flags = 0) + { + flags |= UserAcctCtrlFlags.UF_SCRIPT; + NetUserAdd(Target, new USER_INFO_1 + { + usri1_name = userName, + usri1_password = password, + usri1_home_dir = homeFolder, + usri1_comment = comment, + usri1_script_path = scriptPath, + usri1_flags = flags, + usri1_priv = UserPrivilege.USER_PRIV_USER + }, 1); + return new UserAccount(userName, Target); + } + + /// Removes all items from the collection. + public void Clear() + { + foreach (UserAccount a in Enumerate().ToArray()) + { + Remove(a); + } + } + + /// Determines whether the collection contains the specified user account. + /// The user account to find. + /// if the collection contains the specified user account, otherwise, . + public bool Contains(UserAccount item) => Enumerate().Contains(item); + + /// Copies the elements of the collection to an Array, starting at a particular Array index. + /// + /// The one-dimensional Array that is the destination of the elements copied from the collection. The Array must have zero-based indexing. + /// + /// The zero-based index in at which copying begins. + public void CopyTo(UserAccount[] array, int arrayIndex) + { + UserAccount[] a = Enumerate().ToArray(); + Array.Copy(a, 0, array, arrayIndex, a.Length); + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() => Enumerate().GetEnumerator(); + + /// Deletes this account from the server. + /// + /// + /// If you call this function on a domain controller that is running Active Directory, access is allowed or denied based on the + /// access control list (ACL) for the securable object. The default ACL permits only Domain Admins and Account Operators to call + /// this function. On a member server or workstation, only Administrators and Power Users can call this function. For more + /// information, see Security Requirements for the Network Management Functions. For more information on ACLs, ACEs, and access + /// tokens, see Access Control Model. + /// + /// The security descriptor of the User object is used to perform the access check for this function. + /// + /// An account cannot be deleted while a user or application is accessing a server resource. If the user was added to the system + /// with a call to the NetUserAdd function, deleting the user also deletes the user's system account. + /// + /// + public bool Remove(UserAccount user) => NetUserDel(user.Target, user.UserName).Succeeded; + + /// + void ICollection.Add(UserAccount item) => throw new NotImplementedException(); + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private IEnumerable Enumerate() => NetUserEnum(Target, 0).Select(u => new UserAccount(u.usri0_name, Target)); + } +} \ No newline at end of file diff --git a/UnitTests/System/ComputerTests.cs b/UnitTests/System/ComputerTests.cs index f25ac4dd..15b8e135 100644 --- a/UnitTests/System/ComputerTests.cs +++ b/UnitTests/System/ComputerTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using System.Text; using System.Linq; using Vanara.PInvoke.Tests; +using Vanara.InteropServices; namespace Vanara.Diagnostics.Tests { @@ -27,6 +28,58 @@ namespace Vanara.Diagnostics.Tests } } + [Test] + public void EnumLocalGroupsTest() + { + const string name = "TestUser123"; + const string newcomment = "new testing user"; + + var lg = Computer.Local.LocalGroups.Add(name); + try + { + Assert.That(Computer.Local.LocalGroups.Count, Is.GreaterThanOrEqualTo(1)); + Assert.That(lg.Name, Is.EqualTo(name)); + Assert.That(lg.Target, Is.EqualTo(Computer.Local.Target)); + Assert.That(lg.Comment, Is.Empty); + Assert.IsTrue(Computer.Local.LocalGroups.Contains(lg)); + Assert.That(() => lg.Comment = newcomment, Throws.Nothing); + Assert.That(lg.Comment, Is.EqualTo(newcomment)); + } + finally + { + Computer.Local.LocalGroups.Remove(lg); + } + } + + [Test] + public void EnumUsersTest() + { + const string user = "TestUser123"; + const string comment = "testing user"; + const string newcomment = "new testing user"; + string hmfld = TestCaseSources.TempDir; + var pwd = Guid.NewGuid().ToString("B"); + + var acct = Computer.Local.UserAccounts.Add(user, pwd, null, comment, null, PInvoke.NetApi32.UserAcctCtrlFlags.UF_ACCOUNTDISABLE); + try + { + Assert.That(Computer.Local.UserAccounts.Count, Is.GreaterThanOrEqualTo(1)); + Assert.That(acct.UserName, Is.EqualTo(user)); + Assert.That(acct.Target, Is.EqualTo(Computer.Local.Target)); + Assert.That(acct.Comment, Is.EqualTo(comment)); + Assert.IsTrue(Computer.Local.UserAccounts.Contains(acct)); + Assert.That(() => acct.Comment = newcomment, Throws.Nothing); + Assert.That(acct.Comment, Is.EqualTo(newcomment)); + Assert.That(acct.HomeFolder, Is.Empty); + Assert.That(() => acct.HomeFolder = hmfld, Throws.Nothing); + Assert.That(acct.HomeFolder, Is.EqualTo(hmfld)); + } + finally + { + Computer.Local.UserAccounts.Remove(acct); + } + } + [Test] public void MapUnmapDriveTest() { diff --git a/UnitTests/System/DeviceTests.cs b/UnitTests/System/DeviceTests.cs index c35ee18c..52219a79 100644 --- a/UnitTests/System/DeviceTests.cs +++ b/UnitTests/System/DeviceTests.cs @@ -74,7 +74,7 @@ namespace Vanara.Diagnostics.Tests { cl.Properties[DEVPKEY_DeviceClass_Exclusive] = false; val = cl.Properties[DEVPKEY_DeviceClass_Exclusive]; - Assert.IsFalse((BOOLEAN)val); + Assert.IsFalse((bool)val); } finally {