diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs
new file mode 100644
index 000000000..81772c6b9
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs
@@ -0,0 +1,297 @@
+#region Using directives
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Security.Principal;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Add-LocalGroupMember cmdlet adds one or more users or groups to a local
+ /// group.
+ ///
+ [Cmdlet(VerbsCommon.Add, "LocalGroupMember",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717987")]
+ [Alias("algm")]
+ public class AddLocalGroupMemberCommand : PSCmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Group".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ParameterSetName = "Group")]
+ [ValidateNotNull]
+ public Microsoft.PowerShell.Commands.LocalGroup Group
+ {
+ get { return this.group;}
+ set { this.group = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalGroup group;
+
+ ///
+ /// The following is the definition of the input parameter "Member".
+ /// Specifies one or more users or groups to add to this local group. You can
+ /// identify users or groups by specifying their names or SIDs, or by passing
+ /// Microsoft.PowerShell.Commands.LocalPrincipal objects.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 1,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public Microsoft.PowerShell.Commands.LocalPrincipal[] Member
+ {
+ get { return this.member;}
+ set { this.member = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalPrincipal[] member;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+ #endregion Parameter Properties
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ if (Group != null)
+ ProcessGroup(Group);
+ else if (Name != null)
+ ProcessName(Name);
+ else if (SID != null)
+ ProcessSid(SID);
+ }
+ catch (GroupNotFoundException ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+
+ ///
+ /// Creates a list of objects
+ /// ready to be processed by the cmdlet.
+ ///
+ ///
+ /// Name or SID (as a string) of the group we'll be adding to.
+ /// This string is used primarily for specifying the target
+ /// in WhatIf scenarios.
+ ///
+ ///
+ /// LocalPrincipal object to be processed
+ ///
+ ///
+ /// A LocalPrincipal Object to be added to the group
+ ///
+ ///
+ ///
+ /// LocalPrincipal objects in the Member parameter may not be complete,
+ /// particularly those created from a name or a SID string given to the
+ /// Member cmdlet parameter. The object returned from this method contains
+ /// , at the very least, a valid SID.
+ ///
+ ///
+ /// Any Member objects provided by name or SID string will be looked up
+ /// to ensure that such an object exists. If an object is not found,
+ /// an error message is displayed by PowerShell and null will be returned
+ ///
+ ///
+ /// This method also handles the WhatIf scenario. If the Cmdlet's
+ /// ShouldProcess method returns false on any Member object,
+ /// that object will not be included in the returned List.
+ ///
+ ///
+ private LocalPrincipal MakePrincipal(string groupId, LocalPrincipal member)
+ {
+ LocalPrincipal principal = null;
+ // if the member has a SID, we can use it directly
+ if (member.SID != null)
+ {
+ principal = member;
+ }
+ else // otherwise it must have been constructed by name
+ {
+ SecurityIdentifier sid = this.TrySid(member.Name);
+
+ if (sid != null)
+ {
+ member.SID = sid;
+ principal = member;
+ }
+ else
+ {
+ try
+ {
+ principal = sam.LookupAccount(member.Name);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ if (CheckShouldProcess(principal, groupId))
+ return principal;
+
+ return null;
+ }
+
+
+ ///
+ /// Determine if a principal should be processed.
+ /// Just a wrapper around Cmdlet.ShouldProcess, with localized string
+ /// formatting.
+ ///
+ /// Name of the principal to be added.
+ ///
+ /// Name of the group to which the members will be added.
+ ///
+ ///
+ /// True if the principal should be processed, false otherwise.
+ ///
+ private bool CheckShouldProcess(LocalPrincipal principal, string groupName)
+ {
+ if (principal == null)
+ return false;
+
+ string msg = StringUtil.Format(Strings.ActionAddGroupMember, principal.ToString());
+
+ return ShouldProcess(groupName, msg);
+ }
+
+ ///
+ /// Add members to a group.
+ ///
+ ///
+ /// A object representing the group to which
+ /// the members will be added.
+ ///
+ private void ProcessGroup(LocalGroup group)
+ {
+ string groupId = group.Name ?? group.SID.ToString();
+ foreach (var member in this.Member)
+ {
+ LocalPrincipal principal = MakePrincipal(groupId, member);
+ if (null != principal)
+ {
+ var ex = sam.AddLocalGroupMember(group, principal);
+ if (null != ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Add members to a group specified by name.
+ ///
+ ///
+ /// The name of the group to which the members will be added.
+ ///
+ private void ProcessName(string name)
+ {
+ ProcessGroup(sam.GetLocalGroup(name));
+ }
+
+ ///
+ /// Add members to a group specified by SID.
+ ///
+ ///
+ /// A object identifying the group
+ /// to which the members will be added.
+ ///
+ private void ProcessSid(SecurityIdentifier groupSid)
+ {
+ foreach (var member in this.Member)
+ {
+ LocalPrincipal principal = MakePrincipal(groupSid.ToString(), member);
+ if (null != principal)
+ {
+ var ex = sam.AddLocalGroupMember(groupSid, principal);
+ if (null != ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs
new file mode 100644
index 000000000..2be2a7890
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs
@@ -0,0 +1,215 @@
+#region Using directives
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Disable-LocalUser cmdlet disables local user accounts. When a user
+ /// account is disabled, the user is not permitted to log on. When a user
+ /// account is enabled, the user is permitted to log on normally.
+ ///
+ [Cmdlet(VerbsLifecycle.Disable, "LocalUser",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717986")]
+ [Alias("dlu")]
+ public class DisableLocalUserCommand : Cmdlet
+ {
+ #region Constants
+ private const Enabling enabling = Enabling.Disable;
+ #endregion Constants
+
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the of the local user accounts to disable in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public Microsoft.PowerShell.Commands.LocalUser[] InputObject
+ {
+ get { return this.inputobject; }
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalUser[] inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the names of the local user accounts to disable in the local
+ /// Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public string[] Name
+ {
+ get { return this.name; }
+ set { this.name = value; }
+ }
+ private string[] name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies the LocalUser accounts to disable by
+ /// System.Security.Principal.SecurityIdentifier.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public System.Security.Principal.SecurityIdentifier[] SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier[] sid;
+ #endregion Parameter Properties
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ ProcessUsers();
+ ProcessNames();
+ ProcessSids();
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process users requested by -Name
+ ///
+ ///
+ /// All arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ ///
+ private void ProcessNames()
+ {
+ if (Name != null)
+ {
+ foreach (var name in Name)
+ {
+ try
+ {
+ if (CheckShouldProcess(name))
+ sam.EnableLocalUser(sam.GetLocalUser(name), enabling);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users requested by -SID
+ ///
+ private void ProcessSids()
+ {
+ if (SID != null)
+ {
+ foreach (var sid in SID)
+ {
+ try
+ {
+ if (CheckShouldProcess(sid.ToString()))
+ sam.EnableLocalUser(sid, enabling);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users requested by -InputObject
+ ///
+ private void ProcessUsers()
+ {
+ if (InputObject != null)
+ {
+ foreach (var user in InputObject)
+ {
+ try
+ {
+ if (CheckShouldProcess(user.Name))
+ sam.EnableLocalUser(user, enabling);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionDisableUser);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs
new file mode 100644
index 000000000..cc44e4ecf
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs
@@ -0,0 +1,217 @@
+#region Using directives
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Enable-LocalUser cmdlet enables local user accounts. When a user account
+ /// is disabled, the user is not permitted to log on. When a user account is
+ /// enabled, the user is permitted to log on normally.
+ ///
+ [Cmdlet(VerbsLifecycle.Enable, "LocalUser",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717985")]
+ [Alias("elu")]
+ public class EnableLocalUserCommand : Cmdlet
+ {
+ #region Constants
+ private const Enabling enabling = Enabling.Enable;
+ #endregion Constants
+
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the of the local user accounts to enable in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public Microsoft.PowerShell.Commands.LocalUser[] InputObject
+ {
+ get { return this.inputobject; }
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalUser[] inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local user accounts to enable in the local Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public string[] Name
+ {
+ get { return this.name; }
+ set { this.name = value; }
+ }
+ private string[] name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies the LocalUser accounts to enable by
+ /// System.Security.Principal.SecurityIdentifier.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public System.Security.Principal.SecurityIdentifier[] SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier[] sid;
+ #endregion Parameter Properties
+
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ ProcessUsers();
+ ProcessNames();
+ ProcessSids();
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process users requested by -Name
+ ///
+ ///
+ /// All arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ ///
+ private void ProcessNames()
+ {
+ if (Name != null)
+ {
+ foreach (var name in Name)
+ {
+ try
+ {
+ if (CheckShouldProcess(name))
+ sam.EnableLocalUser(sam.GetLocalUser(name), enabling);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users requested by -SID
+ ///
+ private void ProcessSids()
+ {
+ if (SID != null)
+ {
+ foreach (var sid in SID)
+ {
+ try
+ {
+ if (CheckShouldProcess(sid.ToString()))
+ sam.EnableLocalUser(sid, enabling);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users requested by -InputObject
+ ///
+ private void ProcessUsers()
+ {
+ if (InputObject != null)
+ {
+ foreach (var user in InputObject)
+ {
+ try
+ {
+ if (CheckShouldProcess(user.Name))
+ sam.EnableLocalUser(user, enabling);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionEnableUser);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs
new file mode 100644
index 000000000..e0141e588
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs
@@ -0,0 +1,170 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+using System.Security.Principal;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Get-LocalGroup cmdlet gets local groups from the Windows Security
+ /// Accounts manager.
+ ///
+ [Cmdlet(VerbsCommon.Get, "LocalGroup",
+ DefaultParameterSetName = "Default",
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717974")]
+ [Alias("glg")]
+ public class GetLocalGroupCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local groups to get from the local Security Accounts Manager.
+ ///
+ [Parameter(Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNull]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public string[] Name
+ {
+ get { return this.name; }
+ set { this.name = value; }
+ }
+ private string[] name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a local group from the local Security Accounts Manager.
+ ///
+ [Parameter(Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public System.Security.Principal.SecurityIdentifier[] SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier[] sid;
+ #endregion Parameter Properties
+
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ if (Name == null && SID == null)
+ {
+ foreach (var group in sam.GetAllLocalGroups())
+ WriteObject(group);
+
+ return;
+ }
+
+ ProcessNames();
+ ProcessSids();
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process groups requested by -Name
+ ///
+ ///
+ /// All arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ /// Groups may be specified using wildcards.
+ ///
+ private void ProcessNames()
+ {
+ if (Name != null)
+ {
+ foreach (var name in Name)
+ {
+ try
+ {
+ if (WildcardPattern.ContainsWildcardCharacters(name))
+ {
+ var pattern = new WildcardPattern(name, WildcardOptions.Compiled
+ | WildcardOptions.IgnoreCase);
+
+ foreach (var group in sam.GetMatchingLocalGroups(n => pattern.IsMatch(n)))
+ WriteObject(group);
+ }
+ else
+ {
+ WriteObject(sam.GetLocalGroup(name));
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process groups requested by -SID
+ ///
+ private void ProcessSids()
+ {
+ if (SID != null)
+ {
+ foreach (var sid in SID)
+ {
+ try
+ {
+ WriteObject(sam.GetLocalGroup(sid));
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs
new file mode 100644
index 000000000..e5704d8c0
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs
@@ -0,0 +1,229 @@
+#region Using directives
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Security.Principal;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Get-LocalGroupMember cmdlet gets the members of a local group.
+ ///
+ [Cmdlet(VerbsCommon.Get, "LocalGroupMember",
+ DefaultParameterSetName = "Default",
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717988")]
+ [Alias("glgm")]
+ public class GetLocalGroupMemberCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Group".
+ /// The security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Group")]
+ [ValidateNotNull]
+ public Microsoft.PowerShell.Commands.LocalGroup Group
+ {
+ get { return this.group;}
+ set { this.group = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalGroup group;
+
+ ///
+ /// The following is the definition of the input parameter "Member".
+ /// Specifies the name of the user or group that is a member of this group. If
+ /// this parameter is not specified, all members of the specified group are
+ /// returned. This accepts a name, SID, or wildcard string.
+ ///
+ [Parameter(Position = 1)]
+ [ValidateNotNullOrEmpty]
+ public string Member
+ {
+ get { return this.member;}
+ set { this.member = value; }
+ }
+ private string member;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// The security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// The security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNullOrEmpty]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+ #endregion Parameter Properties
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ IEnumerable principals = null;
+
+ if (Group != null)
+ principals = ProcessGroup(Group);
+ else if (Name != null)
+ principals = ProcessName(Name);
+ else if (SID != null)
+ principals = ProcessSid(SID);
+
+ if (principals != null)
+ WriteObject(principals, true);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ private IEnumerable ProcessesMembership(IEnumerable membership)
+ {
+ List rv;
+
+ // if no members are specified, return all of them
+ if (Member == null)
+ {
+ //return membership;
+ rv = new List(membership);
+ }
+ else
+ {
+ //var rv = new List();
+ rv = new List();
+
+ if (WildcardPattern.ContainsWildcardCharacters(Member))
+ {
+ var pattern = new WildcardPattern(Member, WildcardOptions.Compiled
+ | WildcardOptions.IgnoreCase);
+
+ foreach (var m in membership)
+ if (pattern.IsMatch(sam.StripMachineName(m.Name)))
+ rv.Add(m);
+ }
+ else
+ {
+ var sid = this.TrySid(Member);
+
+ if (sid != null)
+ {
+ foreach (var m in membership)
+ {
+ if (m.SID == sid)
+ {
+ rv.Add(m);
+ break;
+ }
+ }
+ }
+ else
+ {
+ foreach (var m in membership)
+ {
+ if (sam.StripMachineName(m.Name).Equals(Member, StringComparison.CurrentCultureIgnoreCase))
+ {
+ rv.Add(m);
+ break;
+ }
+ }
+ }
+
+ if (rv.Count == 0)
+ {
+ var ex = new PrincipalNotFoundException(member, member);
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ // sort the resulting principals by mane
+ rv.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, StringComparison.CurrentCultureIgnoreCase));
+
+ return rv;
+ }
+
+ private IEnumerable ProcessGroup(LocalGroup group)
+ {
+ return ProcessesMembership(sam.GetLocalGroupMembers(group));
+ }
+
+ private IEnumerable ProcessName(string name)
+ {
+ return ProcessGroup(sam.GetLocalGroup(name));
+ }
+
+ private IEnumerable ProcessSid(SecurityIdentifier groupSid)
+ {
+ return ProcessesMembership(sam.GetLocalGroupMembers(groupSid));
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs
new file mode 100644
index 000000000..493bfc773
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs
@@ -0,0 +1,172 @@
+#region Using directives
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Get-LocalUser cmdlet gets local user accounts from the Windows Security
+ /// Accounts Manager. This includes local accounts that have been connected to a
+ /// Microsoft account.
+ ///
+ [Cmdlet(VerbsCommon.Get, "LocalUser",
+ DefaultParameterSetName = "Default",
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717980")]
+ [Alias("glu")]
+ public class GetLocalUserCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local user accounts to get from the local Security Accounts
+ /// Manager. This accepts a name or wildcard string.
+ ///
+ [Parameter(Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNull]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public string[] Name
+ {
+ get { return this.name; }
+
+ set { this.name = value; }
+ }
+ private string[] name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a user from the local Security Accounts Manager.
+ ///
+ [Parameter(Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public System.Security.Principal.SecurityIdentifier[] SID
+ {
+ get { return this.sid; }
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier[] sid;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ if (Name == null && SID == null)
+ {
+ foreach (var user in sam.GetAllLocalUsers())
+ WriteObject(user);
+
+ return;
+ }
+
+ ProcessNames();
+ ProcessSids();
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process users requested by -Name
+ ///
+ ///
+ /// All arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ /// Users may be specified using wildcards.
+ ///
+ private void ProcessNames()
+ {
+ if (Name != null)
+ {
+ foreach (var nm in Name)
+ {
+ try
+ {
+ if (WildcardPattern.ContainsWildcardCharacters(nm))
+ {
+ var pattern = new WildcardPattern(nm, WildcardOptions.Compiled
+ | WildcardOptions.IgnoreCase);
+
+ foreach (var user in sam.GetMatchingLocalUsers(n => pattern.IsMatch(n)))
+ WriteObject(user);
+ }
+ else
+ {
+ WriteObject(sam.GetLocalUser(nm));
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users requested by -SID
+ ///
+ private void ProcessSids()
+ {
+ if (SID != null)
+ {
+ foreach (var s in SID)
+ {
+ try
+ {
+ WriteObject(sam.GetLocalUser(s));
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs
new file mode 100644
index 000000000..4ded31ba6
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs
@@ -0,0 +1,120 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The New-LocalGroup Cmdlet can be used to create a new local security group
+ /// in the Windows Security Accounts Manager.
+ ///
+ [Cmdlet(VerbsCommon.New, "LocalGroup",
+ SupportsShouldProcess = true,
+ HelpUri ="http://go.microsoft.com/fwlink/?LinkId=717990")]
+ [Alias("nlg")]
+ public class NewLocalGroupCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Description".
+ /// A descriptive comment (48 characters).
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNull]
+ [ValidateLength(0, 48)]
+ public string Description
+ {
+ get { return this.description;}
+ set { this.description = value; }
+ }
+ private string description;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// The group name for the local security group.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ [ValidateLength(1, 256)]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ if (CheckShouldProcess(Name))
+ {
+ var group = sam.CreateLocalGroup(new LocalGroup
+ {
+ Description = Description,
+ Name = Name
+ });
+
+ WriteObject(group);
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionNewGroup);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs
new file mode 100644
index 000000000..5b01e0ff1
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs
@@ -0,0 +1,277 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The New-LocalUser cmdlet creates a new local user account.
+ ///
+ [Cmdlet(VerbsCommon.New, "LocalUser",
+ DefaultParameterSetName = "Password",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717981")]
+ [Alias("nlu")]
+ public class NewLocalUserCommand : PSCmdlet
+ {
+ #region Static Data
+ // Names of object- and boolean-type parameters.
+ // Switch parameters don't need to be included.
+ private static string[] parameterNames = new string[]
+ {
+ "AccountExpires",
+ "Description",
+ "Disabled",
+ "FullName",
+ "Password",
+ "UserMayNotChangePassword"
+ };
+ #endregion Static Data
+
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "AccountExpires".
+ /// Specifies when the user account will expire.
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ public System.DateTime AccountExpires
+ {
+ get { return this.accountexpires;}
+ set { this.accountexpires = value; }
+ }
+ private System.DateTime accountexpires;
+
+ // This parameter added by hand (copied from SetLocalUserCommand), not by Cmdlet Designer
+ ///
+ /// The following is the definition of the input parameter "AccountNeverExpires".
+ /// Specifies that the account will not expire.
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ public System.Management.Automation.SwitchParameter AccountNeverExpires
+ {
+ get { return this.accountneverexpires;}
+ set { this.accountneverexpires = value; }
+ }
+ private System.Management.Automation.SwitchParameter accountneverexpires;
+
+ ///
+ /// The following is the definition of the input parameter "Description".
+ /// A descriptive comment for this user account (48 characters).
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNull]
+ [ValidateLength(0, 48)]
+ public string Description
+ {
+ get { return this.description;}
+ set { this.description = value; }
+ }
+ private string description;
+
+ ///
+ /// The following is the definition of the input parameter "Disabled".
+ /// Specifies whether this user account is enabled or disabled.
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ public System.Management.Automation.SwitchParameter Disabled
+ {
+ get { return this.disabled;}
+ set { this.disabled = value; }
+ }
+ private System.Management.Automation.SwitchParameter disabled;
+
+ ///
+ /// The following is the definition of the input parameter "FullName".
+ /// Specifies the full name of the user account. This is different from the
+ /// username of the user account.
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNull]
+ public string FullName
+ {
+ get { return this.fullname;}
+ set { this.fullname = value; }
+ }
+ private string fullname;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the user name for the local user account. This can be a local user
+ /// account or a local user account that is connected to a Microsoft Account.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ [ValidateLength(1, 20)]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "Password".
+ /// Specifies the password for the local user account. A password can contain up
+ /// to 127 characters.
+ ///
+ [Parameter(Mandatory = true,
+ ParameterSetName = "Password",
+ ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNull]
+ public System.Security.SecureString Password
+ {
+ get { return this.password;}
+ set { this.password = value; }
+ }
+ private System.Security.SecureString password;
+
+ ///
+ /// The following is the definition of the input parameter "PasswordChangeableDate".
+ /// Specifies that the new User account has no password.
+ ///
+ [Parameter(Mandatory = true,
+ ParameterSetName = "NoPassword",
+ ValueFromPipelineByPropertyName = true)]
+ public System.Management.Automation.SwitchParameter NoPassword
+ {
+ get { return this.nopassword; }
+ set { this.nopassword = value; }
+ }
+ private System.Management.Automation.SwitchParameter nopassword;
+
+ ///
+ /// The following is the definition of the input parameter "PasswordNeverExpires".
+ /// Specifies that the password will not expire.
+ ///
+ [Parameter(ParameterSetName = "Password",
+ ValueFromPipelineByPropertyName = true)]
+ public System.Management.Automation.SwitchParameter PasswordNeverExpires
+ {
+ get { return this.passwordneverexpires; }
+ set { this.passwordneverexpires = value; }
+ }
+ private System.Management.Automation.SwitchParameter passwordneverexpires;
+
+ ///
+ /// The following is the definition of the input parameter "UserMayNotChangePassword".
+ /// Specifies whether the user is allowed to change the password on this
+ /// account. The default value is True.
+ ///
+ [Parameter(ValueFromPipelineByPropertyName = true)]
+ public System.Management.Automation.SwitchParameter UserMayNotChangePassword
+ {
+ get { return this.usermaynotchangepassword;}
+ set { this.usermaynotchangepassword = value; }
+ }
+ private System.Management.Automation.SwitchParameter usermaynotchangepassword;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ if (this.HasParameter("AccountExpires") && AccountNeverExpires.IsPresent)
+ {
+ InvalidParametersException ex = new InvalidParametersException("AccountExpires", "AccountNeverExpires");
+ ThrowTerminatingError(ex.MakeErrorRecord());
+ }
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ if (CheckShouldProcess(Name))
+ {
+ var user = new LocalUser
+ {
+ Name = Name,
+ Description = Description,
+ Enabled = true,
+ FullName = FullName,
+ UserMayChangePassword = true
+ };
+
+ foreach (var paramName in parameterNames)
+ {
+ if (this.HasParameter(paramName))
+ {
+ switch (paramName)
+ {
+ case "AccountExpires":
+ user.AccountExpires = AccountExpires;
+ break;
+
+ case "Disabled":
+ user.Enabled = !Disabled;
+ break;
+
+ case "UserMayNotChangePassword":
+ user.UserMayChangePassword = !UserMayNotChangePassword;
+ break;
+ }
+ }
+ }
+
+ if (AccountNeverExpires.IsPresent)
+ user.AccountExpires = null;
+
+ // Password will be null if NoPassword was given
+ user = sam.CreateLocalUser(user, Password, PasswordNeverExpires.IsPresent);
+
+ WriteObject(user);
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionNewUser);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs
new file mode 100644
index 000000000..24a467c9c
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs
@@ -0,0 +1,208 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Remove-LocalGroup cmdlet deletes a security group from the Windows
+ /// Security Accounts manager.
+ ///
+ [Cmdlet(VerbsCommon.Remove, "LocalGroup",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717975")]
+ [Alias("rlg")]
+ public class RemoveLocalGroupCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies security groups from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public Microsoft.PowerShell.Commands.LocalGroup[] InputObject
+ {
+ get { return this.inputobject; }
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalGroup[] inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local groups to be deleted from the local Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public string[] Name
+ {
+ get { return this.name; }
+ set { this.name = value; }
+ }
+ private string[] name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies the LocalGroup accounts to remove by
+ /// System.Security.Principal.SecurityIdentifier.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public System.Security.Principal.SecurityIdentifier[] SID
+ {
+ get { return this.sid; }
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier[] sid;
+ #endregion Parameter Properties
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ ProcessGroups();
+ ProcessNames();
+ ProcessSids();
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process groups requested by -Name
+ ///
+ ///
+ /// All arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ ///
+ private void ProcessNames()
+ {
+ if (Name != null)
+ {
+ foreach (var name in Name)
+ {
+ try
+ {
+ if (CheckShouldProcess(name))
+ sam.RemoveLocalGroup(sam.GetLocalGroup(name));
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process groups requested by -SID
+ ///
+ private void ProcessSids()
+ {
+ if (SID != null)
+ {
+ foreach (var sid in SID)
+ {
+ try
+ {
+ if (CheckShouldProcess(sid.ToString()))
+ sam.RemoveLocalGroup(sid);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process groups given through -InputObject
+ ///
+ private void ProcessGroups()
+ {
+ if (InputObject != null)
+ {
+ foreach (var group in InputObject)
+ {
+ try
+ {
+ if (CheckShouldProcess(group.Name))
+ sam.RemoveLocalGroup(group);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionRemoveGroup);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs
new file mode 100644
index 000000000..d06669b7c
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs
@@ -0,0 +1,295 @@
+#region Using directives
+using System;
+using System.Collections.Generic;
+using System.Management.Automation;
+using System.Security.Principal;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Remove-LocalGroupMember cmdlet removes one or more members (users or
+ /// groups) from a local security group.
+ ///
+ [Cmdlet(VerbsCommon.Remove, "LocalGroupMember",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717989")]
+ [Alias("rlgm")]
+ public class RemoveLocalGroupMemberCommand : PSCmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Group".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ParameterSetName = "Group")]
+ [ValidateNotNull]
+ public Microsoft.PowerShell.Commands.LocalGroup Group
+ {
+ get { return this.group;}
+ set { this.group = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalGroup group;
+
+ ///
+ /// The following is the definition of the input parameter "Member".
+ /// Specifies one or more users or groups to remove from this local group. You can
+ /// identify users or groups by specifying their names or SIDs, or by passing
+ /// Microsoft.PowerShell.Commands.LocalPrincipal objects.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 1,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true)]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public Microsoft.PowerShell.Commands.LocalPrincipal[] Member
+ {
+ get { return this.member;}
+ set { this.member = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalPrincipal[] member;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// The security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+ #endregion Parameter Properties
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ if (Group != null)
+ ProcessGroup(Group);
+ else if (Name != null)
+ ProcessName(Name);
+ else if (SID != null)
+ ProcessSid(SID);
+ }
+ catch (GroupNotFoundException ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+
+ ///
+ /// Creates a list of objects
+ /// ready to be processed by the cmdlet.
+ ///
+ ///
+ /// Name or SID (as a string) of the group we'll be removing from.
+ /// This string is used primarily for specifying the target
+ /// in WhatIf scenarios.
+ ///
+ ///
+ /// LocalPrincipal object to be processed
+ ///
+ ///
+ /// LocalPrincipal object processed and ready to be removed
+ ///
+ ///
+ ///
+ /// LocalPrincipal object in the Member parameter may not be complete,
+ /// particularly those created from a name or a SID string given to the
+ /// Member cmdlet parameter. The object returned from this method contains at the very least, contain a valid SID.
+ ///
+ ///
+ /// Any Member object provided by name or SID string will be looked up
+ /// to ensure that such an object exists. If an object is not found,
+ /// an error message is displayed by PowerShell and null will be returned from this method
+ ///
+ ///
+ /// This method also handles the WhatIf scenario. If the Cmdlet's
+ /// ShouldProcess method returns false on any Member object
+ ///
+ ///
+ private LocalPrincipal MakePrincipal(string groupId, LocalPrincipal member)
+ {
+ LocalPrincipal principal = null;
+
+ // if the member has a SID, we can use it directly
+ if (member.SID != null)
+ {
+ principal = member;
+ }
+ else // otherwise it must have been constructed by name
+ {
+ SecurityIdentifier sid = this.TrySid(member.Name);
+
+ if (sid != null)
+ {
+ member.SID = sid;
+ principal = member;
+ }
+ else
+ {
+ try
+ {
+ principal = sam.LookupAccount(member.Name);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ if (CheckShouldProcess(principal, groupId))
+ return principal;
+
+ return null;
+ }
+
+ ///
+ /// Determine if a principal should be processed.
+ /// Just a wrapper around Cmdlet.ShouldProcess, with localized string
+ /// formatting.
+ ///
+ /// Name of the principal to be removed.
+ ///
+ /// Name of the group from which the members will be removed.
+ ///
+ ///
+ /// True if the principal should be processed, false otherwise.
+ ///
+ private bool CheckShouldProcess(LocalPrincipal principal, string groupName)
+ {
+ if (principal == null)
+ return false;
+
+ string msg = StringUtil.Format(Strings.ActionRemoveGroupMember, principal.ToString());
+
+ return ShouldProcess(groupName, msg);
+ }
+
+ ///
+ /// Remove members from a group.
+ ///
+ ///
+ /// A object representing the group from which
+ /// the members will be removed.
+ ///
+ private void ProcessGroup(LocalGroup group)
+ {
+ string groupId = group.Name ?? group.SID.ToString();
+ foreach (var member in this.Member)
+ {
+ LocalPrincipal principal = MakePrincipal(groupId, member);
+ if (null != principal)
+ {
+ var ex = sam.RemoveLocalGroupMember(group, principal);
+ if (null != ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Remove members from a group specified by name.
+ ///
+ ///
+ /// The name of the group from which the members will be removed.
+ ///
+ private void ProcessName(string name)
+ {
+ ProcessGroup(sam.GetLocalGroup(name));
+ }
+
+ ///
+ /// Remove members from a group specified by SID.
+ ///
+ ///
+ /// A object identifying the group
+ /// from which the members will be removed.
+ ///
+ private void ProcessSid(SecurityIdentifier groupSid)
+ {
+ foreach (var member in this.Member)
+ {
+ LocalPrincipal principal = MakePrincipal(groupSid.ToString(), member);
+ if (null != principal)
+ {
+ var ex = sam.RemoveLocalGroupMember(groupSid, principal);
+ if (null != ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs
new file mode 100644
index 000000000..3d07a155c
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs
@@ -0,0 +1,210 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+using System.Diagnostics.CodeAnalysis;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Remove-LocalUser cmdlet deletes a user account from the Windows Security
+ /// Accounts manager.
+ ///
+ [Cmdlet(VerbsCommon.Remove, "LocalUser",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717982")]
+ [Alias("rlu")]
+ public class RemoveLocalUserCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the of the local user accounts to remove in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public Microsoft.PowerShell.Commands.LocalUser[] InputObject
+ {
+ get { return this.inputobject;}
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalUser[] inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the user accounts to be deleted from the local Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public string[] Name
+ {
+ get { return this.name; }
+ set { this.name = value; }
+ }
+ private string[] name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies the local user accounts to remove by
+ /// System.Security.Principal.SecurityIdentifier.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNullOrEmpty]
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")]
+ public System.Security.Principal.SecurityIdentifier[] SID
+ {
+ get { return this.sid; }
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier[] sid;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ ProcessUsers();
+ ProcessNames();
+ ProcessSids();
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process users requested by -Name
+ ///
+ ///
+ /// All arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ ///
+ private void ProcessNames()
+ {
+ if (Name != null)
+ {
+ foreach (var name in Name)
+ {
+ try
+ {
+ if (CheckShouldProcess(name))
+ sam.RemoveLocalUser(sam.GetLocalUser(name));
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users requested by -SID
+ ///
+ private void ProcessSids()
+ {
+ if (SID != null)
+ {
+ foreach (var sid in SID)
+ {
+ try
+ {
+ if (CheckShouldProcess(sid.ToString()))
+ sam.RemoveLocalUser(sid);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ ///
+ /// Process users given through -InputObject
+ ///
+ private void ProcessUsers()
+ {
+ if (InputObject != null)
+ {
+ foreach (var user in InputObject)
+ {
+ try
+ {
+ if (CheckShouldProcess(user.Name))
+ sam.RemoveLocalUser(user);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+ }
+
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionRemoveUser);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs
new file mode 100644
index 000000000..c64beb11c
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs
@@ -0,0 +1,226 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Rename-LocalGroup cmdlet renames a local security group in the Security
+ /// Accounts Manager.
+ ///
+ [Cmdlet(VerbsCommon.Rename, "LocalGroup",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717978")]
+ [Alias("rnlg")]
+ public class RenameLocalGroupCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the of the local group account to rename in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNullOrEmpty]
+ public Microsoft.PowerShell.Commands.LocalGroup InputObject
+ {
+ get { return this.inputobject;}
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalGroup inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local group to be renamed in the local Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "NewName".
+ /// Specifies the new name for the local security group in the Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 1)]
+ [ValidateNotNullOrEmpty]
+ public string NewName
+ {
+ get { return this.newname;}
+ set { this.newname = value; }
+ }
+ private string newname;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNullOrEmpty]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+ #endregion Parameter Properties
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ ProcessGroup();
+ ProcessName();
+ ProcessSid();
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process group requested by -Name
+ ///
+ ///
+ /// Arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ ///
+ private void ProcessName()
+ {
+ if (Name != null)
+ {
+ try
+ {
+ if (CheckShouldProcess(Name, NewName))
+ sam.RenameLocalGroup(sam.GetLocalGroup(Name), NewName);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ ///
+ /// Process group requested by -SID
+ ///
+ private void ProcessSid()
+ {
+ if (SID != null)
+ {
+ try
+ {
+ if (CheckShouldProcess(SID.ToString(), NewName))
+ sam.RenameLocalGroup(SID, NewName);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ ///
+ /// Process group given through -InputObject
+ ///
+ private void ProcessGroup()
+ {
+ if (InputObject != null)
+ {
+ try
+ {
+ if (CheckShouldProcess(InputObject.Name, NewName))
+ sam.RenameLocalGroup(InputObject, NewName);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ ///
+ /// Determine if a group should be processed.
+ /// Just a wrapper around Cmdlet.ShouldProcess, with localized string
+ /// formatting.
+ ///
+ ///
+ /// Name of the group to rename.
+ ///
+ ///
+ /// New name for the group.
+ ///
+ ///
+ /// True if the group should be processed, false otherwise.
+ ///
+ private bool CheckShouldProcess(string groupName, string newName)
+ {
+ string msg = StringUtil.Format(Strings.ActionRenameGroup, newName);
+
+ return ShouldProcess(groupName, msg);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs
new file mode 100644
index 000000000..148ef3843
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs
@@ -0,0 +1,227 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Rename-LocalUser cmdlet renames a local user account in the Security
+ /// Accounts Manager.
+ ///
+ [Cmdlet(VerbsCommon.Rename, "LocalUser",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkID=717983")]
+ [Alias("rnlu")]
+ public class RenameLocalUserCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the of the local user account to rename in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNull]
+ public Microsoft.PowerShell.Commands.LocalUser InputObject
+ {
+ get { return this.inputobject;}
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalUser inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local user account to be renamed in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNullOrEmpty]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "NewName".
+ /// Specifies the new name for the local user account in the Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 1)]
+ [ValidateNotNullOrEmpty]
+ public string NewName
+ {
+ get { return this.newname;}
+ set { this.newname = value; }
+ }
+ private string newname;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies the local user to rename.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ ProcessUser();
+ ProcessName();
+ ProcessSid();
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ ///
+ /// Process user requested by -Name
+ ///
+ ///
+ /// Arguments to -Name will be treated as names,
+ /// even if a name looks like a SID.
+ ///
+ private void ProcessName()
+ {
+ if (Name != null)
+ {
+ try
+ {
+ if (CheckShouldProcess(Name, NewName))
+ sam.RenameLocalUser(sam.GetLocalUser(Name), NewName);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ ///
+ /// Process user requested by -SID
+ ///
+ private void ProcessSid()
+ {
+ if (SID != null)
+ {
+ try
+ {
+ if (CheckShouldProcess(SID.ToString(), NewName))
+ sam.RenameLocalUser(SID, NewName);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ ///
+ /// Process group given through -InputObject
+ ///
+ private void ProcessUser()
+ {
+ if (InputObject != null)
+ {
+ try
+ {
+ if (CheckShouldProcess(InputObject.Name, NewName))
+ sam.RenameLocalUser(InputObject, NewName);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+ }
+
+ ///
+ /// Determine if a user should be processed.
+ /// Just a wrapper around Cmdlet.ShouldProcess, with localized string
+ /// formatting.
+ ///
+ ///
+ /// Name of the user to rename.
+ ///
+ ///
+ /// New name for the user.
+ ///
+ ///
+ /// True if the user should be processed, false otherwise.
+ ///
+ private bool CheckShouldProcess(string userName, string newName)
+ {
+ string msg = StringUtil.Format(Strings.ActionRenameUser, newName);
+
+ return ShouldProcess(userName, msg);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs
new file mode 100644
index 000000000..9abfffddb
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs
@@ -0,0 +1,175 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Set-LocalGroup cmdlet modifies the properties of a local security group
+ /// in the Windows Security Accounts Manager.
+ ///
+ [Cmdlet(VerbsCommon.Set, "LocalGroup",
+ SupportsShouldProcess = true,
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717979")]
+ [Alias("slg")]
+ public class SetLocalGroupCommand : Cmdlet
+ {
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "Description".
+ /// A descriptive comment (48 characters).
+ ///
+ [Parameter(Mandatory = true)]
+ [ValidateNotNull]
+ [ValidateLength(0, 48)]
+ public string Description
+ {
+ get { return this.description;}
+ set { this.description = value; }
+ }
+ private string description;
+
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the local group account to modify in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNull]
+ public Microsoft.PowerShell.Commands.LocalGroup InputObject
+ {
+ get { return this.inputobject;}
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalGroup inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local group to be renamed in the local Security Accounts
+ /// Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Default")]
+ [ValidateNotNull]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a security group from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ LocalGroup group = null;
+
+ if (InputObject != null)
+ {
+ if (CheckShouldProcess(InputObject.ToString()))
+ group = InputObject;
+ }
+ else if (Name != null)
+ {
+ group = sam.GetLocalGroup(Name);
+
+ if (!CheckShouldProcess(Name))
+ group = null;
+ }
+ else if (SID != null)
+ {
+ group = sam.GetLocalGroup(SID);
+
+ if (!CheckShouldProcess(SID.ToString()))
+ group = null;
+ }
+
+ if (group != null)
+ {
+ var delta = group.Clone();
+
+ delta.Description = Description;
+ sam.UpdateLocalGroup(group, delta);
+ }
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionSetGroup);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs
new file mode 100644
index 000000000..098c43f63
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs
@@ -0,0 +1,314 @@
+#region Using directives
+using System;
+using System.Management.Automation;
+
+using System.Management.Automation.SecurityAccountsManager;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+
+using Microsoft.PowerShell.LocalAccounts;
+#endregion
+
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// The Set-LocalUser cmdlet changes the properties of a user account in the
+ /// local Windows Security Accounts Manager. It can also reset the password of a
+ /// local user account.
+ ///
+ [Cmdlet(VerbsCommon.Set, "LocalUser",
+ SupportsShouldProcess = true,
+ DefaultParameterSetName = "Name",
+ HelpUri = "http://go.microsoft.com/fwlink/?LinkId=717984")]
+ [Alias("slu")]
+ public class SetLocalUserCommand : PSCmdlet
+ {
+ #region Static Data
+ // Names of object- and boolean-type parameters.
+ // Switch parameters don't need to be included.
+ private static string[] parameterNames = new string[]
+ {
+ "AccountExpires",
+ "Description",
+ "FullName",
+ "Password",
+ "UserMayChangePassword",
+ "PasswordNeverExpires"
+ };
+ #endregion Static Data
+
+ #region Instance Data
+ private Sam sam = null;
+ #endregion Instance Data
+
+ #region Parameter Properties
+ ///
+ /// The following is the definition of the input parameter "AccountExpires".
+ /// Specifies when the user account will expire. Set to null to indicate that
+ /// the account will never expire. The default value is null (account never
+ /// expires).
+ ///
+ [Parameter]
+ public System.DateTime AccountExpires
+ {
+ get { return this.accountexpires;}
+ set { this.accountexpires = value; }
+ }
+ private System.DateTime accountexpires;
+
+ ///
+ /// The following is the definition of the input parameter "AccountNeverExpires".
+ /// Specifies that the account will not expire.
+ ///
+ [Parameter]
+ public System.Management.Automation.SwitchParameter AccountNeverExpires
+ {
+ get { return this.accountneverexpires;}
+ set { this.accountneverexpires = value; }
+ }
+ private System.Management.Automation.SwitchParameter accountneverexpires;
+
+ ///
+ /// The following is the definition of the input parameter "Description".
+ /// A descriptive comment for this user account (48 characters).
+ ///
+ [Parameter]
+ [ValidateNotNull]
+ [ValidateLength(0, 48)]
+ public string Description
+ {
+ get { return this.description;}
+ set { this.description = value; }
+ }
+ private string description;
+
+ ///
+ /// The following is the definition of the input parameter "FullName".
+ /// Specifies the full name of the user account. This is different from the
+ /// username of the user account.
+ ///
+ [Parameter]
+ [ValidateNotNull]
+ public string FullName
+ {
+ get { return this.fullname;}
+ set { this.fullname = value; }
+ }
+ private string fullname;
+ ///
+ /// The following is the definition of the input parameter "InputObject".
+ /// Specifies the of the local user account to modify in the local Security
+ /// Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "InputObject")]
+ [ValidateNotNull]
+ public Microsoft.PowerShell.Commands.LocalUser InputObject
+ {
+ get { return this.inputobject;}
+ set { this.inputobject = value; }
+ }
+ private Microsoft.PowerShell.Commands.LocalUser inputobject;
+
+ ///
+ /// The following is the definition of the input parameter "Name".
+ /// Specifies the local user account to change.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "Name")]
+ [ValidateNotNullOrEmpty]
+ public string Name
+ {
+ get { return this.name;}
+ set { this.name = value; }
+ }
+ private string name;
+
+ ///
+ /// The following is the definition of the input parameter "Password".
+ /// Specifies the password for the local user account.
+ ///
+ [Parameter]
+ [ValidateNotNull]
+ public System.Security.SecureString Password
+ {
+ get { return this.password;}
+ set { this.password = value; }
+ }
+ private System.Security.SecureString password;
+
+ ///
+ /// The following is the definition of the input parameter "PasswordNeverExpires".
+ /// Specifies that the password will not expire.
+ ///
+ [Parameter]
+ public System.Boolean PasswordNeverExpires
+ {
+ get { return this.passwordneverexpires; }
+ set { this.passwordneverexpires = value; }
+ }
+ private System.Boolean passwordneverexpires;
+
+ ///
+ /// The following is the definition of the input parameter "SID".
+ /// Specifies a user from the local Security Accounts Manager.
+ ///
+ [Parameter(Mandatory = true,
+ Position = 0,
+ ValueFromPipeline = true,
+ ValueFromPipelineByPropertyName = true,
+ ParameterSetName = "SecurityIdentifier")]
+ [ValidateNotNull]
+ public System.Security.Principal.SecurityIdentifier SID
+ {
+ get { return this.sid;}
+ set { this.sid = value; }
+ }
+ private System.Security.Principal.SecurityIdentifier sid;
+
+ ///
+ /// The following is the definition of the input parameter "UserMayChangePassword".
+ /// Specifies whether the user is allowed to change the password on this
+ /// account. The default value is True.
+ ///
+ [Parameter]
+ public System.Boolean UserMayChangePassword
+ {
+ get { return this.usermaychangepassword;}
+ set { this.usermaychangepassword = value; }
+ }
+ private System.Boolean usermaychangepassword;
+ #endregion Parameter Properties
+
+
+
+ #region Cmdlet Overrides
+ ///
+ /// BeginProcessing method.
+ ///
+ protected override void BeginProcessing()
+ {
+ if (this.HasParameter("AccountExpires") && AccountNeverExpires.IsPresent)
+ {
+ InvalidParametersException ex = new InvalidParametersException("AccountExpires", "AccountNeverExpires");
+ ThrowTerminatingError(ex.MakeErrorRecord());
+ }
+ sam = new Sam();
+ }
+
+
+ ///
+ /// ProcessRecord method.
+ ///
+ protected override void ProcessRecord()
+ {
+ try
+ {
+ LocalUser user = null;
+
+ if (InputObject != null)
+ {
+ if (CheckShouldProcess(InputObject.ToString()))
+ user = InputObject;
+ }
+ else if (Name != null)
+ {
+ user = sam.GetLocalUser(Name);
+
+ if (!CheckShouldProcess(Name))
+ user = null;
+ }
+ else if (SID != null)
+ {
+ user = sam.GetLocalUser(SID);
+
+ if (!CheckShouldProcess(SID.ToString()))
+ user = null;
+ }
+
+ if (user == null)
+ return;
+
+ // We start with what already exists
+ var delta = user.Clone();
+ bool? passwordNeverExpires = null;
+
+ foreach (var paramName in parameterNames)
+ {
+ if (this.HasParameter(paramName))
+ {
+ switch (paramName)
+ {
+ case "AccountExpires":
+ delta.AccountExpires = this.AccountExpires;
+ break;
+
+ case "Description":
+ delta.Description = this.Description;
+ break;
+
+ case "FullName":
+ delta.FullName = this.FullName;
+ break;
+
+ case "UserMayChangePassword":
+ delta.UserMayChangePassword = this.UserMayChangePassword;
+ break;
+
+ case "PasswordNeverExpires":
+ passwordNeverExpires = this.PasswordNeverExpires;
+ break;
+ }
+ }
+ }
+
+ if (AccountNeverExpires.IsPresent)
+ delta.AccountExpires = null;
+
+ sam.UpdateLocalUser(user, delta, Password, passwordNeverExpires);
+ }
+ catch (Exception ex)
+ {
+ WriteError(ex.MakeErrorRecord());
+ }
+ }
+
+
+ ///
+ /// EndProcessing method.
+ ///
+ protected override void EndProcessing()
+ {
+ if (sam != null)
+ {
+ sam.Dispose();
+ sam = null;
+ }
+ }
+ #endregion Cmdlet Overrides
+
+ #region Private Methods
+ private LocalUser GetUser()
+ {
+ if (InputObject != null)
+ return InputObject;
+
+ return SID != null ? sam.GetLocalUser(SID)
+ : sam.GetLocalUser(Name);
+ }
+
+ private bool CheckShouldProcess(string target)
+ {
+ return ShouldProcess(target, Strings.ActionSetUser);
+ }
+ #endregion Private Methods
+ }//End Class
+
+}//End namespace
+
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs
new file mode 100644
index 000000000..4bf120de1
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Exceptions.cs
@@ -0,0 +1,695 @@
+using System;
+using System.Management.Automation;
+using System.Management.Automation.SecurityAccountsManager;
+using System.Runtime.Serialization;
+using Microsoft.PowerShell.LocalAccounts;
+
+#if CORECLR
+using Microsoft.PowerShell.CoreClr.Stubs;
+#endif
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Base class for cmdlet-specific exceptions.
+ ///
+ public class LocalAccountsException : Exception
+ {
+#region Public Properties
+ ///
+ /// Gets the
+ /// value for this exception.
+ ///
+ public ErrorCategory ErrorCategory
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the target object for this exception. This is used as
+ /// the TargetObject member of a PowerShell
+ /// object.
+ ///
+ public object Target
+ {
+ get;
+ private set;
+ }
+
+ ///
+ /// Gets the error name. This is used as the ErrorId parameter when
+ /// constructing a PowerShell
+ /// oject.
+ ///
+ public string ErrorName
+ {
+ get
+ {
+ string exname = "Exception";
+ var exlen = exname.Length;
+ var name = this.GetType().Name;
+
+ if (name.EndsWith(exname, StringComparison.OrdinalIgnoreCase) && name.Length > exlen)
+ name = name.Substring(0, name.Length - exlen);
+ return name;
+ }
+ }
+#endregion Public Properties
+
+ internal LocalAccountsException(string message, object target, ErrorCategory errorCategory)
+ : base(message)
+ {
+ ErrorCategory = errorCategory;
+ Target = target;
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public LocalAccountsException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public LocalAccountsException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public LocalAccountsException(String message, Exception ex) : base(message, ex) { }
+
+#if CORECLR
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ // This is not supported for CoreCLR
+ protected LocalAccountsException(SerializationInfo info, StreamingContext ctx) : base() { }
+#else
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected LocalAccountsException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+#endif
+ }
+
+ ///
+ /// Exception indicating an error occurred during one of the internal
+ /// operations such as opening or closing a handle.
+ ///
+ public class InternalException : LocalAccountsException
+ {
+#region Public Properties
+ ///
+ /// Gets the NTSTATUS code for this exception.
+ ///
+ public UInt32 StatusCode
+ {
+ get;
+ private set;
+ }
+#endregion Public Properties
+
+ internal InternalException(UInt32 ntStatus,
+ string message,
+ object target,
+ ErrorCategory errorCategory = ErrorCategory.NotSpecified)
+ : base(message, target, errorCategory)
+ {
+ StatusCode = ntStatus;
+ }
+
+ internal InternalException(UInt32 ntStatus,
+ object target,
+ ErrorCategory errorCategory = ErrorCategory.NotSpecified)
+ : this(ntStatus,
+ StringUtil.Format(Strings.UnspecifiedErrorNtStatus, ntStatus),
+ target,
+ errorCategory)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public InternalException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public InternalException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public InternalException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected InternalException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating an error occurred when a native function
+ /// is called that returns a Win32 error code as opposed to an
+ /// NT Status code.
+ ///
+ public class Win32InternalException : LocalAccountsException
+ {
+#region Public Properties
+ ///
+ /// The Win32 error code for this exception.
+ ///
+ public int NativeErrorCode
+ {
+ get;
+ private set;
+ }
+#endregion Public Properties
+
+ internal Win32InternalException(int errorCode,
+ string message,
+ object target,
+ ErrorCategory errorCategory = ErrorCategory.NotSpecified)
+ : base(message, target, errorCategory)
+ {
+ NativeErrorCode = errorCode;
+ }
+
+ internal Win32InternalException(int errorCode,
+ object target,
+ ErrorCategory errorCategory = ErrorCategory.NotSpecified)
+ : this(errorCode,
+ StringUtil.Format(Strings.UnspecifiedErrorWin32Error, errorCode),
+ target,
+ errorCategory)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public Win32InternalException() : base() {}
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public Win32InternalException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public Win32InternalException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected Win32InternalException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating an invalid password.
+ ///
+ public class InvalidPasswordException : LocalAccountsException
+ {
+ ///
+ /// Generates with a default invalid password message.
+ ///
+ public InvalidPasswordException()
+ : base(Strings.InvalidPassword, null, ErrorCategory.InvalidArgument)
+ {
+ }
+
+ ///
+ /// Generates the exception with the specified message.
+ ///
+ ///
+ public InvalidPasswordException(string message)
+ : base(message, null, ErrorCategory.InvalidArgument)
+ {
+ }
+
+ ///
+ /// Creates a message from the specified error code.
+ ///
+ ///
+ public InvalidPasswordException(uint errorCode)
+ : base(StringUtil.GetSystemMessage(errorCode), null, ErrorCategory.InvalidArgument)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public InvalidPasswordException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected InvalidPasswordException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception thrown when invalid parameter pairing is detected.
+ ///
+ public class InvalidParametersException : LocalAccountsException
+ {
+ internal InvalidParametersException(string message)
+ : base(message, null, ErrorCategory.InvalidArgument)
+ {
+ }
+
+ internal InvalidParametersException(string parameterA, string parameterB)
+ : this(StringUtil.Format(Strings.InvalidParameterPair, parameterA, parameterB))
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public InvalidParametersException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public InvalidParametersException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected InvalidParametersException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating permission denied.
+ ///
+ public class AccessDeniedException : LocalAccountsException
+ {
+ internal AccessDeniedException(object target)
+ : base(Strings.AccessDenied, target, ErrorCategory.PermissionDenied)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public AccessDeniedException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public AccessDeniedException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public AccessDeniedException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected AccessDeniedException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that the name of a user or group is invalid.
+ ///
+ public class InvalidNameException : LocalAccountsException
+ {
+ internal InvalidNameException(string name, object target)
+ : base(StringUtil.Format(Strings.InvalidName, name), target, ErrorCategory.InvalidArgument)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public InvalidNameException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public InvalidNameException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public InvalidNameException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected InvalidNameException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that the specified name is already in use.
+ ///
+ public class NameInUseException : LocalAccountsException
+ {
+ internal NameInUseException(string name, object target)
+ : base(StringUtil.Format(Strings.NameInUse, name), target, ErrorCategory.InvalidArgument)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public NameInUseException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public NameInUseException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public NameInUseException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected NameInUseException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that an entity of some kind was not found.
+ /// Also serves as a base class for more specific object-not-found errors.
+ ///
+ public class NotFoundException : LocalAccountsException
+ {
+ internal NotFoundException(string message, object target)
+ : base(message, target, ErrorCategory.ObjectNotFound)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public NotFoundException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public NotFoundException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public NotFoundException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected NotFoundException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that a principal was not Found
+ ///
+ public class PrincipalNotFoundException : NotFoundException
+ {
+ internal PrincipalNotFoundException(string principal, object target)
+ : base(StringUtil.Format(Strings.PrincipalNotFound, principal), target)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public PrincipalNotFoundException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public PrincipalNotFoundException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public PrincipalNotFoundException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected PrincipalNotFoundException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that a group was not found.
+ ///
+ public class GroupNotFoundException : NotFoundException
+ {
+ internal GroupNotFoundException(string group, object target)
+ : base(StringUtil.Format(Strings.GroupNotFound, group), target)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public GroupNotFoundException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public GroupNotFoundException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public GroupNotFoundException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected GroupNotFoundException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that a user was not found.
+ ///
+ public class UserNotFoundException : NotFoundException
+ {
+ internal UserNotFoundException(string user, object target)
+ : base(StringUtil.Format(Strings.UserNotFound, user), target)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public UserNotFoundException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public UserNotFoundException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public UserNotFoundException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected UserNotFoundException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that a group member was not found.
+ ///
+ public class MemberNotFoundException : NotFoundException
+ {
+ internal MemberNotFoundException(string member, string group)
+ : base(StringUtil.Format(Strings.MemberNotFound, member, group), member)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public MemberNotFoundException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public MemberNotFoundException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public MemberNotFoundException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected MemberNotFoundException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that an entity of some kind already exists.
+ /// Also serves as a base class for more specific object-exists errors.
+ ///
+ public class ObjectExistsException : LocalAccountsException
+ {
+ internal ObjectExistsException(string message, object target)
+ : base(message, target, ErrorCategory.ResourceExists)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public ObjectExistsException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public ObjectExistsException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public ObjectExistsException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected ObjectExistsException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that a group already exists.
+ ///
+ public class GroupExistsException : ObjectExistsException
+ {
+ internal GroupExistsException(string group, object target)
+ : base(StringUtil.Format(Strings.GroupExists, group), target)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public GroupExistsException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public GroupExistsException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public GroupExistsException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected GroupExistsException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that a group already exists.
+ ///
+ public class UserExistsException : ObjectExistsException
+ {
+ internal UserExistsException(string user, object target)
+ : base(StringUtil.Format(Strings.UserExists, user), target)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public UserExistsException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public UserExistsException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public UserExistsException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected UserExistsException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+
+ ///
+ /// Exception indicating that an object already exists as a group member.
+ ///
+ public class MemberExistsException : ObjectExistsException
+ {
+ internal MemberExistsException(string member, string group, object target)
+ : base(StringUtil.Format(Strings.MemberExists, member, group), target)
+ {
+ }
+
+ ///
+ /// Compliance Constructor
+ ///
+ public MemberExistsException() : base() { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ public MemberExistsException(String message) : base(message) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ public MemberExistsException(String message, Exception ex) : base(message, ex) { }
+ ///
+ /// Compliance Constructor
+ ///
+ ///
+ ///
+ protected MemberExistsException(SerializationInfo info, StreamingContext ctx) : base(info, ctx) { }
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs
new file mode 100644
index 000000000..68a378219
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Extensions.cs
@@ -0,0 +1,217 @@
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Security.Principal;
+using System.Text.RegularExpressions;
+
+using Microsoft.PowerShell.Commands;
+using Microsoft.PowerShell.LocalAccounts;
+
+namespace System.Management.Automation.SecurityAccountsManager.Extensions
+{
+ ///
+ /// Provides extension methods for the Cmdlet class
+ ///
+ internal static class CmdletExtensions
+ {
+ ///
+ /// Attempt to create a SID from a string.
+ ///
+ /// The cmdlet being extended with this method.
+ /// The string to be converted to a SID.
+ ///
+ /// A boolean indicating whether SID constants, such as "BA", are considered.
+ ///
+ ///
+ /// A object if the conversion was successful,
+ /// null otherwise.
+ ///
+ internal static SecurityIdentifier TrySid(this Cmdlet cmdlet,
+ string s,
+ bool allowSidConstants = false)
+ {
+ if (!allowSidConstants)
+ if (!(s.Length > 2 && s.StartsWith("S-") && Char.IsDigit(s[2])))
+ return null;
+
+ SecurityIdentifier sid = null;
+
+ try
+ {
+ sid = new SecurityIdentifier(s);
+ }
+ catch (ArgumentException)
+ {
+ // do nothing here, just fall through to the return
+ }
+
+ return sid;
+ }
+ }
+
+ ///
+ /// Provides extension methods for the PSCmdlet class
+ ///
+ internal static class PSExtensions
+ {
+ ///
+ /// Determine if a given parameter was provided to the cmdlet
+ ///
+ ///
+ /// The object to check.
+ ///
+ ///
+ /// A string containing the name of the parameter. This should be in the
+ /// same letter-casing as the defined parameter.
+ ///
+ ///
+ /// True if the specified parameter was given on the cmdlet invocation,
+ /// false otherwise.
+ ///
+ internal static bool HasParameter(this PSCmdlet cmdlet, string parameterName)
+ {
+ var invocation = cmdlet.MyInvocation;
+ if (invocation != null)
+ {
+ var parameters = invocation.BoundParameters;
+
+ if (parameters != null)
+ {
+ // PowerShell sets the parameter names in the BoundParameters dictionary
+ // to their "proper" casing, so we don't have to do a case-insensitive search.
+ if (parameters.ContainsKey(parameterName))
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// Provides extension methods for the SecurityIdentifier class.
+ ///
+ internal static class SidExtensions
+ {
+ ///
+ /// Get the Relative ID (RID) from a object.
+ ///
+ /// The SecurityIdentifier containing the desired Relative ID.
+ ///
+ /// A UInt32 value containing the Relative ID in the SecurityIdentifier.
+ ///
+ internal static UInt32 GetRid(this SecurityIdentifier sid)
+ {
+ byte[] sidBinary = new byte[sid.BinaryLength];
+ sid.GetBinaryForm(sidBinary, 0);
+
+ return System.BitConverter.ToUInt32(sidBinary, sidBinary.Length-4);
+ }
+
+ ///
+ /// Gets the Identifier Authority portion of a
+ ///
+ /// The SecurityIdentifier containing the desired Authority.
+ ///
+ /// A long integer value containing the SecurityIdentifier's Identifier Authority value.
+ ///
+ ///
+ /// This method is used primarily for determining the Source of a Principal.
+ /// The Win32 API LsaLookupUserAccountType function does not (yet) properly
+ /// identify MicrosoftAccount principals.
+ ///
+ internal static long GetIdentifierAuthority(this SecurityIdentifier sid)
+ {
+ byte[] sidBinary = new byte[sid.BinaryLength];
+
+ sid.GetBinaryForm(sidBinary, 0);
+
+ // The Identifier Authority is six bytes wide,
+ // in big-endian format, starting at the third byte
+ long authority = (long) (((long)sidBinary[2]) << 40) +
+ (((long)sidBinary[3]) << 32) +
+ (((long)sidBinary[4]) << 24) +
+ (((long)sidBinary[5]) << 16) +
+ (((long)sidBinary[6]) << 8) +
+ (((long)sidBinary[7]) );
+
+ return authority;
+ }
+
+ internal static bool IsMsaAccount(this SecurityIdentifier sid)
+ {
+ return sid.GetIdentifierAuthority() == 11;
+ }
+ }
+
+ internal static class SecureStringExtensions
+ {
+ private static Regex pwRegex = new Regex(@"^[^\x00-\x1F""/\\\[\]:;\|=,+\*?<>@]{0,127}$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ internal static bool IsValidPassword(this SecureString str)
+ {
+ return str.Length == 0 || pwRegex.IsMatch(str.AsString());
+ }
+
+ internal static void ValidatePassword(this SecureString str)
+ {
+ if (!str.IsValidPassword())
+ throw new InvalidPasswordException();
+ }
+
+ ///
+ /// Extension method to extract clear text from a
+ /// object.
+ ///
+ ///
+ /// This SecureString object, containing encrypted text.
+ ///
+ ///
+ /// A string containing the SecureString object's original text.
+ ///
+ internal static string AsString(this SecureString str)
+ {
+#if CORECLR
+ IntPtr buffer = SecureStringMarshal.SecureStringToCoTaskMemUnicode(str);
+ string clear = Marshal.PtrToStringUni(buffer);
+ SecureStringMarshal.ZeroFreeCoTaskMemUnicode(buffer);
+#else
+ var bstr = Marshal.SecureStringToBSTR(str);
+ string clear = Marshal.PtrToStringAuto(bstr);
+ Marshal.ZeroFreeBSTR(bstr);
+#endif
+ return clear;
+ }
+ }
+
+ internal static class ExceptionExtensions
+ {
+ internal static ErrorRecord MakeErrorRecord(this Exception ex,
+ string errorId,
+ ErrorCategory errorCategory,
+ object target = null)
+ {
+ return new ErrorRecord(ex, errorId, errorCategory, target);
+ }
+
+ internal static ErrorRecord MakeErrorRecord(this Exception ex, object target = null)
+ {
+ // This part is somewhat less than beautiful, but it prevents
+ // having to have multiple exception handlers in every cmdlet command.
+ var exTemp = ex as LocalAccountsException;
+
+ if (exTemp != null)
+ return MakeErrorRecord(exTemp, target ?? exTemp.Target);
+
+ return new ErrorRecord(ex,
+ Strings.UnspecifiedError,
+ ErrorCategory.NotSpecified,
+ target);
+ }
+
+ internal static ErrorRecord MakeErrorRecord(this LocalAccountsException ex, object target = null)
+ {
+ return ex.MakeErrorRecord(ex.ErrorName, ex.ErrorCategory, target ?? ex.Target);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs
new file mode 100644
index 000000000..170493522
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalGroup.cs
@@ -0,0 +1,79 @@
+using System;
+
+using Microsoft.PowerShell.LocalAccounts;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Describes a Local Group.
+ /// Objects of this type are provided to and returned from group-related Cmdlets.
+ ///
+ public class LocalGroup : LocalPrincipal
+ {
+ #region Public Properties
+ ///
+ /// A short description of the Group.
+ ///
+ public String Description { get; set; }
+ #endregion Public Properties
+
+ #region Construction
+ ///
+ /// Initializes a new LocalGroup object.
+ ///
+ public LocalGroup()
+ {
+ ObjectClass = Strings.ObjectClassGroup;
+ }
+
+ ///
+ /// Initializes a new LocalUser object with the specified name.
+ ///
+ /// Name of the new LocalGroup.
+ public LocalGroup(string name)
+ : base(name)
+ {
+ ObjectClass = Strings.ObjectClassGroup;
+ }
+
+ ///
+ /// Construct a new LocalGroup object that is a copy of another
+ ///
+ ///
+ private LocalGroup(LocalGroup other)
+ : this(other.Name)
+ {
+ Description = other.Description;
+ }
+ #endregion Construction
+
+
+ #region Public Methods
+ ///
+ /// Provides a string representation of the LocalGroup object.
+ ///
+ ///
+ /// A string containing the Group Name.
+ ///
+ public override string ToString()
+ {
+ return Name ?? SID.ToString();
+ }
+
+ ///
+ /// Create a copy of a LocalGroup object.
+ ///
+ ///
+ /// A new LocalGroup object with the same property values as this one.
+ ///
+ public LocalGroup Clone()
+ {
+ if (null == this)
+ {
+ throw new NullReferenceException();
+ }
+ return new LocalGroup(this);
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs
new file mode 100644
index 000000000..94e5972d2
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalPrincipal.cs
@@ -0,0 +1,97 @@
+using System.Security.Principal;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Defines the source of a Principal
+ ///
+ public enum PrincipalSource
+ {
+ ///
+ /// The principal source is unknown or could not be determined
+ ///
+ Unknown = 0,
+
+ ///
+ /// The principal is sourced from the local Windows Security Accounts Manager.
+ ///
+ Local,
+
+ ///
+ /// The principal is sourced from an Active Directory domain.
+ ///
+ ActiveDirectory,
+
+ ///
+ /// The principal is sourced from Azure Active Directory.
+ ///
+ AzureAD,
+
+ ///
+ /// The principal is a Microsoft Account, such as
+ /// MicrosoftAccount\user@domain.com
+ ///
+ MicrosoftAccount
+ }
+
+ ///
+ /// Represents a Principal. Serves as a base class for Users and Groups.
+ ///
+ public class LocalPrincipal
+ {
+ #region Public Properties
+ ///
+ /// The account name of the Principal.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The Security Identifier that uniquely identifies the Principal/
+ ///
+ public SecurityIdentifier SID { get; set; }
+
+ ///
+ /// Indicates the account store from which the principal is sourced.
+ /// One of the PrincipalSource enumerations.
+ ///
+ public PrincipalSource? PrincipalSource { get; set; }
+
+ ///
+ /// The object class that represents this principal.
+ /// This can be User or Group.
+ ///
+ public string ObjectClass { get; set; }
+ #endregion Public Properties
+
+ #region Construction
+ ///
+ /// Initializes a new LocalPrincipal object.
+ ///
+ public LocalPrincipal()
+ {
+ }
+
+ ///
+ /// Initializes a new LocalPrincipal object with the specified name.
+ ///
+ /// Name of the new LocalPrincipal.
+ public LocalPrincipal(string name)
+ {
+ Name = name;
+ }
+ #endregion Construction
+
+ #region Public Methods
+ ///
+ /// Provides a string representation of the Principal.
+ ///
+ ///
+ /// A string, in SDDL form, representing the Principal.
+ ///
+ public override string ToString()
+ {
+ return Name ?? SID.ToString();
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs
new file mode 100644
index 000000000..fb27679fe
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/LocalUser.cs
@@ -0,0 +1,145 @@
+using System;
+
+using Microsoft.PowerShell.LocalAccounts;
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Describes a Local User.
+ /// Objects of this type are provided to and returned from user-related Cmdlets.
+ ///
+ public class LocalUser : LocalPrincipal
+ {
+ #region Public Properties
+ ///
+ /// The date and time at which this user account expires.
+ /// A value of null indicates that the account never expires.
+ ///
+ public DateTime? AccountExpires { get; set; }
+
+ ///
+ /// A short description of the User.
+ ///
+ public string Description { get; set; }
+
+ ///
+ /// Indicates whether the user account is enabled (true) or disabled (false).
+ ///
+ public bool Enabled { get; set; }
+
+ ///
+ /// The user's full name. Not the same as the User name.
+ ///
+ public string FullName { get; set; }
+
+ ///
+ /// The date and time at which this user account password is allowed
+ /// to be changed. The password cannot be changed before this time.
+ /// A value of null indicates that the password can be changed anytime.
+ ///
+ public DateTime? PasswordChangeableDate { get; set; }
+
+ ///
+ /// The date and time at which this user account password must be changed
+ /// to a new password. A value of null indicates that the password will
+ /// never expire.
+ ///
+ public DateTime? PasswordExpires { get; set; }
+
+ ///
+ /// Indicates whether the user is allowed to change the password (true)
+ /// or not (false).
+ ///
+ public bool UserMayChangePassword { get; set; }
+
+
+ ///
+ /// Indicates whether the user must have a password (true) or not (false).
+ ///
+ public bool PasswordRequired { get; set; }
+
+ ///
+ /// The date and time at which this user last changed the account password.
+ ///
+ public DateTime? PasswordLastSet { get; set; }
+
+ ///
+ /// The date and time at which the user last logged on to the machine.
+ ///
+ public DateTime? LastLogon { get; set; }
+ #endregion Public Properties
+
+
+ #region Construction
+ ///
+ /// Initializes a new LocalUser object.
+ ///
+ public LocalUser()
+ {
+ ObjectClass = Strings.ObjectClassUser;
+ }
+
+ ///
+ /// Initializes a new LocalUser object with the specified name.
+ ///
+ /// Name of the new LocalUser.
+ public LocalUser(string name)
+ : base(name)
+ {
+ ObjectClass = Strings.ObjectClassUser;
+ }
+
+ ///
+ /// Construct a new LocalUser object that is a copy of another.
+ ///
+ /// The LocalUser object to copy.
+ private LocalUser(LocalUser other)
+ : this(other.Name)
+ {
+ SID = other.SID;
+ PrincipalSource = other.PrincipalSource;
+ ObjectClass = other.ObjectClass;
+
+ AccountExpires = other.AccountExpires;
+ Description = other.Description;
+ Enabled = other.Enabled;
+ FullName = other.FullName;
+ PasswordChangeableDate = other.PasswordChangeableDate;
+ PasswordExpires = other.PasswordExpires;
+ UserMayChangePassword = other.UserMayChangePassword;
+
+ PasswordRequired = other.PasswordRequired;
+ PasswordLastSet = other.PasswordLastSet;
+ LastLogon = other.LastLogon;
+ }
+ #endregion Construction
+
+ #region Public Methods
+ ///
+ /// Provides a string representation of the LocalUser object.
+ ///
+ ///
+ /// A string containing the User Name.
+ ///
+ public override string ToString()
+ {
+ return Name ?? SID.ToString();
+ }
+
+ ///
+ /// Create a copy of a LocalUser object.
+ ///
+ ///
+ /// A new LocalUser object with the same property values as this one.
+ ///
+ public LocalUser Clone()
+ {
+ if (null == this)
+ {
+ throw new NullReferenceException();
+ }
+ return new LocalUser(this);
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs
new file mode 100644
index 000000000..a84837fe0
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Native.cs
@@ -0,0 +1,523 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.Management.Automation.SecurityAccountsManager.Native
+{
+ #region Enums
+ internal enum POLICY_INFORMATION_CLASS
+ {
+ PolicyAuditLogInformation = 1,
+ PolicyAuditEventsInformation,
+ PolicyPrimaryDomainInformation,
+ PolicyPdAccountInformation,
+ PolicyAccountDomainInformation,
+ PolicyLsaServerRoleInformation,
+ PolicyReplicaSourceInformation,
+ PolicyDefaultQuotaInformation,
+ PolicyModificationInformation,
+ PolicyAuditFullSetInformation,
+ PolicyAuditFullQueryInformation,
+ PolicyDnsDomainInformation
+ }
+
+ [Flags]
+ internal enum LSA_AccessPolicy : long
+ {
+ POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
+ POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
+ POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
+ POLICY_TRUST_ADMIN = 0x00000008L,
+ POLICY_CREATE_ACCOUNT = 0x00000010L,
+ POLICY_CREATE_SECRET = 0x00000020L,
+ POLICY_CREATE_PRIVILEGE = 0x00000040L,
+ POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
+ POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
+ POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
+ POLICY_SERVER_ADMIN = 0x00000400L,
+ POLICY_LOOKUP_NAMES = 0x00000800L,
+ POLICY_NOTIFICATION = 0x00001000L
+ }
+
+ internal enum SID_NAME_USE
+ {
+ SidTypeUser = 1,
+ SidTypeGroup,
+ SidTypeDomain,
+ SidTypeAlias,
+ SidTypeWellKnownGroup,
+ SidTypeDeletedAccount,
+ SidTypeInvalid,
+ SidTypeUnknown,
+ SidTypeComputer,
+ SidTypeLabel
+ }
+
+ internal enum LSA_USER_ACCOUNT_TYPE
+ {
+ UnknownUserAccountType = 0,
+ LocalUserAccountType,
+ PrimaryDomainUserAccountType,
+ ExternalDomainUserAccountType,
+ LocalConnectedUserAccountType, // Microsoft Account
+ AADUserAccountType,
+ InternetUserAccountType, // Generic internet User (eg. if the SID supplied is MSA's internet SID)
+ MSAUserAccountType // !!! NOT YET IN THE ENUM SPECIFIED IN THE C API !!!
+
+ }
+ #endregion Enums
+
+ #region Structures
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SECURITY_DESCRIPTOR
+ {
+ public byte Revision;
+ public byte Sbz1;
+ public UInt16 Control; // SECURITY_DESCRIPTOR_CONTROL
+ public IntPtr Owner;
+ public IntPtr Group;
+ public IntPtr Sacl;
+ public IntPtr Dacl;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ACL
+ {
+ public byte AclRevision;
+ public byte Sbz1;
+ public UInt16 AclSize;
+ public UInt16 AceCount;
+ public UInt16 Sbz2;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct USER_INFO_1
+ {
+ public string name;
+ public string password;
+ public int password_age;
+ public int priv;
+ public string home_dir;
+ public string comment;
+ public uint flags;
+ public string script_path;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_INFO_1008
+ {
+ public uint flags;
+ }
+
+ ///
+ /// The UNICODE_STRING structure is passed to a number of the SAM and LSA
+ /// API functions. This adds cleanup and managed-string conversion behaviors.
+ ///
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct UNICODE_STRING
+ {
+ public UInt16 Length;
+ public UInt16 MaximumLength;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ private string buffer;
+
+ public UNICODE_STRING(string s)
+ {
+ buffer = String.IsNullOrEmpty(s) ? String.Empty : s;
+ Length = (UInt16)(2 * buffer.Length);
+ MaximumLength = Length;
+ }
+
+ public override string ToString()
+ {
+ // UNICODE_STRING structures that were populated by unmanaged code
+ // often have buffers that point to junk if Length = 0, or that
+ // point to non-null-terminated strings, resulting in marshaled
+ // String objects that have more characters than they should.
+ return Length == 0 ? String.Empty
+ : buffer.Substring(0, Length / 2);
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct OBJECT_ATTRIBUTES : IDisposable
+ {
+ public int Length;
+ public IntPtr RootDirectory;
+ public uint Attributes;
+ public IntPtr SecurityDescriptor;
+ public IntPtr SecurityQualityOfService;
+
+ private IntPtr objectName;
+
+ public OBJECT_ATTRIBUTES(string name, uint attrs)
+ {
+ Length = 0;
+ RootDirectory = IntPtr.Zero;
+ objectName = IntPtr.Zero;
+ Attributes = attrs;
+ SecurityDescriptor = IntPtr.Zero;
+ SecurityQualityOfService = IntPtr.Zero;
+
+ Length = ClrFacade.SizeOf();
+ ObjectName = new UNICODE_STRING(name);
+ }
+
+ public UNICODE_STRING ObjectName
+ {
+ get
+ {
+ return ClrFacade.PtrToStructure(objectName);
+ }
+
+ set
+ {
+ bool fDeleteOld = objectName != IntPtr.Zero;
+
+ if (!fDeleteOld)
+ objectName = Marshal.AllocHGlobal(Marshal.SizeOf(value));
+
+ Marshal.StructureToPtr(value, objectName, fDeleteOld);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (objectName != IntPtr.Zero)
+ {
+ ClrFacade.DestroyStructure(objectName);
+ Marshal.FreeHGlobal(objectName);
+ objectName = IntPtr.Zero;
+ }
+ }
+ }
+
+
+// These structures are filled in by Marshalling, so fields will be initialized
+// invisibly to the C# compiler, and some fields will not be used in C# code.
+#pragma warning disable 0649, 0169
+ [StructLayout(LayoutKind.Explicit, Size = 8)]
+ struct LARGE_INTEGER
+ {
+ [FieldOffset(0)]
+ public Int64 QuadPart;
+ [FieldOffset(0)]
+ public UInt32 LowPart;
+ [FieldOffset(4)]
+ public Int32 HighPart;
+ }
+#pragma warning restore 0649, 0169
+ #endregion Structures
+
+ ///
+ /// Wraps calls to Marshal functions that differ between .Net 4.5 and CoreCLR. .Net 4.5.1 types are not allowed for PowerShell.
+ ///
+ internal class ClrFacade
+ {
+ ///
+ /// Private constructor to prevent auto-generation of a default constructor
+ ///
+ private ClrFacade()
+ {
+ }
+
+ ///
+ /// Facade for Marshal.SizeOf
+ ///
+ internal static int SizeOf()
+ {
+#if CORECLR
+ // Marshal.SizeOf(Type) is obsolete in CoreCLR
+ return Marshal.SizeOf();
+#else
+ return Marshal.SizeOf(typeof(T));
+#endif
+ }
+
+ ///
+ /// Facade for Marshal.DestroyStructure
+ ///
+ internal static void DestroyStructure(IntPtr ptr)
+ {
+#if CORECLR
+ // Marshal.DestroyStructure(IntPtr, Type) is obsolete in CoreCLR
+ Marshal.DestroyStructure(ptr);
+#else
+ Marshal.DestroyStructure(ptr, typeof(T));
+#endif
+ }
+
+ ///
+ /// Facade for Marshal.PtrToStructure
+ ///
+ internal static T PtrToStructure(IntPtr ptr)
+ {
+#if CORECLR
+ // Marshal.PtrToStructure(IntPtr, Type) is obsolete in CoreCLR
+ return Marshal.PtrToStructure(ptr);
+#else
+ return (T)Marshal.PtrToStructure(ptr, typeof(T));
+#endif
+ }
+
+ ///
+ /// Wraps Marshal.StructureToPtr to hide differences between the CLRs.
+ ///
+ internal static void StructureToPtr(
+ T structure,
+ IntPtr ptr,
+ bool deleteOld)
+ {
+#if CORECLR
+ Marshal.StructureToPtr( structure, ptr, deleteOld );
+#else
+ Marshal.StructureToPtr(structure, ptr, deleteOld);
+#endif
+ }
+ }
+
+ internal static class Win32
+ {
+ #region Constants
+ // The following are masks for the predefined standard access types
+ internal const UInt32 DELETE = 0x00010000;
+ internal const UInt32 READ_CONTROL = 0x00020000;
+ internal const UInt32 WRITE_DAC = 0x00040000;
+ internal const UInt32 WRITE_OWNER = 0x00080000;
+ internal const UInt32 SYNCHRONIZE = 0x00100000;
+
+ internal const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
+
+ internal const UInt32 STANDARD_RIGHTS_READ = READ_CONTROL;
+ internal const UInt32 STANDARD_RIGHTS_WRITE = READ_CONTROL;
+ internal const UInt32 STANDARD_RIGHTS_EXECUTE = READ_CONTROL;
+
+
+ internal const UInt32 STANDARD_RIGHTS_ALL = 0x001F0000;
+
+ internal const UInt32 SPECIFIC_RIGHTS_ALL = 0x0000FFFF;
+
+ internal const UInt32 ACCESS_SYSTEM_SECURITY = 0x01000000;
+
+ internal const UInt32 MAXIMUM_ALLOWED = 0x02000000;
+
+ internal const UInt32 GENERIC_READ = 0x80000000;
+ internal const UInt32 GENERIC_WRITE = 0x40000000;
+ internal const UInt32 GENERIC_EXECUTE = 0x20000000;
+ internal const UInt32 GENERIC_ALL = 0x10000000;
+
+
+ // These constants control the behavior of the FormatMessage Windows API function
+ internal const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100;
+ internal const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
+ internal const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
+ internal const uint FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
+ internal const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800;
+ internal const uint FORMAT_MESSAGE_FROM_STRING = 0x00000400;
+
+ #region Win32 Error Codes
+ //
+ // MessageText:
+ //
+ // The operation completed successfully.
+ //
+ internal const Int32 ERROR_SUCCESS = 0;
+ internal const Int32 NO_ERROR = ERROR_SUCCESS;
+
+ //
+ // MessageId: ERROR_ACCESS_DENIED
+ //
+ // MessageText:
+ //
+ // Access is denied.
+ //
+ internal const int ERROR_ACCESS_DENIED = 5;
+
+ //
+ // MessageId: ERROR_BAD_NETPATH
+ //
+ // MessageText:
+ //
+ // The network path was not found.
+ //
+ internal const int ERROR_BAD_NETPATH = 53;
+
+ //
+ // MessageId: ERROR_NETWORK_ACCESS_DENIED
+ //
+ // MessageText:
+ //
+ // Network access is denied.
+ //
+ internal const int ERROR_NETWORK_ACCESS_DENIED = 65;
+
+ //
+ // MessageId: ERROR_INVALID_PARAMETER
+ //
+ // MessageText:
+ //
+ // The parameter is incorrect.
+ //
+ internal const int ERROR_INVALID_PARAMETER = 87;
+
+ //
+ // MessageText:
+ //
+ // The file name is too long.
+ //
+ internal const Int32 ERROR_BUFFER_OVERFLOW = 111;
+
+ //
+ // MessageText:
+ //
+ // The data area passed to a system call is too small.
+ //
+ internal const Int32 ERROR_INSUFFICIENT_BUFFER = 122;
+
+ //
+ // MessageId: ERROR_INVALID_LEVEL
+ //
+ // MessageText:
+ //
+ // The system call level is not correct.
+ //
+ internal const int ERROR_INVALID_LEVEL = 124;
+
+ //
+ // MessageId: ERROR_INVALID_FLAGS
+ //
+ // MessageText:
+ //
+ // Invalid flags.
+ //
+ internal const Int32 ERROR_INVALID_FLAGS = 1004;
+
+ //
+ // MessageId: ERROR_ILL_FORMED_PASSWORD
+ //
+ // MessageText:
+ //
+ // Unable to update the password. The value provided for the new password contains values that are not allowed in passwords.
+ //
+ internal const UInt32 ERROR_ILL_FORMED_PASSWORD = 1324;
+
+ //
+ // MessageId: ERROR_PASSWORD_RESTRICTION
+ //
+ // MessageText:
+ //
+ // Unable to update the password. The value provided for the new password does not meet the length, complexity, or history requirements of the domain.
+ //
+ internal const UInt32 ERROR_PASSWORD_RESTRICTION = 1325;
+
+ //
+ // MessageText:
+ //
+ // No mapping between account names and security IDs was done.
+ //
+ internal const Int32 ERROR_NONE_MAPPED = 1332;
+
+ internal const int NERR_Success = 0;
+ // NERR_BASE is the base of error codes from network utilities,
+ // chosen to avoid conflict with system and redirector error codes.
+ // 2100 is a value that has been assigned to us by system.
+ internal const int NERR_BASE = 2100;
+
+ internal const int NERR_BadPassword = NERR_BASE + 103; // The password parameter is invalid.
+ internal const int NERR_UserNotFound = NERR_BASE + 121; // The user name could not be found.
+ internal const int NERR_NotPrimary = NERR_BASE + 126; // This operation is only allowed on the primary domain controller of the domain.
+ internal const int NERR_SpeGroupOp = NERR_BASE + 134; // This operation is not allowed on this special group.
+ internal const int NERR_PasswordTooShort = NERR_BASE + 145; // The password does not meet the password policy requirements. Check the minimum password length, password complexity and password history requirements.
+ internal const int NERR_InvalidComputer = NERR_BASE + 251; // This computer name is invalid.
+ internal const int NERR_LastAdmin = NERR_BASE + 352; // This operation is not allowed on the last administrative account.
+ #endregion Win32 Error Codes
+
+
+ #region SECURITY_DESCRIPTOR Control Flags
+ internal const UInt16 SE_DACL_PRESENT = 0x0004;
+ internal const UInt16 SE_SELF_RELATIVE = 0x8000;
+ #endregion SECURITY_DESCRIPTOR Control Flags
+
+ #region SECURITY_INFORMATION Values
+ internal const int DACL_SECURITY_INFORMATION = 0x00000004;
+ #endregion SECURITY_INFORMATION Values
+ #endregion Constants
+
+ #region Win32 Functions
+ [DllImport(PInvokeDllNames.LookupAccountSidDllName, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool LookupAccountSid(string systemName,
+ byte[] accountSid,
+ StringBuilder accountName,
+ ref Int32 nameLength,
+ StringBuilder domainName,
+ ref Int32 domainNameLength,
+ out SID_NAME_USE use);
+
+ [DllImport(PInvokeDllNames.LookupAccountNameDllName, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool LookupAccountName(string systemName,
+ string accountName,
+ [MarshalAs(UnmanagedType.LPArray)]
+ byte[] sid,
+ ref uint sidLength,
+ StringBuilder domainName,
+ ref uint domainNameLength,
+ out SID_NAME_USE peUse);
+
+ [DllImport(PInvokeDllNames.GetComputerNameDllName, CharSet=CharSet.Unicode, SetLastError=true)]
+ internal static extern bool GetComputerName(StringBuilder name, ref uint size);
+
+ [DllImport(PInvokeDllNames.GetSecurityDescriptorDaclDllName, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool GetSecurityDescriptorDacl(IntPtr pSecurityDescriptor,
+ [MarshalAs(UnmanagedType.Bool)]
+ out bool bDaclPresent,
+ out IntPtr pDacl,
+ [MarshalAs(UnmanagedType.Bool)]
+ out bool bDaclDefaulted);
+
+ [DllImport(PInvokeDllNames.SetSecurityDescriptorDaclDllName, SetLastError = true)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool SetSecurityDescriptorDacl(IntPtr pSecurityDescriptor,
+ [MarshalAs(UnmanagedType.Bool)]
+ bool bDaclPresent,
+ IntPtr pDacl,
+ [MarshalAs(UnmanagedType.Bool)]
+ bool bDaclDefaulted);
+
+ [DllImport(PInvokeDllNames.FormatMessageDllName, SetLastError = true)]
+ internal static extern uint FormatMessage(uint dwFlags,
+ IntPtr lpSource,
+ uint dwMessageId,
+ uint dwLanguageId,
+ [Out] StringBuilder lpBuffer,
+ uint nSize,
+ string[] Arguments);
+
+ [DllImport("ntdll.dll")]
+ internal static extern uint RtlNtStatusToDosError(uint ntStatus);
+ #endregion Win32 Functions
+
+ #region LSA Functions
+ [DllImport(PInvokeDllNames.LsaOpenPolicyDllName, CharSet = CharSet.Unicode)]
+ internal static extern UInt32 LsaOpenPolicy(ref UNICODE_STRING SystemName,
+ ref OBJECT_ATTRIBUTES ObjectAttributes,
+ uint DesiredAccess,
+ out IntPtr PolicyHandle);
+
+ [DllImport(PInvokeDllNames.LsaQueryInformationPolicyDllName, CharSet = CharSet.Unicode)]
+ internal static extern UInt32 LsaQueryInformationPolicy(IntPtr lsaHandle,
+ POLICY_INFORMATION_CLASS infoClass,
+ out IntPtr buffer);
+
+ [DllImport(PInvokeDllNames.LsaFreeMemoryDllName)]
+ internal static extern UInt32 LsaFreeMemory(IntPtr buffer);
+
+ [DllImport(PInvokeDllNames.LsaCloseDllName)]
+ internal static extern UInt32 LsaClose(IntPtr handle);
+
+ [DllImport("api-ms-win-security-lsalookup-l1-1-2.dll")]
+ internal static extern UInt32 LsaLookupUserAccountType([MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
+ out LSA_USER_ACCOUNT_TYPE accountType);
+ #endregion LSA Functions
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs
new file mode 100644
index 000000000..b22a543eb
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/NtStatus.cs
@@ -0,0 +1,516 @@
+
+namespace System.Management.Automation.SecurityAccountsManager.Native
+{
+ internal static class NtStatus
+ {
+ #region Constants
+ //
+ // These values are taken from ntstatus.h
+ //
+
+ //
+ // Severity codes
+ //
+ public const UInt32 STATUS_SEVERITY_WARNING = 0x2;
+ public const UInt32 STATUS_SEVERITY_SUCCESS = 0x0;
+ public const UInt32 STATUS_SEVERITY_INFORMATIONAL = 0x1;
+ public const UInt32 STATUS_SEVERITY_ERROR = 0x3;
+
+
+ public const UInt32 STATUS_SUCCESS = 0x00000000;
+ //
+ // MessageText:
+ //
+ // Returned by enumeration APIs to indicate more information is available to successive calls.
+ //
+ public const UInt32 STATUS_MORE_ENTRIES = 0x00000105;
+
+
+
+
+
+
+
+ /////////////////////////////////////////////////////////////////////////
+ //
+ // Standard Information values
+ //
+ /////////////////////////////////////////////////////////////////////////
+
+ //
+ // MessageText:
+ //
+ // {Object Exists}
+ // An attempt was made to create an object and the object name already existed.
+ //
+ public const UInt32 STATUS_OBJECT_NAME_EXISTS = 0x40000000;
+
+ //
+ // MessageText:
+ //
+ // {Password Too Complex}
+ // The Windows password is too complex to be converted to a LAN Manager password. The LAN Manager password returned is a NULL string.
+ //
+ public const UInt32 STATUS_NULL_LM_PASSWORD = 0x4000000D;
+
+ //
+ // MessageText:
+ //
+ // {Access Denied}
+ // A process has requested access to an object, but has not been granted those access rights.
+ //
+ public const UInt32 STATUS_ACCESS_DENIED = 0xC0000022;
+
+ //
+ // MessageText:
+ //
+ // The name provided is not a properly formed account name.
+ //
+ public const UInt32 STATUS_INVALID_ACCOUNT_NAME = 0xC0000062;
+
+ //
+ // MessageText:
+ //
+ // The specified account already exists.
+ //
+ public const UInt32 STATUS_USER_EXISTS = 0xC0000063;
+
+ //
+ // MessageText:
+ //
+ // The specified account does not exist.
+ //
+ public const UInt32 STATUS_NO_SUCH_USER = 0xC0000064; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // The specified group already exists.
+ //
+ public const UInt32 STATUS_GROUP_EXISTS = 0xC0000065;
+
+ //
+ // MessageText:
+ //
+ // The specified group does not exist.
+ //
+ public const UInt32 STATUS_NO_SUCH_GROUP = 0xC0000066;
+
+ //
+ // MessageText:
+ //
+ // The specified user account is already in the specified group account. Also used to indicate a group cannot be deleted because it contains a member.
+ //
+ public const UInt32 STATUS_MEMBER_IN_GROUP = 0xC0000067;
+
+ //
+ // MessageText:
+ //
+ // The specified user account is not a member of the specified group account.
+ //
+ public const UInt32 STATUS_MEMBER_NOT_IN_GROUP = 0xC0000068;
+
+ //
+ // MessageText:
+ //
+ // Indicates the requested operation would disable, delete or could prevent logon for an administration account.
+ // This is not allowed to prevent creating a situation in which the system cannot be administrated.
+ //
+ public const UInt32 STATUS_LAST_ADMIN = 0xC0000069;
+
+ //
+ // MessageText:
+ //
+ // When trying to update a password, this return status indicates that the value provided as the current password is not correct.
+ //
+ public const UInt32 STATUS_WRONG_PASSWORD = 0xC000006A; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // When trying to update a password, this return status indicates that the value provided for the new password contains values that are not allowed in passwords.
+ //
+ public const UInt32 STATUS_ILL_FORMED_PASSWORD = 0xC000006B;
+
+ //
+ // MessageText:
+ //
+ // When trying to update a password, this status indicates that some password update rule has been violated. For example, the password may not meet length criteria.
+ //
+ public const UInt32 STATUS_PASSWORD_RESTRICTION = 0xC000006C; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // The user account's password has expired.
+ //
+ public const UInt32 STATUS_PASSWORD_EXPIRED = 0xC0000071; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // The referenced account is currently disabled and may not be logged on to.
+ //
+ public const UInt32 STATUS_ACCOUNT_DISABLED = 0xC0000072; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // None of the information to be translated has been translated.
+ //
+ public const UInt32 STATUS_NONE_MAPPED = 0xC0000073;
+
+ //
+ // MessageText:
+ //
+ // Indicates the sub-authority value is invalid for the particular use.
+ //
+ public const UInt32 STATUS_INVALID_SUB_AUTHORITY = 0xC0000076;
+
+ //
+ // MessageText:
+ //
+ // Indicates the ACL structure is not valid.
+ //
+ public const UInt32 STATUS_INVALID_ACL = 0xC0000077;
+
+ //
+ // MessageText:
+ //
+ // Indicates the SID structure is not valid.
+ //
+ public const UInt32 STATUS_INVALID_SID = 0xC0000078;
+
+ //
+ // MessageText:
+ //
+ // Indicates the SECURITY_DESCRIPTOR structure is not valid.
+ //
+ public const UInt32 STATUS_INVALID_SECURITY_DESCR = 0xC0000079;
+
+ //
+ // Network specific errors.
+ //
+ //
+ //
+ // MessageText:
+ //
+ // The request is not supported.
+ //
+ public const UInt32 STATUS_NOT_SUPPORTED = 0xC00000BB;
+
+ //
+ // MessageText:
+ //
+ // This remote computer is not listening.
+ //
+ public const UInt32 STATUS_REMOTE_NOT_LISTENING = 0xC00000BC;
+
+ //
+ // MessageText:
+ //
+ // Network access is denied.
+ //
+ public const UInt32 STATUS_NETWORK_ACCESS_DENIED = 0xC00000CA;
+
+ //
+ // MessageText:
+ //
+ // Indicates an attempt was made to operate on the security of an object that does not have security associated with it.
+ //
+ public const UInt32 STATUS_NO_SECURITY_ON_OBJECT = 0xC00000D7;
+
+ //
+ // MessageText:
+ //
+ // An internal error occurred.
+ //
+ public const UInt32 STATUS_INTERNAL_ERROR = 0xC00000E5;
+
+ //
+ // MessageText:
+ //
+ // Indicates a security descriptor is not in the necessary format (absolute or self-relative).
+ //
+ public const UInt32 STATUS_BAD_DESCRIPTOR_FORMAT = 0xC00000E7;
+
+ //
+ // MessageText:
+ //
+ // A specified name string is too long for its intended use.
+ //
+ public const UInt32 STATUS_NAME_TOO_LONG = 0xC0000106;
+
+ //
+ // MessageText:
+ //
+ // Indicates a name specified as a remote computer name is syntactically invalid.
+ //
+ public const UInt32 STATUS_INVALID_COMPUTER_NAME = 0xC0000122;
+
+ //
+ // MessageText:
+ //
+ // Indicates an operation has been attempted on a built-in (special) SAM account which is incompatible with built-in accounts. For example, built-in accounts cannot be deleted.
+ //
+ public const UInt32 STATUS_SPECIAL_ACCOUNT = 0xC0000124;
+
+ //
+ // MessageText:
+ //
+ // The operation requested may not be performed on the specified group because it is a built-in special group.
+ //
+ public const UInt32 STATUS_SPECIAL_GROUP = 0xC0000125;
+
+ //
+ // MessageText:
+ //
+ // The operation requested may not be performed on the specified user because it is a built-in special user.
+ //
+ public const UInt32 STATUS_SPECIAL_USER = 0xC0000126;
+
+ //
+ // MessageText:
+ //
+ // Indicates a member cannot be removed from a group because the group is currently the member's primary group.
+ //
+ public const UInt32 STATUS_MEMBERS_PRIMARY_GROUP = 0xC0000127;
+
+ //
+ // MessageText:
+ //
+ // The specified local group does not exist.
+ //
+ public const UInt32 STATUS_NO_SUCH_ALIAS = 0xC0000151;
+
+ //
+ // MessageText:
+ //
+ // The specified account name is not a member of the group.
+ //
+ public const UInt32 STATUS_MEMBER_NOT_IN_ALIAS = 0xC0000152;
+
+ //
+ // MessageText:
+ //
+ // The specified account name is already a member of the group.
+ //
+ public const UInt32 STATUS_MEMBER_IN_ALIAS = 0xC0000153;
+
+ //
+ // MessageText:
+ //
+ // The specified local group already exists.
+ //
+ public const UInt32 STATUS_ALIAS_EXISTS = 0xC0000154;
+
+ //
+ // MessageText:
+ //
+ // A member could not be added to or removed from the local group because the member does not exist.
+ //
+ public const UInt32 STATUS_NO_SUCH_MEMBER = 0xC000017A;
+
+ //
+ // MessageText:
+ //
+ // A new member could not be added to a local group because the member has the wrong account type.
+ //
+ public const UInt32 STATUS_INVALID_MEMBER = 0xC000017B;
+
+ //
+ // MessageText:
+ //
+ // The user's account has expired.
+ //
+ public const UInt32 STATUS_ACCOUNT_EXPIRED = 0xC0000193; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // {Invalid ACE Condition}
+ // The specified access control entry (ACE) contains an invalid condition.
+ //
+ public const UInt32 STATUS_INVALID_ACE_CONDITION = 0xC00001A2;
+
+ //
+ // MessageText:
+ //
+ // The user's password must be changed before signing in.
+ //
+ public const UInt32 STATUS_PASSWORD_MUST_CHANGE = 0xC0000224; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // The object was not found.
+ //
+ public const UInt32 STATUS_NOT_FOUND = 0xC0000225;
+
+ //
+ // MessageText:
+ //
+ // Could not find a domain controller for this domain.
+ //
+ public const UInt32 STATUS_DOMAIN_CONTROLLER_NOT_FOUND = 0xC0000233;
+
+ //
+ // MessageText:
+ //
+ // The user account has been automatically locked because too many invalid logon attempts or password change attempts have been requested.
+ //
+ public const UInt32 STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234; // ntsubauth
+
+ //
+ // MessageText:
+ //
+ // The password provided is too short to meet the policy of your user account. Please choose a longer password.
+ //
+ public const UInt32 STATUS_PWD_TOO_SHORT = 0xC000025A;
+
+ //
+ // MessageText:
+ //
+ // The policy of your user account does not allow you to change passwords too frequently. This is done to prevent users from changing back to a familiar, but potentially discovered, password. If you feel your password has been compromised then please contact your administrator immediately to have a new one assigned.
+ //
+ public const UInt32 STATUS_PWD_TOO_RECENT = 0xC000025B;
+
+ //
+ // MessageText:
+ //
+ // You have attempted to change your password to one that you have used in the past. The policy of your user account does not allow this. Please select a password that you have not previously used.
+ //
+ public const UInt32 STATUS_PWD_HISTORY_CONFLICT = 0xC000025C;
+
+ //
+ // MessageText:
+ //
+ // The password provided is too long to meet the policy of your user account. Please choose a shorter password.
+ //
+ public const UInt32 STATUS_PWD_TOO_LONG = 0xC000027A;
+
+ //
+ // MessageText:
+ //
+ // Only an administrator can modify the membership list of an administrative group.
+ //
+ public const UInt32 STATUS_DS_SENSITIVE_GROUP_VIOLATION = 0xC00002CD;
+
+ //
+ // MessageText:
+ //
+ // The specified group type is invalid.
+ //
+ public const UInt32 STATUS_DS_INVALID_GROUP_TYPE = 0xC00002D4;
+
+ //
+ // MessageText:
+ //
+ // A local group cannot have another cross domain local group as a member.
+ //
+ public const UInt32 STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER = 0xC00002DB;
+
+ //
+ // MessageText:
+ //
+ // Cannot change to security disabled group because of having primary members in this group.
+ //
+ public const UInt32 STATUS_DS_HAVE_PRIMARY_MEMBERS = 0xC00002DC;
+
+ //
+ // MessageText:
+ //
+ // EAS policy requires that the user change their password before this operation can be performed.
+ //
+ public const UInt32 STATUS_PASSWORD_CHANGE_REQUIRED = 0xC000030C;
+
+ #endregion Constants
+
+ #region Public Methods
+ ///
+ /// Determine if an NTSTATUS value indicates Success
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// True if the NTSTATUS value indicates success, false otherwise.
+ ///
+ public static bool IsSuccess(UInt32 ntstatus)
+ {
+ return Severity(ntstatus) == STATUS_SEVERITY_SUCCESS;
+ }
+
+ ///
+ /// Determine if an NTSTATUS value indicates an Error
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// True if the NTSTATUS value indicates an error, false otherwise.
+ ///
+ public static bool IsError(UInt32 ntstatus)
+ {
+ return Severity(ntstatus) == STATUS_SEVERITY_ERROR;
+ }
+
+ ///
+ /// Determine if an NTSTATUS value indicates a Warning
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// True if the NTSTATUS value indicates a warning, false otherwise.
+ ///
+ public static bool IsWarning(UInt32 ntstatus)
+ {
+ return Severity(ntstatus) == STATUS_SEVERITY_WARNING;
+ }
+
+ ///
+ /// Determine if an NTSTATUS value indicates that the value is Informational.
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// True if the NTSTATUS value indicates that it is informational, false otherwise.
+ ///
+ public static bool IsInformational(UInt32 ntstatus)
+ {
+ return Severity(ntstatus) == STATUS_SEVERITY_INFORMATIONAL;
+ }
+
+
+ ///
+ /// Return the Severity part of an NTSTATUS value
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// One of the STATUS_SEVERITY_* values
+ ///
+ public static uint Severity(UInt32 ntstatus)
+ {
+ return ntstatus >> 30;
+ }
+
+ ///
+ /// Return the Facility part of an NSTATUS value
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// The value of the Facility portion of an NTSTATUS value.
+ ///
+ public static uint Facility(UInt32 ntstatus)
+ {
+ return (ntstatus >> 16) & 0x0FFF;
+ }
+
+ ///
+ /// Return the Code part of an NTSTATUS value
+ ///
+ /// The NTSTATUS value returned from native functions.
+ ///
+ /// The value of the Code portion of an NTSTATUS value.
+ ///
+ public static uint Code(UInt32 ntstatus)
+ {
+ return ntstatus & 0xFFFF;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs
new file mode 100644
index 000000000..b97b03e31
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/PInvokeDllNames.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (C) Microsoft. All rights reserved.
+//
+
+namespace System.Management.Automation
+{
+ ///
+ /// PinvokeDllNames contains the DLL names to be use for PInvoke in FullCLR/CoreCLR powershell.
+ ///
+ /// * When adding a new DLL name here, make sure that you add both the FullCLR and CoreCLR version
+ /// of it. Add the comment '/*COUNT*/' with the new DLL name, and make sure the 'COUNT' is the
+ /// same for both FullCLR and CoreCLR DLL names.
+ ///
+ internal static class PInvokeDllNames
+ {
+#if CORECLR
+ internal const string GetLastErrorDllName = "api-ms-win-core-errorhandling-l1-1-0.dll"; /* 1*/
+ internal const string LookupAccountSidDllName = "api-ms-win-security-lsalookup-l2-1-1.dll"; /* 2*/
+ internal const string IsValidSidDllName = "api-ms-win-security-base-l1-2-0.dll"; /* 3*/
+ internal const string GetLengthSidDllName = "api-ms-win-security-base-l1-2-0.dll"; /* 4*/
+ internal const string LsaFreeMemoryDllName = "api-ms-win-security-lsapolicy-l1-1-0.dll"; /* 5*/
+ internal const string LsaOpenPolicyDllName = "api-ms-win-security-lsapolicy-l1-1-0.dll"; /* 6*/
+ internal const string LsaQueryInformationPolicyDllName = "api-ms-win-security-lsapolicy-l1-1-0.dll"; /* 7*/
+ internal const string LsaCloseDllName = "api-ms-win-security-lsapolicy-l1-1-0.dll"; /* 8*/
+ internal const string LookupAccountNameDllName = "api-ms-win-security-lsalookup-l2-1-1.dll"; /* 9*/
+ internal const string GetComputerNameDllName = "api-ms-win-downlevel-kernel32-l2-1-0.dll"; /*10*/
+ internal const string GetSecurityDescriptorDaclDllName = "api-ms-win-security-base-l1-2-0"; /*11*/
+ internal const string SetSecurityDescriptorDaclDllName = "api-ms-win-security-base-l1-2-0"; /*12*/
+ internal const string FormatMessageDllName = "api-ms-win-core-localization-l1-2-1"; /*13*/
+ internal const string GetVersionExDllName = "api-ms-win-core-sysinfo-l1-2-1.dll"; /*14*/
+#else
+ internal const string GetLastErrorDllName = "kernel32.dll"; /* 1*/
+ internal const string LookupAccountSidDllName = "advapi32.dll"; /* 2*/
+ internal const string IsValidSidDllName = "advapi32.dll"; /* 3*/
+ internal const string GetLengthSidDllName = "advapi32.dll"; /* 4*/
+ internal const string LsaFreeMemoryDllName = "advapi32.dll"; /* 5*/
+ internal const string LsaOpenPolicyDllName = "advapi32.dll"; /* 6*/
+ internal const string LsaQueryInformationPolicyDllName = "advapi32.dll"; /* 7*/
+ internal const string LsaCloseDllName = "advapi32.dll"; /* 8*/
+ internal const string LookupAccountNameDllName = "advapi32.dll"; /* 9*/
+ internal const string GetComputerNameDllName = "kernel32.dll"; /*10*/
+ internal const string GetSecurityDescriptorDaclDllName = "advapi32.dll"; /*11*/
+ internal const string SetSecurityDescriptorDaclDllName = "advapi32.dll"; /*12*/
+ internal const string FormatMessageDllName = "kernel32.dll"; /*13*/
+ internal const string GetVersionExDllName = "kernel32.dll"; /*14*/
+#endif
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Properties/AssemblyInfo.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..5a23f694e
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+using System.Reflection;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("LocalAccounts")]
+[assembly: AssemblyDescription("PowerShell cmdlet for local accounts.")]
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs
new file mode 100644
index 000000000..21992d791
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Sam.cs
@@ -0,0 +1,3300 @@
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using System.ComponentModel;
+
+using Microsoft.PowerShell.Commands;
+using System.Management.Automation.SecurityAccountsManager.Extensions;
+using System.Management.Automation.SecurityAccountsManager.Native;
+using System.Management.Automation.SecurityAccountsManager.Native.NtSam;
+using System.Text;
+
+using Microsoft.PowerShell.LocalAccounts;
+
+namespace System.Management.Automation.SecurityAccountsManager
+{
+ ///
+ /// Defines enumeration constants for enabling and disabling something.
+ ///
+ internal enum Enabling
+ {
+ Disable = 0,
+ Enable
+ }
+
+ ///
+ /// Managed version of the SAM_RID_ENUMERATION native structure,
+ /// to be returned from the EnumerateLocalUsers method of Sam.
+ /// Contains the original structure's members along with additional
+ /// members of use.
+ ///
+ internal class SamRidEnumeration
+ {
+#region Original struct members
+ public String Name;
+ public UInt32 RelativeId;
+#endregion Original struct members
+
+#region Additional members
+ public IntPtr domainHandle; // The domain handle used to acquire the data.
+#endregion Additional members
+ }
+
+
+ ///
+ /// Provides methods for manipulating local Users and Groups.
+ ///
+ internal class Sam : IDisposable
+ {
+#region Enums
+ [Flags]
+ private enum GroupProperties
+ {
+ Name = 0x0001, // NOT changeable through Set-LocalGroup
+ Description = 0x0002,
+
+ AllSetable = Description,
+ AllReadable = AllSetable | Name
+ }
+
+ ///
+ /// Defines a set of flags, each coresponding to a member of LocalUser,
+ /// which indicate fields to be updated.
+ ///
+ ///
+ /// Although password can be set through Create-LocalUser and Set-LocalUser,
+ /// it is not a member of LocalUser so does not appear in this enumeration.
+ ///
+ [Flags]
+ private enum UserProperties
+ {
+ None = 0x0000, // not actually a LocalUser member
+ Name = 0x0001, // NOT changeable through Set-LocalUser
+ AccountExpires = 0x0002,
+ Description = 0x0004,
+ Enabled = 0x0008, // NOT changeable through Set-LocalUser
+ FullName = 0x0010,
+ PasswordChangeableDate = 0x0020,
+ PasswordExpires = 0x0040,
+ PasswordNeverExpires = 0x0080,
+ UserMayChangePassword = 0x0100,
+ PasswordRequired = 0x0200,
+ PasswordLastSet = 0x0400, // CANNOT be set by cmdlet
+ LastLogon = 0x0800, // CANNOT be set by cmdlet
+
+ // All properties that can be set through Set-LocalUser
+ AllSetable = AccountExpires
+ | Description
+ | FullName
+ | PasswordChangeableDate
+ | PasswordExpires
+ | PasswordNeverExpires
+ | UserMayChangePassword
+ | PasswordRequired,
+
+ // Properties that can be set by Create-LocalUser
+ AllCreateable = AllSetable | Name | Enabled,
+
+ // Properties that can be read by e.g., Get-LocalUser
+ AllReadable = AllCreateable | PasswordLastSet | LastLogon
+ }
+
+ private enum PasswordExpiredState
+ {
+ Unchanged = -1,
+ NotExpired = 0,
+ Expired = 1
+ }
+
+ [Flags]
+ internal enum ObjectAccess : uint
+ {
+ AliasRead = Win32.STANDARD_RIGHTS_READ
+ | ALIAS_LIST_MEMBERS,
+ ALiasWrite = Win32.STANDARD_RIGHTS_WRITE
+ | ALIAS_WRITE_ACCOUNT
+ | ALIAS_ADD_MEMBER
+ | ALIAS_REMOVE_MEMBER,
+
+ UserAllAccess = Win32.STANDARD_RIGHTS_REQUIRED
+ | USER_READ_PREFERENCES
+ | USER_READ_LOGON
+ | USER_LIST_GROUPS
+ | USER_READ_GROUP_INFORMATION
+ | USER_WRITE_PREFERENCES
+ | USER_CHANGE_PASSWORD
+ | USER_FORCE_PASSWORD_CHANGE
+ | USER_READ_GENERAL
+ | USER_READ_ACCOUNT
+ | USER_WRITE_ACCOUNT
+ | USER_WRITE_GROUP_INFORMATION,
+ UserRead = Win32.STANDARD_RIGHTS_READ
+ | USER_READ_GENERAL // not in original USER_READ
+ | USER_READ_PREFERENCES
+ | USER_READ_LOGON
+ | USER_READ_ACCOUNT
+ | USER_LIST_GROUPS
+ | USER_READ_GROUP_INFORMATION,
+ UserWrite = Win32.STANDARD_RIGHTS_WRITE
+ | USER_WRITE_PREFERENCES
+ | USER_CHANGE_PASSWORD
+ }
+
+ [Flags]
+ internal enum DomainAccess : uint
+ {
+ AllAccess = Win32.STANDARD_RIGHTS_REQUIRED
+ | DOMAIN_READ_OTHER_PARAMETERS
+ | DOMAIN_WRITE_OTHER_PARAMETERS
+ | DOMAIN_WRITE_PASSWORD_PARAMS
+ | DOMAIN_CREATE_USER
+ | DOMAIN_CREATE_GROUP
+ | DOMAIN_CREATE_ALIAS
+ | DOMAIN_GET_ALIAS_MEMBERSHIP
+ | DOMAIN_LIST_ACCOUNTS
+ | DOMAIN_READ_PASSWORD_PARAMETERS
+ | DOMAIN_LOOKUP
+ | DOMAIN_ADMINISTER_SERVER,
+
+ Read = Win32.STANDARD_RIGHTS_READ
+ | DOMAIN_LIST_ACCOUNTS
+ | DOMAIN_GET_ALIAS_MEMBERSHIP
+ | DOMAIN_READ_OTHER_PARAMETERS,
+
+ Write = Win32.STANDARD_RIGHTS_WRITE
+ | DOMAIN_WRITE_OTHER_PARAMETERS
+ | DOMAIN_WRITE_PASSWORD_PARAMS
+ | DOMAIN_CREATE_USER
+ | DOMAIN_CREATE_GROUP
+ | DOMAIN_CREATE_ALIAS
+ | DOMAIN_ADMINISTER_SERVER,
+
+ Max = Win32.MAXIMUM_ALLOWED
+ }
+
+
+ ///
+ /// The operation under way. Used in the class.
+ ///
+ private enum ContextOperation
+ {
+ New = 1,
+ Enable,
+ Disable,
+ Get,
+ Remove,
+ Rename,
+ Set,
+ AddMember,
+ GetMember,
+ RemoveMember
+ }
+
+ ///
+ /// The type of object currently operating with.
+ /// used in the class.
+ ///
+ private enum ContextObjectType
+ {
+ User = 1,
+ Group
+ }
+#endregion Enums
+
+#region Internal Classes
+ ///
+ /// Holds information about the underway operation.
+ ///
+ ///
+ /// Used primarily by the private ThrowOnFailure method when building
+ /// Exception objects to throw.
+ ///
+ private class Context
+ {
+ public ContextOperation operation;
+ public ContextObjectType type;
+ public Object target;
+ public string objectId;
+ public string memberId;
+
+ ///
+ /// Initialize a new Context object.
+ ///
+ ///
+ /// One of the enumerations indicating
+ /// the type of operation under way.
+ ///
+ ///
+ /// One of the enumerations indicating
+ /// the type of object (user or group) being used.
+ ///
+ ///
+ /// A string containing the name of the object. This may be either a
+ /// user/group name or a string representation of a SID.
+ ///
+ ///
+ /// The target being operated on.
+ ///
+ ///
+ /// A string containing the name of the member being added or removed
+ /// from a group. Used only in such cases.
+ ///
+ public Context(ContextOperation operation,
+ ContextObjectType objectType,
+ string objectIdentifier,
+ object target,
+ string memberIdentifier = null)
+ {
+ this.operation = operation;
+ this.type = objectType;
+ this.objectId = objectIdentifier;
+ this.target = target;
+ this.memberId = memberIdentifier;
+ }
+
+ ///
+ /// Default constructor
+ ///
+ public Context()
+ {
+ }
+ ///
+ /// Gets a string containing the type of operation under way.
+ ///
+ public string OperationName
+ {
+ get { return operation.ToString(); }
+ }
+
+ ///
+ /// Gets a string containing the type of object ("User" or "Group")
+ /// being used.
+ ///
+ public string TypeNamne
+ {
+ get { return type.ToString(); }
+ }
+
+ ///
+ /// Gets a string containing the name of the object being used.
+ ///
+ public string ObjectName
+ {
+ get { return objectId; }
+ }
+
+ ///
+ /// Gets a string containing the name of the member being added to
+ /// or removed from a group. Returns null if the operation does not
+ /// involve group members.
+ ///
+ public string MemberName
+ {
+ get { return memberId; }
+ }
+ }
+
+ ///
+ /// Contains basic information about an Account.
+ ///
+ ///
+ /// AccountInfo is the return type from the private
+ /// LookupAccountInfo method.
+ ///
+ private class AccountInfo
+ {
+ public string AccountName;
+ public string DomainName;
+ public SecurityIdentifier Sid;
+ public Native.SID_NAME_USE Use;
+
+ public override string ToString()
+ {
+ if (!string.IsNullOrEmpty(DomainName))
+ return DomainName + '\\' + AccountName;
+ else
+ return AccountName;
+ }
+ }
+#endregion Internal Classes
+
+#region Constants
+ //
+ // Access rights
+ //
+ private const UInt32 ALIAS_ADD_MEMBER = 0x0001;
+ private const UInt32 ALIAS_REMOVE_MEMBER = 0x0002;
+ private const UInt32 ALIAS_LIST_MEMBERS = 0x0004;
+ private const UInt32 ALIAS_READ_INFORMATION = 0x0008;
+ private const UInt32 ALIAS_WRITE_ACCOUNT = 0x0010;
+
+ private const UInt32 USER_READ_GENERAL = 0x0001;
+ private const UInt32 USER_READ_PREFERENCES = 0x0002;
+ private const UInt32 USER_WRITE_PREFERENCES = 0x0004;
+ private const UInt32 USER_READ_LOGON = 0x0008;
+ private const UInt32 USER_READ_ACCOUNT = 0x0010;
+ private const UInt32 USER_WRITE_ACCOUNT = 0x0020;
+ private const UInt32 USER_CHANGE_PASSWORD = 0x0040;
+ private const UInt32 USER_FORCE_PASSWORD_CHANGE = 0x0080;
+ private const UInt32 USER_LIST_GROUPS = 0x0100;
+ private const UInt32 USER_READ_GROUP_INFORMATION = 0x0200;
+ private const UInt32 USER_WRITE_GROUP_INFORMATION = 0x0400;
+
+ private const UInt32 DOMAIN_READ_PASSWORD_PARAMETERS = 0x0001;
+ private const UInt32 DOMAIN_WRITE_PASSWORD_PARAMS = 0x0002;
+ private const UInt32 DOMAIN_READ_OTHER_PARAMETERS = 0x0004;
+ private const UInt32 DOMAIN_WRITE_OTHER_PARAMETERS = 0x0008;
+ private const UInt32 DOMAIN_CREATE_USER = 0x0010;
+ private const UInt32 DOMAIN_CREATE_GROUP = 0x0020;
+ private const UInt32 DOMAIN_CREATE_ALIAS = 0x0040;
+ private const UInt32 DOMAIN_GET_ALIAS_MEMBERSHIP = 0x0080;
+ private const UInt32 DOMAIN_LIST_ACCOUNTS = 0x0100;
+ private const UInt32 DOMAIN_LOOKUP = 0x0200;
+ private const UInt32 DOMAIN_ADMINISTER_SERVER = 0x0400;
+#endregion Constants
+
+#region Static Data
+ private static SecurityIdentifier worldSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
+#endregion Static Data
+
+#region Instance Data
+ private IntPtr samHandle = IntPtr.Zero;
+ private IntPtr localDomainHandle = IntPtr.Zero;
+ private IntPtr builtinDomainHandle = IntPtr.Zero;
+ private Context context = null;
+ private string machineName = String.Empty;
+#endregion Instance Data
+
+#region Construction
+ internal Sam()
+ {
+ OpenHandles();
+
+ // CoreCLR does not have Environment.MachineName,
+ // so we'll use this instead.
+ machineName = System.Net.Dns.GetHostName();
+ }
+#endregion Construction
+
+#region Public (Internal) Methods
+ public string StripMachineName(string name)
+ {
+ var mn = machineName + '\\';
+
+ if (name.StartsWith(mn, StringComparison.CurrentCultureIgnoreCase))
+ return name.Substring(mn.Length);
+
+ return name;
+ }
+#region Local Groups
+ ///
+ /// Retrieve a named local group
+ ///
+ /// Name of the desired local group.
+ ///
+ /// A object containing information about
+ /// the local group.
+ ///
+ ///
+ /// Thrown when the named group cannot be found.
+ ///
+ internal LocalGroup GetLocalGroup(string groupName)
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.Group, groupName, groupName);
+
+ foreach (var sre in EnumerateGroups())
+ if (sre.Name.Equals(groupName, StringComparison.CurrentCultureIgnoreCase))
+ return MakeLocalGroupObject(sre); // return a populated group
+
+ throw new GroupNotFoundException(groupName, context.target);
+ }
+
+ ///
+ /// Retrieve a local group by SID
+ ///
+ ///
+ /// A object identifying the desired group.
+ ///
+ ///
+ /// A object containing information about
+ /// the local group.
+ ///
+ ///
+ /// Thrown when the specified group cannot be found.
+ ///
+ internal LocalGroup GetLocalGroup(SecurityIdentifier sid)
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.Group, sid.ToString(), sid);
+
+ foreach (var sre in EnumerateGroups())
+ if (RidToSid(sre.domainHandle, sre.RelativeId) == sid)
+ return MakeLocalGroupObject(sre); // return a populated group
+
+ throw new GroupNotFoundException(sid.ToString(), context.target);
+ }
+
+ ///
+ /// Create a local group.
+ ///
+ /// A object containing
+ /// information about the local group to be created.
+ ///
+ ///
+ /// A new LocalGroup object containing information about the newly
+ /// created local group.
+ ///
+ ///
+ /// Thrown when an attempt is made to create a local group that already
+ /// exists.
+ ///
+ internal LocalGroup CreateLocalGroup(LocalGroup group)
+ {
+ context = new Context(ContextOperation.New, ContextObjectType.Group, group.Name, group.Name);
+
+ return CreateGroup(group, localDomainHandle);
+ }
+
+ ///
+ /// Update a local group with new property values
+ ///
+ ///
+ /// A object representing the group to be updated.
+ ///
+ ///
+ /// A LocalGroup object containing the desired changes.
+ ///
+ ///
+ /// Currently, a group's description is the only changeable property.
+ ///
+ internal void UpdateLocalGroup(LocalGroup group, LocalGroup changed)
+ {
+ context = new Context(ContextOperation.Set, ContextObjectType.Group, group.Name, group);
+
+ UpdateGroup(group, changed);
+ }
+
+ ///
+ /// Remove a local group.
+ ///
+ ///
+ /// A object identifying the
+ /// local group to be removed.
+ ///
+ ///
+ /// Thrown when the specified group cannot be found.
+ ///
+ internal void RemoveLocalGroup(SecurityIdentifier sid)
+ {
+ context = new Context(ContextOperation.Remove, ContextObjectType.Group, sid.ToString(), sid);
+
+ RemoveGroup(sid);
+ }
+
+ ///
+ /// Remove a local group.
+ ///
+ ///
+ /// A object containing
+ /// information about the local group to be removed.
+ ///
+ ///
+ /// Thrown when the specified group cannot be found.
+ ///
+ internal void RemoveLocalGroup(LocalGroup group)
+ {
+ context = new Context(ContextOperation.Remove, ContextObjectType.Group, group.Name, group);
+
+ if (group.SID == null)
+ context.target = group = GetLocalGroup(group.Name);
+
+ RemoveGroup(group.SID);
+ }
+
+ ///
+ /// Rename a local group.
+ ///
+ ///
+ /// A object identifying
+ /// the local group to be renamed.
+ ///
+ ///
+ /// A string containing the new name for the local group.
+ ///
+ ///
+ /// Thrown when the specified group cannot be found.
+ ///
+ internal void RenameLocalGroup(SecurityIdentifier sid, string newName)
+ {
+ context = new Context(ContextOperation.Rename, ContextObjectType.Group, sid.ToString(), sid);
+
+ RenameGroup(sid, newName);
+ }
+
+ ///
+ /// Rename a local group.
+ ///
+ ///
+ /// A object containing
+ /// information about the local group to be renamed.
+ ///
+ ///
+ /// A string containing the new name for the local group.
+ ///
+ ///
+ /// Thrown when the specified group cannot be found.
+ ///
+ internal void RenameLocalGroup(LocalGroup group, string newName)
+ {
+ context = new Context(ContextOperation.Rename, ContextObjectType.Group, group.Name, group);
+
+ if (group.SID == null)
+ context.target = group = GetLocalGroup(group.Name);
+
+ RenameGroup(group.SID, newName);
+ }
+
+ ///
+ /// Get all local groups whose names satisfy the specified predicate.
+ ///
+ ///
+ /// Predicate that determines whether a group satisfies the conditions.
+ ///
+ ///
+ /// An object containing LocalGroup
+ /// objects that satisfy the predicate condition.
+ ///
+ internal IEnumerable GetMatchingLocalGroups(Predicate pred)
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.Group, string.Empty, null);
+
+ foreach (var sre in EnumerateGroups())
+ {
+
+ if (pred(sre.Name))
+ {
+ context.target = sre.Name;
+ yield return MakeLocalGroupObject(sre);
+ }
+ }
+ }
+
+ ///
+ /// Get all local groups.
+ ///
+ ///
+ /// An object containing a
+ /// LocalGroup object for each local group.
+ ///
+ internal IEnumerable GetAllLocalGroups()
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.Group, string.Empty, null);
+
+ foreach (var sre in EnumerateGroups())
+ {
+ context.target = sre.Name;
+ yield return MakeLocalGroupObject(sre);
+ }
+ }
+
+ ///
+ /// Add members to a local group.
+ ///
+ ///
+ /// A object identifying the group to
+ /// which to add members.
+ ///
+ ///
+ /// An object of type identifying
+ /// the member to be added.
+ ///
+ ///
+ /// An Exception object indicating any errors encountered.
+ ///
+ ///
+ /// Thrown if the group could not be found.
+ ///
+ internal Exception AddLocalGroupMember(LocalGroup group, LocalPrincipal member)
+ {
+ context = new Context(ContextOperation.AddMember, ContextObjectType.Group, group.Name, group);
+ if (group.SID == null)
+ context.target = group = GetLocalGroup(group.Name);
+
+ return AddGroupMember(group.SID, member);
+ }
+
+ ///
+ /// Add members to a local group.
+ ///
+ ///
+ /// A object identifying the group to
+ /// which to add members.
+ ///
+ ///
+ /// An object of type identifying
+ /// the member to be added.
+ ///
+ ///
+ /// An Exception object indicating any errors encountered.
+ ///
+ ///
+ /// Thrown if the group could not be found.
+ ///
+ internal Exception AddLocalGroupMember(SecurityIdentifier groupSid, LocalPrincipal member)
+ {
+ context = new Context(ContextOperation.AddMember, ContextObjectType.Group, groupSid.ToString(), groupSid);
+
+ return AddGroupMember(groupSid, member);
+ }
+
+ ///
+ /// Retrieve members of a Local group.
+ ///
+ ///
+ /// A object identifying the group whose members
+ /// are requested.
+ ///
+ ///
+ /// An IEnumerable of objects containing the group's
+ /// members.
+ ///
+ internal IEnumerable GetLocalGroupMembers(LocalGroup group)
+ {
+ context = new Context(ContextOperation.GetMember, ContextObjectType.Group, group.Name, group);
+
+ if (group.SID == null)
+ context.target = group = GetLocalGroup(group.Name);
+
+ return GetGroupMembers(group.SID);
+ }
+
+ ///
+ /// Retrieve members of a Local group.
+ ///
+ ///
+ /// A object identifying the group whose members
+ /// are requested.
+ ///
+ ///
+ /// An IEnumerable of objects containing the group's
+ /// members.
+ ///
+ internal IEnumerable GetLocalGroupMembers(SecurityIdentifier groupSid)
+ {
+ context = new Context(ContextOperation.GetMember, ContextObjectType.Group, groupSid.ToString(), groupSid);
+
+ return GetGroupMembers(groupSid);
+ }
+
+ ///
+ /// Remove members from a local group.
+ ///
+ ///
+ /// A object identifying the group from
+ /// which to remove members
+ ///
+ ///
+ /// An object of type identifying
+ /// the member to be removed.
+ ///
+ ///
+ /// An Exception object indicating any errors encountered.
+ ///
+ ///
+ /// Thrown if the group could not be found.
+ ///
+ internal Exception RemoveLocalGroupMember(LocalGroup group, LocalPrincipal member)
+ {
+ context = new Context(ContextOperation.RemoveMember, ContextObjectType.Group, group.Name, group);
+
+ if (group.SID == null)
+ context.target = group = GetLocalGroup(group.Name);
+
+ return RemoveGroupMember(group.SID, member);
+ }
+
+ ///
+ /// Remove members from a local group.
+ ///
+ ///
+ /// A object identifying the group from
+ /// which to remove members
+ ///
+ ///
+ /// An Object of type identifying
+ /// the member to be removed.
+ ///
+ ///
+ /// An Exception object indicating any errors encountered.
+ ///
+ ///
+ /// Thrown if the group could not be found.
+ ///
+ internal Exception RemoveLocalGroupMember(SecurityIdentifier groupSid, LocalPrincipal member)
+ {
+ context = new Context(ContextOperation.RemoveMember, ContextObjectType.Group, groupSid.ToString(), groupSid);
+
+ return RemoveGroupMember(groupSid, member);
+ }
+#endregion Local Groups
+
+#region Local Users
+ ///
+ /// Retrieve a named local user
+ ///
+ /// Name of the desired local user.
+ ///
+ /// A object containing information about
+ /// the local user.
+ ///
+ ///
+ /// Thrown when the named user cannot be found.
+ ///
+ internal LocalUser GetLocalUser(string userName)
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.User, userName, userName);
+
+ foreach (var sre in EnumerateUsers())
+ if (sre.Name.Equals(userName, StringComparison.CurrentCultureIgnoreCase))
+ return MakeLocalUserObject(sre);
+
+ throw new UserNotFoundException(userName, userName);
+ }
+
+ ///
+ /// Retrieve a local user by SID
+ ///
+ ///
+ /// A object identifying the desired user.
+ ///
+ ///
+ /// A object containing information about
+ /// the local user.
+ ///
+ ///
+ /// Thrown when the specified user cannot be found.
+ ///
+ internal LocalUser GetLocalUser(SecurityIdentifier sid)
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.User, sid.ToString(), sid);
+
+ foreach (var sre in EnumerateUsers())
+ if (RidToSid(sre.domainHandle, sre.RelativeId) == sid)
+ return MakeLocalUserObject(sre); // return a populated user
+
+ throw new UserNotFoundException(sid.ToString(), sid);
+ }
+
+ ///
+ /// Create a local user
+ ///
+ /// A object containing
+ /// information about the local user to be created.
+ ///
+ /// A containing
+ /// the initial password to be set for the new local user. If this parameter is null,
+ /// no password is set.
+ ///
+ ///
+ /// Indicates whether PasswordNeverExpires was specified
+ ///
+ ///
+ /// A new LocalGroup object containing information about the newly
+ /// created local user.
+ ///
+ ///
+ /// Thrown when an attempt is made to create a local user that already
+ /// exists.
+ ///
+ internal LocalUser CreateLocalUser(LocalUser user, System.Security.SecureString password, bool setPasswordNeverExpires)
+ {
+ context = new Context(ContextOperation.New, ContextObjectType.User, user.Name, user);
+
+ return CreateUser(user, password, localDomainHandle, setPasswordNeverExpires);
+ }
+
+ ///
+ /// Remove a local user.
+ ///
+ ///
+ /// A object identifying
+ /// the local user to be removed.
+ ///
+ ///
+ /// Thrown when the specified user cannot be found.
+ ///
+ internal void RemoveLocalUser(SecurityIdentifier sid)
+ {
+ context = new Context(ContextOperation.Remove, ContextObjectType.User, sid.ToString(), sid);
+
+ RemoveUser(sid);
+ }
+
+ ///
+ /// Remove a local user.
+ ///
+ ///
+ /// A object containing
+ /// information about the local user to be removed.
+ ///
+ ///
+ /// Thrown when the specified user cannot be found.
+ ///
+ internal void RemoveLocalUser(LocalUser user)
+ {
+ context = new Context(ContextOperation.Remove, ContextObjectType.User, user.Name, user);
+
+ if (user.SID == null)
+ context.target = user = GetLocalUser(user.Name);
+
+ RemoveUser(user.SID);
+ }
+
+
+ ///
+ /// Rename a local user.
+ ///
+ ///
+ /// A objects identifying
+ /// the local user to be renamed.
+ ///
+ ///
+ /// A string containing the new name for the local user.
+ ///
+ ///
+ /// Thrown when the specified user cannot be found.
+ ///
+ internal void RenameLocalUser(SecurityIdentifier sid, string newName)
+ {
+ context = new Context(ContextOperation.Rename, ContextObjectType.User, sid.ToString(), sid);
+
+ RenameUser(sid, newName);
+ }
+
+ ///
+ /// Rename a local user.
+ ///
+ ///
+ /// A objects containing
+ /// information about the local user to be renamed.
+ ///
+ ///
+ /// A string containing the new name for the local user.
+ ///
+ ///
+ /// Thrown when the specified user cannot be found.
+ ///
+ internal void RenameLocalUser(LocalUser user, string newName)
+ {
+ context = new Context(ContextOperation.Rename, ContextObjectType.User, user.Name, user);
+
+ if (user.SID == null)
+ context.target = user = GetLocalUser(user.Name);
+
+ RenameUser(user.SID, newName);
+ }
+
+ ///
+ /// Enable or disable a Local User
+ ///
+ ///
+ /// A object identifying the user to enable or disable.
+ ///
+ ///
+ /// One of the enumeration values, indicating whether to
+ /// enable or disable the user.
+ ///
+ internal void EnableLocalUser(SecurityIdentifier sid, Enabling enable)
+ {
+ context = new Context(enable == Enabling.Enable ? ContextOperation.Enable
+ : ContextOperation.Disable,
+ ContextObjectType.User, sid.ToString(),
+ sid);
+
+ EnableUser(sid, enable);
+ }
+
+ ///
+ /// Enable or disable a Local User
+ ///
+ ///
+ /// A object representing the user to enable or disable.
+ ///
+ ///
+ /// One of the enumeration values, indicating whether to
+ /// enable or disable the user.
+ ///
+ internal void EnableLocalUser(LocalUser user, Enabling enable)
+ {
+ context = new Context(enable == Enabling.Enable ? ContextOperation.Enable
+ : ContextOperation.Disable,
+ ContextObjectType.User, user.Name,
+ user);
+
+ if (user.SID == null)
+ context.target = user = GetLocalUser(user.Name);
+
+ EnableUser(user.SID, enable);
+ }
+
+ ///
+ /// Update a local user with new properties.
+ ///
+ ///
+ /// A object representing the user to be updated.
+ ///
+ ///
+ /// A LocalUser object containing the desired changes.
+ ///
+ /// A
+ /// object containing the new password. A null value in this parameter
+ /// indicates that the password is not to be changed.
+ ///
+ ///
+ /// Specifies whether the PasswordNeverExpires parameter was set.
+ ///
+ ///
+ /// Call this overload when intending to leave the password-expired
+ /// marker in its current state. To set the password and the
+ /// password-expired state, call the overload with a boolean as the
+ /// fourth parameter
+ ///
+ internal void UpdateLocalUser(LocalUser user, LocalUser changed, System.Security.SecureString password, bool? setPasswordNeverExpires)
+ {
+ context = new Context(ContextOperation.Set, ContextObjectType.User, user.Name, user);
+
+ UpdateUser(user, changed, password, PasswordExpiredState.Unchanged, setPasswordNeverExpires);
+ }
+
+ ///
+ /// Get all local users whose names satisfy the specified predicate.
+ ///
+ ///
+ /// Predicate that determines whether a user satisfies the conditions.
+ ///
+ ///
+ /// An object containing LocalUser
+ /// objects that satisfy the predicate condition.
+ ///
+ internal IEnumerable GetMatchingLocalUsers(Predicate pred)
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.User, string.Empty, null);
+
+ foreach (var sre in EnumerateUsers())
+ {
+ if (pred(sre.Name))
+ {
+ context.target = sre.Name;
+ yield return MakeLocalUserObject(sre);
+ }
+ }
+ }
+
+ ///
+ /// Get all local users.
+ ///
+ ///
+ /// An object containing a
+ /// LocalUser object for each local user.
+ ///
+ internal IEnumerable GetAllLocalUsers()
+ {
+ context = new Context(ContextOperation.Get, ContextObjectType.User, null, null);
+
+ foreach (var sre in EnumerateUsers())
+ yield return MakeLocalUserObject(sre);
+ }
+#endregion Local Users
+
+#region Local Principals
+ internal LocalPrincipal LookupAccount(string name)
+ {
+ var info = LookupAccountInfo(name);
+
+ if (info == null)
+ throw new PrincipalNotFoundException(name, name);
+
+ return MakeLocalPrincipalObject(info);
+ }
+#endregion Local Principals
+#endregion Public (Internal) Methods
+
+#region Private Methods
+ ///
+ /// Open the handles stored by Sam instances.
+ ///
+ private void OpenHandles()
+ {
+ var systemName = new UNICODE_STRING();
+ var oa = new OBJECT_ATTRIBUTES();
+ IntPtr pInfo = IntPtr.Zero;
+ IntPtr pSid = IntPtr.Zero;
+ IntPtr lsaHandle = IntPtr.Zero;
+ UInt32 status = 0;
+
+ try
+ {
+ status = Win32.LsaOpenPolicy(ref systemName, ref oa, (UInt32)LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION, out lsaHandle);
+ ThrowOnFailure(status);
+
+ POLICY_PRIMARY_DOMAIN_INFO domainInfo;
+
+ status = Win32.LsaQueryInformationPolicy(lsaHandle,
+ POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation,
+ out pInfo);
+ ThrowOnFailure(status);
+ status = Win32.LsaClose(lsaHandle);
+ ThrowOnFailure(status);
+
+ lsaHandle = IntPtr.Zero;
+
+ domainInfo = ClrFacade.PtrToStructure(pInfo);
+
+ status = SamApi.SamConnect(ref systemName, out samHandle, SamApi.SAM_SERVER_LOOKUP_DOMAIN, ref oa);
+ ThrowOnFailure(status);
+
+ // Open the local domain
+ status = SamApi.SamOpenDomain(samHandle, Win32.MAXIMUM_ALLOWED, domainInfo.Sid, out localDomainHandle);
+ ThrowOnFailure(status);
+
+ // Open the "BuiltIn" domain
+ SecurityIdentifier sid = new SecurityIdentifier("S-1-5-32");
+ byte[] bSid = new byte[sid.BinaryLength];
+ int size = ClrFacade.SizeOf() * bSid.Length;
+
+ pSid = Marshal.AllocHGlobal(size);
+
+ sid.GetBinaryForm(bSid, 0);
+ Marshal.Copy(bSid, 0, pSid, bSid.Length);
+
+ status = SamApi.SamOpenDomain(samHandle, Win32.MAXIMUM_ALLOWED, pSid, out builtinDomainHandle);
+
+ ThrowOnFailure(status);
+ }
+ finally
+ {
+ if (pInfo != IntPtr.Zero)
+ status = Win32.LsaFreeMemory(pInfo);
+
+ Marshal.FreeHGlobal(pSid);
+
+ if (lsaHandle != IntPtr.Zero)
+ status = Win32.LsaClose(lsaHandle);
+ }
+ }
+
+ ///
+ /// Find a named group and return a object
+ /// representing the group.
+ ///
+ /// Name of the group to search for.
+ ///
+ /// A SamRidEnumeration object representing the group.
+ ///
+ ///
+ /// Thrown when the specified group is not found.
+ ///
+ ///
+ /// This method saves some time and effort over the GetGroup method
+ /// because it does not have to open a group to populate a full Group
+ /// object.
+ ///
+ private SamRidEnumeration GetGroupSre(string groupName)
+ {
+ foreach (var sre in EnumerateGroups())
+ if (sre.Name.Equals(groupName, StringComparison.CurrentCultureIgnoreCase))
+ return sre;
+
+ throw new GroupNotFoundException(groupName, context.target);
+ }
+
+ ///
+ /// Find a group by SID and return a object
+ /// representing the group.
+ ///
+ /// A object identifying
+ /// the group to search for.
+ ///
+ /// A SamRidEnumeration object representing the group.
+ ///
+ ///
+ /// Thrown when the specified group is not found.
+ ///
+ ///
+ /// This method saves some time and effort over the GetGroup method
+ /// because it does not have to open a group to populate a full Group
+ /// object.
+ ///
+ private SamRidEnumeration GetGroupSre(SecurityIdentifier sid)
+ {
+ foreach (var sre in EnumerateGroups())
+ if (RidToSid(sre.domainHandle, sre.RelativeId) == sid)
+ return sre;
+
+ throw new GroupNotFoundException(sid.ToString(), sid);
+ }
+
+ ///
+ /// Find a named user and return a object
+ /// representing the user.
+ ///
+ /// Name of the user to search for.
+ ///
+ /// A SamRidEnumeration object representing the user.
+ ///
+ ///
+ /// Thrown when the specified user is not found.
+ ///
+ ///
+ /// This method saves some time and effort over the GetUser method
+ /// because it does not have to open a group to populate a full LocalUser
+ /// object.
+ ///
+ private SamRidEnumeration GetUserSre(string userName)
+ {
+ foreach (var sre in EnumerateGroups())
+ if (sre.Name.Equals(userName, StringComparison.CurrentCultureIgnoreCase))
+ return sre;
+
+ throw new GroupNotFoundException(userName, userName);
+ }
+
+ ///
+ /// Find a user by SID and return a object
+ /// representing the user.
+ ///
+ /// A object identifying
+ /// the user to search for.
+ ///
+ /// A SamRidEnumeration object representing the user.
+ ///
+ ///
+ /// Thrown when the specified user is not found.
+ ///
+ ///
+ /// This method saves some time and effort over the GetUser method
+ /// because it does not have to open a user to populate a full LocalUser
+ /// object.
+ ///
+ private SamRidEnumeration GetUserSre(SecurityIdentifier sid)
+ {
+ foreach (var sre in EnumerateUsers())
+ if (RidToSid(sre.domainHandle, sre.RelativeId) == sid)
+ return sre;
+
+ throw new UserNotFoundException(sid.ToString(), sid);
+ }
+
+ ///
+ /// Enumerate local users with native SAM functions.
+ ///
+ /// Handle to the domain to enumerate over.
+ ///
+ /// An IEnumerable of SamRidEnumeration objects, one for each local user.
+ ///
+ ///
+ /// This is a "generator" method. Rather than returning an entire collection,
+ /// it uses 'yield return' to return each object in turn.
+ ///
+ private static IEnumerable EnumerateUsersInDomain(IntPtr domainHandle)
+ {
+ UInt32 status = 0;
+ UInt32 context = 0;
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 countReturned;
+
+ do
+ {
+ status = SamApi.SamEnumerateUsersInDomain2(domainHandle,
+ ref context,
+ 0,
+ SamApi.SAM_USER_ENUMERATION_FILTER_LOCAL,
+ //0, // non-filtered
+ out buffer,
+ 1,
+ out countReturned);
+
+ if (status == NtStatus.STATUS_MORE_ENTRIES && countReturned == 1)
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ SAM_RID_ENUMERATION sre;
+
+ sre = ClrFacade.PtrToStructure(buffer);
+
+ SamApi.SamFreeMemory(buffer);
+ buffer = IntPtr.Zero;
+
+ yield return new SamRidEnumeration
+ {
+ Name = sre.Name.ToString(),
+ RelativeId = sre.RelativeId,
+
+ domainHandle = domainHandle
+ };
+ }
+ }
+ } while (Succeeded(status) && status != 0 && countReturned != 0);
+ }
+
+ ///
+ /// Enumerate user objects in both the local and builtin domains.
+ ///
+ ///
+ /// An IEnumerable of SamRidEnumeration objects, one for each local user.
+ ///
+ ///
+ /// This is a "generator" method. Rather than returning an entire collection,
+ /// it uses 'yield return' to return each object in turn.
+ ///
+ private IEnumerable EnumerateUsers()
+ {
+ foreach (var sre in EnumerateUsersInDomain(localDomainHandle))
+ yield return sre;
+
+ foreach (var sre in EnumerateUsersInDomain(builtinDomainHandle))
+ yield return sre;
+ }
+
+ ///
+ /// Create a new user in the specified domain.
+ ///
+ ///
+ /// A object containing information about the new user.
+ ///
+ /// A containing
+ /// the initial password to be set for the new local user. If this parameter is null,
+ /// no password is set.
+ ///
+ ///
+ /// Handle to the domain in which to create the new user.
+ ///
+ ///
+ /// Indicates whether PasswordNeverExpires was specified
+ ///
+ ///
+ /// A LocalUser object that represents the newly-created user
+ ///
+ private LocalUser CreateUser(LocalUser userInfo, System.Security.SecureString password, IntPtr domainHandle, bool setPasswordNeverExpires)
+ {
+ IntPtr userHandle = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+ UNICODE_STRING str = new UNICODE_STRING();
+ UInt32 status = 0;
+
+ try
+ {
+ UInt32 relativeId = 0;
+ UInt32 grantedAccess = 0;
+
+ str = new UNICODE_STRING(userInfo.Name);
+
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(str));
+ Marshal.StructureToPtr(str, buffer, false);
+
+ status = SamApi.SamCreateUser2InDomain(domainHandle,
+ ref str,
+ (int) SamApi.USER_NORMAL_ACCOUNT,
+ Win32.MAXIMUM_ALLOWED,
+ out userHandle,
+ out grantedAccess,
+ out relativeId);
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ buffer = IntPtr.Zero;
+ ThrowOnFailure(status);
+
+ // set the various properties of the user. A SID is required because some
+ // operations depend on it.
+ userInfo.SID = RidToSid(domainHandle, relativeId);
+
+ SetUserData(userHandle, userInfo, UserProperties.AllCreateable, password, PasswordExpiredState.NotExpired, setPasswordNeverExpires);
+
+ return MakeLocalUserObject(new SamRidEnumeration
+ {
+ domainHandle = domainHandle,
+ Name = userInfo.Name,
+ RelativeId = relativeId
+ },
+ userHandle);
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ Marshal.FreeHGlobal(buffer);
+ if (userHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(userHandle);
+ }
+ }
+
+ ///
+ /// Remove a group identified by SID
+ ///
+ ///
+ /// A object identifying the
+ /// group to be removed.
+ ///
+ private void RemoveGroup(SecurityIdentifier sid)
+ {
+ var sre = GetGroupSre(sid);
+
+ IntPtr aliasHandle = IntPtr.Zero;
+ UInt32 status;
+ try
+ {
+ status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+ ThrowOnFailure(status);
+
+ status = SamApi.SamDeleteAlias(aliasHandle);
+ ThrowOnFailure(status);
+ }
+ finally
+ {
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+ }
+
+ ///
+ /// Rename a group identified by SID.
+ ///
+ ///
+ /// A object identifying
+ /// the local group to be renamed.
+ ///
+ ///
+ /// A string containing the new name for the group.
+ ///
+ ///
+ /// Thrown when the specified group cannot be found.
+ ///
+ private void RenameGroup(SecurityIdentifier sid, string newName)
+ {
+ var sre = GetGroupSre(sid);
+
+ IntPtr aliasHandle = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 status = 0;
+
+ status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+ ThrowOnFailure(status);
+
+ try
+ {
+ ALIAS_NAME_INFORMATION info = new ALIAS_NAME_INFORMATION();
+
+ info.Name = new UNICODE_STRING(newName);
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(info));
+ Marshal.StructureToPtr(info, buffer, false);
+
+ status = SamApi.SamSetInformationAlias(aliasHandle,
+ ALIAS_INFORMATION_CLASS.AliasNameInformation,
+ buffer);
+ ThrowOnFailure(status,
+ new Context {
+ objectId = newName,
+ operation = context.operation,
+ type = context.type
+ }
+ );
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ }
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+ }
+
+ ///
+ /// Add members to a group.
+ ///
+ ///
+ /// A object identifying the group to
+ /// which to add members.
+ ///
+ ///
+ /// An object of type identifying
+ /// the member to be added.
+ ///
+ ///
+ /// An Exception object indicating any errors encountered.
+ ///
+ ///
+ /// Thrown if the group could not be found.
+ ///
+ private Exception AddGroupMember(SecurityIdentifier groupSid, LocalPrincipal member)
+ {
+ var sre = GetGroupSre(groupSid); // We'll let this throw if necessary
+
+ IntPtr aliasHandle = IntPtr.Zero;
+ UInt32 status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+ ThrowOnFailure(status);
+ Exception ex = null;
+ try
+ {
+ var sid = member.SID;
+ var binarySid = new byte[sid.BinaryLength];
+
+ sid.GetBinaryForm(binarySid, 0);
+ status = SamApi.SamAddMemberToAlias(aliasHandle, binarySid);
+ ex = MakeException(status,
+ new Context
+ {
+ memberId = member.ToString(),
+ objectId = context.objectId,
+ operation = context.operation,
+ target = context.target,
+ type = context.type
+ }
+ );
+
+ }
+ finally
+ {
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+
+ return ex;
+ }
+
+ ///
+ /// Retrieve members of a group.
+ ///
+ ///
+ /// A object representing the group whose members
+ /// are requested.
+ ///
+ ///
+ /// An IEnumerable of objects containing the group's
+ /// members.
+ ///
+ private IEnumerable GetGroupMembers(SecurityIdentifier groupSid)
+ {
+ var sre = GetGroupSre(groupSid);
+
+ IntPtr aliasHandle = IntPtr.Zero;
+ IntPtr memberIds = IntPtr.Zero;
+ UInt32 status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+ ThrowOnFailure(status);
+
+ try
+ {
+ UInt32 memberCount = 0;
+
+ status = SamApi.SamGetMembersInAlias(aliasHandle, out memberIds, out memberCount);
+ ThrowOnFailure(status);
+
+ if (memberCount != 0)
+ {
+ IntPtr[] idArray = new IntPtr[memberCount];
+
+ Marshal.Copy(memberIds, idArray, 0, (int)memberCount);
+
+ for (int i=0; i < memberCount; i++)
+ {
+ var sid = new SecurityIdentifier(idArray[i]);
+ yield return MakeLocalPrincipalObject(LookupAccountInfo(sid));
+ }
+ }
+ }
+ finally
+ {
+ if (aliasHandle != IntPtr.Zero)
+ SamApi.SamCloseHandle(aliasHandle);
+
+ if (memberIds != IntPtr.Zero)
+ SamApi.SamFreeMemory(memberIds);
+ }
+ }
+
+ ///
+ /// Remove members from a group.
+ ///
+ ///
+ /// A object identifying the group from
+ /// which to remove members
+ ///
+ ///
+ /// An object of type identifying
+ /// the member to be removed.
+ ///
+ ///
+ /// An IEnumerable of Exception objects indicating any errors encountered.
+ ///
+ ///
+ /// Thrown if the group could not be found.
+ ///
+ private Exception RemoveGroupMember(SecurityIdentifier groupSid, LocalPrincipal member)
+ {
+ var sre = GetGroupSre(groupSid); // We'll let this throw if necessary
+
+ IntPtr aliasHandle = IntPtr.Zero;
+ UInt32 status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+ ThrowOnFailure(status);
+
+ // Now we're processing each member, so any further exceptions will
+ // be stored in the collection and returned later.
+ var rv = new List();
+ Exception ex = null;
+ try
+ {
+ var sid = member.SID;
+ var binarySid = new byte[sid.BinaryLength];
+
+ sid.GetBinaryForm(binarySid, 0);
+ status = SamApi.SamRemoveMemberFromAlias(aliasHandle, binarySid);
+
+ ex = MakeException(status,
+ new Context {
+ memberId = member.ToString(),
+ objectId = context.objectId,
+ operation = context.operation,
+ target = context.target,
+ type = context.type
+ }
+ );
+ }
+ finally
+ {
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+
+ return ex;
+ }
+
+ ///
+ /// Create a populated LocalUser object from a SamRidEnumeration object.
+ ///
+ ///
+ /// A object containing minimal information
+ /// about a local user.
+ ///
+ ///
+ /// A LocalUser object, populated with user information.
+ ///
+ private LocalUser MakeLocalUserObject(SamRidEnumeration sre)
+ {
+ IntPtr userHandle = IntPtr.Zero;
+ var status = SamApi.SamOpenUser(sre.domainHandle,
+ (UInt32)ObjectAccess.UserRead,
+ sre.RelativeId,
+ out userHandle);
+
+ ThrowOnFailure(status);
+
+ try
+ {
+ return MakeLocalUserObject(sre, userHandle);
+ }
+ finally
+ {
+ if (userHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(userHandle);
+ }
+ }
+
+ ///
+ /// Create a populated LocalUser object from a SamRidEnumeration object,
+ /// using an already-opened SAM user handle.
+ ///
+ ///
+ /// A object containing minimal information
+ /// about a local user.
+ ///
+ ///
+ /// Handle to an open SAM user.
+ ///
+ ///
+ /// A LocalUser object, populated with user information.
+ ///
+ private LocalUser MakeLocalUserObject(SamRidEnumeration sre, IntPtr userHandle)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 status = 0;
+
+ try
+ {
+ USER_ALL_INFORMATION allInfo;
+
+ status = SamApi.SamQueryInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserAllInformation,
+ out buffer);
+ ThrowOnFailure(status);
+ allInfo = ClrFacade.PtrToStructure(buffer);
+
+ var userSid = RidToSid(sre.domainHandle, sre.RelativeId);
+ LocalUser user = new LocalUser()
+ {
+ PrincipalSource = GetPrincipalSource(sre),
+ SID = userSid,
+
+ Name = allInfo.UserName.ToString(),
+ FullName = allInfo.FullName.ToString(),
+ Description = allInfo.AdminComment.ToString(),
+
+ //TODO: why is this coming up as 864000000000 (number of ticks per day)?
+ PasswordChangeableDate = DateTimeFromSam(allInfo.PasswordCanChange.QuadPart),
+
+ PasswordExpires = DateTimeFromSam(allInfo.PasswordMustChange.QuadPart),
+
+ //TODO: why is this coming up as 0X7FFFFFFFFFFFFFFF (largest signed 64-bit, and well out of range of DateTime)?
+ AccountExpires = DateTimeFromSam(allInfo.AccountExpires.QuadPart),
+ LastLogon = DateTimeFromSam(allInfo.LastLogon.QuadPart),
+ PasswordLastSet = DateTimeFromSam(allInfo.PasswordLastSet.QuadPart),
+
+ UserMayChangePassword = GetUserMayChangePassword(userHandle, userSid),
+
+ PasswordRequired = (allInfo.UserAccountControl & SamApi.USER_PASSWORD_NOT_REQUIRED) == 0,
+
+ Enabled = !((allInfo.UserAccountControl & SamApi.USER_ACCOUNT_DISABLED) == SamApi.USER_ACCOUNT_DISABLED)
+ };
+
+ return user;
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ status = SamApi.SamFreeMemory(buffer);
+ }
+ }
+
+ ///
+ /// Enable or disable a user.
+ ///
+ ///
+ /// A object identifying the user to be
+ /// enabled or disabled.
+ ///
+ ///
+ /// One of the enumeration values indicating
+ /// whether the user is to be enabled or disabled.
+ ///
+ private void EnableUser(SecurityIdentifier sid, Enabling enable)
+ {
+ IntPtr userHandle = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 status = 0;
+
+ var sre = GetUserSre(sid);
+
+ status = SamApi.SamOpenUser(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out userHandle);
+ ThrowOnFailure(status);
+
+ try
+ {
+ USER_ALL_INFORMATION info;
+
+ status = SamApi.SamQueryInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserAllInformation,
+ out buffer);
+ ThrowOnFailure(status);
+ info = ClrFacade.PtrToStructure(buffer);
+ status = SamApi.SamFreeMemory(buffer);
+ buffer = IntPtr.Zero;
+
+ UInt32 uac = info.UserAccountControl;
+ UInt32 enabled_state = uac & SamApi.USER_ACCOUNT_DISABLED;
+
+ if (enable == Enabling.Enable && enabled_state == SamApi.USER_ACCOUNT_DISABLED)
+ uac &= ~SamApi.USER_ACCOUNT_DISABLED;
+ else if (enable == Enabling.Disable && enabled_state != SamApi.USER_ACCOUNT_DISABLED)
+ uac |= SamApi.USER_ACCOUNT_DISABLED;
+ else
+ return;
+
+ if (uac != info.UserAccountControl)
+ {
+ info.UserAccountControl = uac;
+ info.WhichFields = SamApi.USER_ALL_USERACCOUNTCONTROL;
+
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(info));
+ Marshal.StructureToPtr(info, buffer, false);
+ status = SamApi.SamSetInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserAllInformation,
+ buffer);
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ buffer = IntPtr.Zero;
+ ThrowOnFailure(status);
+ }
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ Marshal.FreeHGlobal(buffer);
+ if (userHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(userHandle);
+ }
+ }
+
+ ///
+ /// Rename a user.
+ ///
+ ///
+ /// A object identifying the user to be
+ /// renamed.
+ ///
+ /// The new user name.
+ private void RenameUser(SecurityIdentifier sid, string newName)
+ {
+ IntPtr userHandle = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 status = 0;
+
+ var sre = GetUserSre(sid);
+
+ status = SamApi.SamOpenUser(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out userHandle);
+ ThrowOnFailure(status);
+
+ try
+ {
+ USER_ACCOUNT_NAME_INFORMATION info = new USER_ACCOUNT_NAME_INFORMATION();
+
+ info.UserName = new UNICODE_STRING(newName);
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(info));
+ Marshal.StructureToPtr(info, buffer, false);
+
+ status = SamApi.SamSetInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserAccountNameInformation,
+ buffer);
+ ThrowOnFailure(status,
+ new Context {
+ objectId = newName,
+ operation = context.operation,
+ type = context.type
+ }
+ );
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ }
+ if (userHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(userHandle);
+ }
+ }
+
+ ///
+ /// Delete a user.
+ ///
+ ///
+ /// A object identifying the user to be
+ /// removed.
+ ///
+ private void RemoveUser(SecurityIdentifier sid)
+ {
+ IntPtr userHandle = IntPtr.Zero;
+
+ var sre = GetUserSre(sid);
+ UInt32 status;
+
+ try
+ {
+ status = SamApi.SamOpenUser(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out userHandle);
+ ThrowOnFailure(status);
+
+ status = SamApi.SamDeleteUser(userHandle);
+ ThrowOnFailure(status);
+ }
+ finally
+ {
+ if (userHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(userHandle);
+ }
+ }
+
+ ///
+ /// Enumerate local users with native SAM functions.
+ ///
+ /// Handle to the domain to enumerate over.
+ ///
+ /// An IEnumerable of SamRidEnumeration objects, one for each local user.
+ ///
+ ///
+ /// This is a "generator" method. Rather than returning an entire collection,
+ /// it uses 'yield return' to return each object in turn.
+ ///
+ private static IEnumerable EnumerateGroupsInDomain(IntPtr domainHandle)
+ {
+ UInt32 status = 0;
+ UInt32 context = 0;
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 countReturned;
+
+ do
+ {
+ // Although the method name indicates that we are operating with "groups",
+ // it actually uses the SAM API's SamEnumerateAliasesInDomain function.
+ status = SamApi.SamEnumerateAliasesInDomain(domainHandle,
+ ref context,
+ out buffer,
+ 1,
+ out countReturned);
+
+ if (status == NtStatus.STATUS_MORE_ENTRIES && countReturned == 1)
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ SAM_RID_ENUMERATION sre;
+
+ sre = ClrFacade.PtrToStructure(buffer);
+
+ SamApi.SamFreeMemory(buffer);
+ buffer = IntPtr.Zero;
+
+ yield return new SamRidEnumeration
+ {
+ Name = sre.Name.ToString(),
+ RelativeId = sre.RelativeId,
+
+ domainHandle = domainHandle
+ };
+ }
+ }
+ } while (Succeeded(status) && status != 0 && countReturned != 0);
+ }
+
+ ///
+ /// Enumerate group objects in both the local and builtin domains.
+ ///
+ ///
+ /// An IEnumerable of SamRidEnumeration objects, one for each local group.
+ ///
+ ///
+ /// This is a "generator" method. Rather than returning an entire collection,
+ /// it uses 'yield return' to return each object in turn.
+ ///
+ internal IEnumerable EnumerateGroups()
+ {
+ foreach (var sre in EnumerateGroupsInDomain(localDomainHandle))
+ yield return sre;
+
+ foreach (var sre in EnumerateGroupsInDomain(builtinDomainHandle))
+ yield return sre;
+ }
+
+ ///
+ /// Create a new group in the specified domain.
+ ///
+ ///
+ /// A object containing information about the new group.
+ ///
+ /// Handle to the domain in which to create the new group.
+ ///
+ /// A LocalGroup object that represents the newly-created group.
+ ///
+ private LocalGroup CreateGroup(LocalGroup groupInfo, IntPtr domainHandle)
+ {
+ IntPtr aliasHandle = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+ UNICODE_STRING str = new UNICODE_STRING();
+ UInt32 status;
+
+ try
+ {
+ UInt32 relativeId;
+
+ str = new UNICODE_STRING(groupInfo.Name);
+
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(str));
+ Marshal.StructureToPtr(str, buffer, false);
+
+ status = SamApi.SamCreateAliasInDomain(domainHandle,
+ buffer,
+ Win32.MAXIMUM_ALLOWED,
+ out aliasHandle,
+ out relativeId);
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ buffer = IntPtr.Zero;
+ ThrowOnFailure(status);
+
+ if (!string.IsNullOrEmpty(groupInfo.Description))
+ {
+ ALIAS_ADM_COMMENT_INFORMATION info = new ALIAS_ADM_COMMENT_INFORMATION();
+
+ info.AdminComment = new UNICODE_STRING(groupInfo.Description);
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(info));
+ Marshal.StructureToPtr(info, buffer, false);
+
+ status = SamApi.SamSetInformationAlias(aliasHandle,
+ ALIAS_INFORMATION_CLASS.AliasAdminCommentInformation,
+ buffer);
+
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ buffer = IntPtr.Zero;
+ ThrowOnFailure(status);
+ }
+
+ return MakeLocalGroupObject(new SamRidEnumeration
+ {
+ domainHandle = domainHandle,
+ Name = groupInfo.Name,
+ RelativeId = relativeId
+ },
+ aliasHandle);
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ Marshal.FreeHGlobal(buffer);
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+ }
+
+ ///
+ /// Update a local group with new property values. This method provides
+ /// the actual implementation.
+ ///
+ ///
+ /// A object representing the group to be updated.
+ ///
+ ///
+ /// A LocalGroup object containing the desired changes.
+ ///
+ ///
+ /// Currently, a group's description is the only changeable property.
+ ///
+ private void UpdateGroup(LocalGroup group, LocalGroup changed)
+ {
+ // Only description may be changed
+ if (group.Description == changed.Description)
+ return;
+
+ IntPtr aliasHandle = IntPtr.Zero;
+ IntPtr buffer = IntPtr.Zero;
+
+ if (group.SID == null)
+ group = GetLocalGroup(group.Name);
+
+ var sre = GetGroupSre(group.SID);
+ UInt32 status;
+
+ try
+ {
+ status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+ ThrowOnFailure(status);
+
+ ALIAS_ADM_COMMENT_INFORMATION info = new ALIAS_ADM_COMMENT_INFORMATION();
+
+ info.AdminComment = new UNICODE_STRING(changed.Description);
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(info));
+ Marshal.StructureToPtr(info, buffer, false);
+
+ status = SamApi.SamSetInformationAlias(aliasHandle,
+ ALIAS_INFORMATION_CLASS.AliasAdminCommentInformation,
+ buffer);
+
+ ThrowOnFailure(status);
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ }
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+ }
+
+ ///
+ /// Create a populated LocalGroup object from a SamRidEnumeration object.
+ ///
+ ///
+ /// A object containing minimal information
+ /// about a local group.
+ ///
+ ///
+ /// A LocalGroup object, populated with group information.
+ ///
+ private LocalGroup MakeLocalGroupObject(SamRidEnumeration sre)
+ {
+ IntPtr aliasHandle = IntPtr.Zero;
+ var status = SamApi.SamOpenAlias(sre.domainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ sre.RelativeId,
+ out aliasHandle);
+
+ ThrowOnFailure(status);
+
+ try
+ {
+ return MakeLocalGroupObject(sre, aliasHandle);
+ }
+ finally
+ {
+ if (aliasHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(aliasHandle);
+ }
+ }
+
+ ///
+ /// Create a populated LocalGroup object from a SamRidEnumeration object,
+ /// using an already-opened SAM alias handle.
+ ///
+ ///
+ /// A object containing minimal information
+ /// about a local group.
+ ///
+ ///
+ /// Handle to an open SAM alias.
+ ///
+ ///
+ /// A LocalGroup object, populated with group information.
+ ///
+ private LocalGroup MakeLocalGroupObject(SamRidEnumeration sre, IntPtr aliasHandle)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ UInt32 status = 0;
+
+ try
+ {
+ ALIAS_GENERAL_INFORMATION generalInfo;
+
+ status = SamApi.SamQueryInformationAlias(aliasHandle,
+ ALIAS_INFORMATION_CLASS.AliasGeneralInformation,
+ out buffer);
+ ThrowOnFailure(status);
+ generalInfo = ClrFacade.PtrToStructure(buffer);
+
+ LocalGroup group = new LocalGroup()
+ {
+ PrincipalSource = GetPrincipalSource(sre),
+ SID = RidToSid(sre.domainHandle, sre.RelativeId),
+
+ Name = generalInfo.Name.ToString(),
+ Description = generalInfo.AdminComment.ToString()
+ };
+
+ return group;
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ status = SamApi.SamFreeMemory(buffer);
+ }
+ }
+
+ ///
+ /// Update a local user with new properties.
+ ///
+ ///
+ /// A object representing the user to be updated.
+ ///
+ ///
+ /// A LocalUser object containing the desired changes.
+ ///
+ /// A
+ /// object containing the new password. A null value in this parameter
+ /// indicates that the password is not to be changed.
+ ///
+ /// One of the
+ /// enumeration values indicating
+ /// whether the password-expired state is to be explicitly set or
+ /// left as is.
+ /// If the parameter is null, this parameter
+ /// is ignored.
+ ///
+ ///
+ /// Indicates whether the PasswordNeverExpires parameter was specified.
+ ///
+ private void UpdateUser(LocalUser user,
+ LocalUser changed,
+ System.Security.SecureString password,
+ PasswordExpiredState passwordExpired,
+ bool? setPasswordNeverExpires)
+ {
+ UserProperties properties = UserProperties.None;
+
+ if (user.AccountExpires != changed.AccountExpires)
+ properties |= UserProperties.AccountExpires;
+
+ if (user.Description != changed.Description)
+ properties |= UserProperties.Description;
+
+ if (user.FullName != changed.FullName)
+ properties |= UserProperties.FullName;
+
+ if (setPasswordNeverExpires.HasValue)
+ properties |= UserProperties.PasswordNeverExpires;
+
+ if (user.UserMayChangePassword != changed.UserMayChangePassword)
+ properties |= UserProperties.UserMayChangePassword;
+
+ if (user.PasswordRequired != changed.PasswordRequired)
+ properties |= UserProperties.PasswordRequired;
+
+ if ( properties != UserProperties.None
+ || passwordExpired != PasswordExpiredState.Unchanged
+ || password != null)
+ {
+ IntPtr userHandle = IntPtr.Zero;
+ UInt32 status = 0;
+
+ try
+ {
+ status = SamApi.SamOpenUser(localDomainHandle,
+ Win32.MAXIMUM_ALLOWED,
+ user.SID.GetRid(),
+ out userHandle);
+ ThrowOnFailure(status);
+
+ SetUserData(userHandle, changed, properties, password, passwordExpired, setPasswordNeverExpires);
+ }
+ finally
+ {
+ if (userHandle != IntPtr.Zero)
+ status = SamApi.SamCloseHandle(userHandle);
+ }
+ }
+ }
+
+ ///
+ /// Set selected properties of a user.
+ ///
+ /// Handle to an open SAM user.
+ ///
+ /// A object containing the data to set into the user.
+ ///
+ ///
+ /// A combination of values indicating the properties to be set.
+ ///
+ /// A
+ /// object containing the new password.
+ ///
+ /// One of the
+ /// enumeration values indicating
+ /// whether the password-expired state is to be explicitly set or
+ /// left as is. If the parameter is null,
+ /// this parameter is ignored.
+ ///
+ ///
+ /// Nullable value the specifies whether the PasswordNeverExpires bit should be flipped
+ ///
+ private void SetUserData(IntPtr userHandle,
+ LocalUser sourceUser,
+ UserProperties setFlags,
+ System.Security.SecureString password,
+ PasswordExpiredState passwordExpired,
+ bool? setPasswordNeverExpires)
+ {
+ IntPtr buffer = IntPtr.Zero;
+
+ try
+ {
+ UInt32 which = 0;
+ UInt32 status = 0;
+ UInt32 uac = GetUserAccountControl(userHandle);
+ USER_ALL_INFORMATION info = new USER_ALL_INFORMATION();
+
+ if (setFlags.HasFlag(UserProperties.AccountExpires))
+ {
+ which |= SamApi.USER_ALL_ACCOUNTEXPIRES;
+ info.AccountExpires.QuadPart = sourceUser.AccountExpires.HasValue
+ ? sourceUser.AccountExpires.Value.ToFileTime()
+ : 0L;
+ }
+ if (setFlags.HasFlag(UserProperties.Description))
+ {
+ which |= SamApi.USER_ALL_ADMINCOMMENT;
+ info.AdminComment = new UNICODE_STRING(sourceUser.Description);
+ }
+ if (setFlags.HasFlag(UserProperties.Enabled))
+ {
+ which |= SamApi.USER_ALL_USERACCOUNTCONTROL;
+ if (sourceUser.Enabled)
+ uac &= ~SamApi.USER_ACCOUNT_DISABLED;
+ else
+ uac |= SamApi.USER_ACCOUNT_DISABLED;
+ }
+ if (setFlags.HasFlag(UserProperties.FullName))
+ {
+ which |= SamApi.USER_ALL_FULLNAME;
+ info.FullName = new UNICODE_STRING(sourceUser.FullName);
+ }
+
+ if (setFlags.HasFlag(UserProperties.PasswordNeverExpires))
+ {
+ // Only modify the bit if a change was requested
+ if (setPasswordNeverExpires.HasValue)
+ {
+ which |= SamApi.USER_ALL_USERACCOUNTCONTROL;
+ if (setPasswordNeverExpires.Value)
+ uac |= SamApi.USER_DONT_EXPIRE_PASSWORD;
+ else
+ uac &= ~SamApi.USER_DONT_EXPIRE_PASSWORD;
+ }
+ }
+
+ if (setFlags.HasFlag(UserProperties.PasswordRequired))
+ {
+ which |= SamApi.USER_ALL_USERACCOUNTCONTROL;
+ if (sourceUser.PasswordRequired)
+ uac &= ~SamApi.USER_PASSWORD_NOT_REQUIRED;
+ else
+ uac |= SamApi.USER_PASSWORD_NOT_REQUIRED;
+ }
+
+ if (which != 0)
+ {
+ info.WhichFields = which;
+ if ((which & SamApi.USER_ALL_USERACCOUNTCONTROL) != 0)
+ info.UserAccountControl = uac;
+
+ buffer = Marshal.AllocHGlobal(ClrFacade.SizeOf());
+ ClrFacade.StructureToPtr(info, buffer, false);
+
+ status = SamApi.SamSetInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserAllInformation,
+ buffer);
+ ThrowOnFailure(status);
+ status = SamApi.SamFreeMemory(buffer);
+ buffer = IntPtr.Zero;
+ }
+
+ if (setFlags.HasFlag(UserProperties.UserMayChangePassword))
+ SetUserMayChangePassword(userHandle, sourceUser.SID, sourceUser.UserMayChangePassword);
+
+ if (password != null)
+ SetUserPassword(userHandle, password, passwordExpired);
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+ }
+
+ ///
+ /// Retrieve the User's User Account Control flags.
+ ///
+ ///
+ /// Handle to an open user.
+ ///
+ ///
+ /// A 32-bit unsigned integer containing the User Account Control
+ /// flags as a set of bits.
+ ///
+ private UInt32 GetUserAccountControl(IntPtr userHandle)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ USER_LOGON_INFORMATION info;
+ UInt32 status;
+
+ try
+ {
+ status = SamApi.SamQueryInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserLogonInformation,
+ out buffer);
+ ThrowOnFailure(status);
+ info = ClrFacade.PtrToStructure(buffer);
+ status = SamApi.SamFreeMemory(buffer);
+ buffer = IntPtr.Zero;
+
+ return info.UserAccountControl;
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ status = SamApi.SamFreeMemory(buffer);
+ }
+ }
+
+ ///
+ /// Retrieve the DACL from a SAM object.
+ ///
+ ///
+ /// A handle to the SAM object whose DACL is to be retrieved.
+ ///
+ ///
+ /// A object containing the DACL retrieved from
+ /// the SAM object.
+ ///
+ private RawAcl GetSamDacl(IntPtr objectHandle)
+ {
+ RawAcl rv = null;
+ IntPtr securityObject = IntPtr.Zero;
+ UInt32 status = 0;
+
+ try
+ {
+ status = SamApi.SamQuerySecurityObject(objectHandle, Win32.DACL_SECURITY_INFORMATION, out securityObject);
+ ThrowOnFailure(status);
+
+ SECURITY_DESCRIPTOR sd = ClrFacade.PtrToStructure(securityObject);
+
+ bool daclPresent;
+ bool daclDefaulted;
+ IntPtr dacl;
+ bool ok = Win32.GetSecurityDescriptorDacl(securityObject, out daclPresent, out dacl, out daclDefaulted);
+
+ if (!ok)
+ {
+ var error = Marshal.GetLastWin32Error();
+ if (error == Win32.ERROR_ACCESS_DENIED)
+ throw new AccessDeniedException(context.target);
+ else
+ throw new Win32InternalException(error, context.target);
+ }
+
+ if (daclPresent)
+ {
+ ACL acl = ClrFacade.PtrToStructure(dacl);
+
+ if (acl.AclSize != 0)
+ {
+ // put the DACL into managed data
+ var bytes = new byte[acl.AclSize];
+
+ Marshal.Copy(dacl, bytes, 0, acl.AclSize);
+ rv = new RawAcl(bytes, 0);
+ }
+ }
+ }
+ finally
+ {
+ if (IntPtr.Zero != securityObject)
+ status = SamApi.SamFreeMemory(securityObject);
+ }
+ return rv;
+ }
+
+ ///
+ /// Set the DACL of a SAM object.
+ ///
+ ///
+ /// A handle to the SAM object whose DACL is to be retrieved.
+ ///
+ ///
+ /// A object containing the DACL to be set into
+ /// the SAM object.
+ ///
+ private void SetSamDacl(IntPtr objectHandle, RawAcl rawAcl)
+ {
+ IntPtr ipsd = IntPtr.Zero;
+ IntPtr ipDacl = IntPtr.Zero;
+
+ try
+ {
+ bool present = false;
+
+ // create a new security descriptor
+ var sd = new SECURITY_DESCRIPTOR() { Revision = 1 };
+ ipsd = Marshal.AllocHGlobal(ClrFacade.SizeOf());
+
+ if (rawAcl != null && rawAcl.BinaryLength > 0)
+ {
+ ClrFacade.StructureToPtr(sd, ipsd, false);
+
+ // put the DACL into unmanaged memory
+ var length = rawAcl.BinaryLength;
+ var bytes = new byte[length];
+ rawAcl.GetBinaryForm(bytes, 0);
+ ipDacl = Marshal.AllocHGlobal(length);
+
+ Marshal.Copy(bytes, 0, ipDacl, length);
+ present = true;
+ }
+
+ // set the DACL into our new sescurity descriptor
+ var ok = Win32.SetSecurityDescriptorDacl(ipsd, present, ipDacl, false);
+ if (!ok)
+ {
+ var error = Marshal.GetLastWin32Error();
+
+ if (error == Win32.ERROR_ACCESS_DENIED)
+ throw new AccessDeniedException(context.target);
+ else
+ throw new Win32InternalException(error, context.target);
+ }
+
+ var status = SamApi.SamSetSecurityObject(objectHandle, Win32.DACL_SECURITY_INFORMATION, ipsd);
+ ThrowOnFailure(status);
+ }
+ finally
+ {
+ Marshal.FreeHGlobal(ipDacl);
+ Marshal.FreeHGlobal(ipsd);
+ }
+ }
+
+ ///
+ /// Determine if a user account password may be changed by the user.
+ ///
+ ///
+ /// Handle to a SAM user object.
+ ///
+ ///
+ /// A object identifying the SAM
+ /// object's associated user.
+ ///
+ ///
+ /// True if the user account password may be changed by its user,
+ /// false otherwise.
+ ///
+ ///
+ /// The ability to for the user to change the user account password
+ /// is a permission in the object's DACL. This method walks through
+ /// the ACEs in the DACL, checking if the permission is granted to
+ /// either Everyone or the user identified by the userSid parameter.
+ ///
+ private bool GetUserMayChangePassword(IntPtr userHandle, SecurityIdentifier userSid)
+ {
+ var rawAcl = GetSamDacl(userHandle);
+
+ // if there is no DACL, then access is granted
+ if (rawAcl == null)
+ return true;
+
+ foreach (var a in rawAcl)
+ {
+ var ace = a as CommonAce;
+ if (ace != null && ace.AceType == AceType.AccessAllowed)
+ {
+ if (ace.SecurityIdentifier == worldSid ||
+ ace.SecurityIdentifier == userSid)
+ {
+ if ((ace.AccessMask & SamApi.USER_CHANGE_PASSWORD) != 0)
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Set whether a user account password may be changed by the user.
+ ///
+ ///
+ /// Handle to a SAM user object.
+ ///
+ ///
+ /// A object identifying the SAM
+ /// object's associated user.
+ ///
+ ///
+ /// A boolean indicating whether the permission is to be enabled or
+ /// disabled.
+ ///
+ ///
+ /// The ability to for the user to change the user account password
+ /// is a permission in the object's DACL. This method walks through
+ /// the ACEs in the DACL, enabling or disabling the permission on ACEs
+ /// associated with either Everyone or the user identified by the
+ /// userSid parameter.
+ ///
+ private void SetUserMayChangePassword(IntPtr userHandle, SecurityIdentifier userSid, bool enable)
+ {
+ var changed = false;
+ var rawAcl = GetSamDacl(userHandle);
+
+ if (rawAcl != null)
+ {
+ foreach (var a in rawAcl)
+ {
+ var ace = a as CommonAce;
+ if (ace != null && ace.AceType == AceType.AccessAllowed)
+ {
+ if (ace.SecurityIdentifier == worldSid ||
+ ace.SecurityIdentifier == userSid)
+ {
+ if (enable)
+ ace.AccessMask |= SamApi.USER_CHANGE_PASSWORD;
+ else
+ ace.AccessMask &= ~SamApi.USER_CHANGE_PASSWORD;
+
+ changed = true;
+ }
+ }
+ }
+
+ if (changed)
+ SetSamDacl(userHandle, rawAcl);
+ }
+ }
+
+ ///
+ /// Determine if a user's password has expired.
+ ///
+ ///
+ /// Handle to an open User.
+ ///
+ ///
+ /// True if the user's password has expired, false otherwise.
+ ///
+ private bool IsPasswordExpired(IntPtr userHandle)
+ {
+ IntPtr buffer = IntPtr.Zero;
+ USER_ALL_INFORMATION info;
+ UInt32 status;
+
+ try
+ {
+ status = SamApi.SamQueryInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserAllInformation,
+ out buffer);
+ ThrowOnFailure(status);
+ info = ClrFacade.PtrToStructure(buffer);
+ status = SamApi.SamFreeMemory(buffer);
+ buffer = IntPtr.Zero;
+
+ return info.PasswordExpired;
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ status = SamApi.SamFreeMemory(buffer);
+ }
+ }
+
+ ///
+ /// Set a user's password.
+ ///
+ /// Handle to an open User.
+ /// A
+ /// object containing the new password.
+ ///
+ /// One of the
+ /// enumeration values indicating
+ /// whether the password-expired state is to be explicitly set or
+ /// left as is.
+ ///
+ private void SetUserPassword(IntPtr userHandle,
+ System.Security.SecureString password,
+ PasswordExpiredState passwordExpired)
+ {
+ if (password != null)
+ {
+ USER_SET_PASSWORD_INFORMATION info = new USER_SET_PASSWORD_INFORMATION();
+ IntPtr buffer = IntPtr.Zero;
+
+ try
+ {
+ bool setPwExpire = false;
+
+ switch (passwordExpired)
+ {
+ case PasswordExpiredState.Expired:
+ setPwExpire = true;
+ break;
+
+ case PasswordExpiredState.NotExpired:
+ setPwExpire = false;
+ break;
+
+ case PasswordExpiredState.Unchanged:
+ setPwExpire = IsPasswordExpired(userHandle);
+ break;
+ }
+
+ info.Password = new UNICODE_STRING(password.AsString());
+ info.PasswordExpired = setPwExpire;
+
+ buffer = Marshal.AllocHGlobal(Marshal.SizeOf(info));
+ ClrFacade.StructureToPtr(info, buffer, false);
+
+ var status = SamApi.SamSetInformationUser(userHandle,
+ USER_INFORMATION_CLASS.UserSetPasswordInformation,
+ buffer);
+ ThrowOnFailure(status);
+ }
+ finally
+ {
+ if (buffer != IntPtr.Zero)
+ {
+ ClrFacade.DestroyStructure(buffer);
+ Marshal.FreeHGlobal(buffer);
+ }
+ }
+ }
+ }
+
+#region Utility Methods
+ ///
+ /// Create a
+ /// object from a relative ID.
+ ///
+ ///
+ /// Handle to the domain from which the ID was acquired.
+ ///
+ ///
+ /// The Relative ID value.
+ ///
+ ///
+ /// A SecurityIdentifier object containing the SID of the
+ /// object identified by the parameter.
+ ///
+ private SecurityIdentifier RidToSid(IntPtr domainHandle, uint rid)
+ {
+ IntPtr sidBytes = IntPtr.Zero;
+ UInt32 status = 0;
+ SecurityIdentifier sid = null;
+
+ try
+ {
+ SamApi.SamRidToSid(domainHandle, rid, out sidBytes);
+
+ if (status == NtStatus.STATUS_NOT_FOUND)
+ throw new InternalException(status,
+ StringUtil.Format(Strings.RidToSidFailed, rid),
+ ErrorCategory.ObjectNotFound);
+ ThrowOnFailure(status);
+
+ sid = new SecurityIdentifier(sidBytes);
+ }
+ finally
+ {
+ if (IntPtr.Zero != sidBytes)
+ status = SamApi.SamFreeMemory(sidBytes);
+ }
+ return sid;
+ }
+
+ ///
+ /// Lookup the account identified by the specified SID.
+ ///
+ ///
+ /// A object identifying the account
+ /// to look up.
+ ///
+ ///
+ /// A object containg information about the
+ /// account, or null if no matching account was found.
+ ///
+ private AccountInfo LookupAccountInfo(SecurityIdentifier sid)
+ {
+ var sbAccountName = new StringBuilder();
+ var sbDomainName = new StringBuilder();
+ var accountNameLength = sbAccountName.Capacity;
+ var domainNameLength = sbDomainName.Capacity;
+ SID_NAME_USE use;
+ var error = Win32.NO_ERROR;
+ var bytes = new byte[sid.BinaryLength];
+
+ sid.GetBinaryForm(bytes, 0);
+
+ if (!Win32.LookupAccountSid(null,
+ bytes,
+ sbAccountName, ref accountNameLength,
+ sbDomainName, ref domainNameLength,
+ out use))
+ {
+ error = Marshal.GetLastWin32Error();
+
+ if (error == Win32.ERROR_INSUFFICIENT_BUFFER)
+ {
+ sbAccountName.EnsureCapacity(accountNameLength);
+ sbDomainName.EnsureCapacity((int)domainNameLength);
+ error = Win32.NO_ERROR;
+ if (!Win32.LookupAccountSid(null,
+ bytes,
+ sbAccountName, ref accountNameLength,
+ sbDomainName, ref domainNameLength,
+ out use))
+ error = Marshal.GetLastWin32Error();
+ }
+ }
+
+ if (error == Win32.ERROR_SUCCESS)
+ return new AccountInfo
+ {
+ AccountName = sbAccountName.ToString(),
+ DomainName = sbDomainName.ToString(),
+ Sid = sid,
+ Use = use
+ };
+ else if (error == Win32.ERROR_NONE_MAPPED)
+ return null;
+ else
+ throw new Win32InternalException(error, context.target);
+ }
+
+ ///
+ /// Lookup the account identified by specified account name.
+ ///
+ ///
+ /// A string containing the name of the account to look up.
+ ///
+ ///
+ /// A object containg information about the
+ /// account, or null if no matching account was found.
+ ///
+ private AccountInfo LookupAccountInfo(string accountName)
+ {
+ var sbDomainName = new StringBuilder();
+ var domainNameLength = (uint)sbDomainName.Capacity;
+ byte [] sid = null;
+ uint sidLength = 0;
+ SID_NAME_USE use;
+ int error = Win32.NO_ERROR;
+
+ if (!Win32.LookupAccountName(null,
+ accountName,
+ sid,
+ ref sidLength,
+ sbDomainName,
+ ref domainNameLength,
+ out use))
+ {
+ error = Marshal.GetLastWin32Error();
+ if (error == Win32.ERROR_INSUFFICIENT_BUFFER || error == Win32.ERROR_INVALID_FLAGS)
+ {
+ sid = new byte[sidLength];
+ sbDomainName.EnsureCapacity((int)domainNameLength);
+ error = Win32.NO_ERROR;
+
+ if (!Win32.LookupAccountName(null,
+ accountName,
+ sid,
+ ref sidLength,
+ sbDomainName,
+ ref domainNameLength,
+ out use))
+ error = Marshal.GetLastWin32Error();
+ }
+
+ }
+
+ if (error == Win32.ERROR_SUCCESS)
+ {
+ // Bug: 7407413 :
+ // If accountname is in the format domain1\user1,
+ //then AccountName.ToString() will return domain1\domain1\user1
+ // Ideally , accountname should be processed to hold only accout name (without domain)
+ // as we are keeping the domain in 'DomainName' variable.
+
+ int index = accountName.IndexOf("\\");
+ if (index > -1)
+ {
+ accountName = accountName.Substring(index + 1);
+ }
+ return new AccountInfo
+ {
+ AccountName = accountName,
+ DomainName = sbDomainName.ToString(),
+ Sid = new SecurityIdentifier(sid, 0),
+ Use = use
+ };
+ }
+ else if (error == Win32.ERROR_NONE_MAPPED)
+ return null;
+ else if (error == Win32.ERROR_ACCESS_DENIED)
+ throw new AccessDeniedException(context.target);
+ else
+ throw new Win32InternalException(error, context.target);
+ }
+
+ ///
+ /// Create a object from information in
+ /// an AccountInfo object.
+ ///
+ ///
+ /// An AccountInfo object containing information about the account
+ /// for which the LocalPrincipal object is being created. This parameter
+ /// may be null, in which case this method returns null.
+ ///
+ ///
+ /// A new LocalPrincipal object representing the account, or null if the
+ /// parameter is null.
+ ///
+ private LocalPrincipal MakeLocalPrincipalObject(AccountInfo info)
+ {
+ if (info == null)
+ return null; // this is a legitimate case
+
+ var rv = new LocalPrincipal(info.ToString());
+ rv.SID = info.Sid;
+ rv.PrincipalSource = GetPrincipalSource(info);
+
+ switch (info.Use)
+ {
+ case SID_NAME_USE.SidTypeAlias: //TODO: is this the right thing to do???
+ case SID_NAME_USE.SidTypeGroup:
+ case SID_NAME_USE.SidTypeWellKnownGroup:
+ rv.ObjectClass = Strings.ObjectClassGroup;
+ break;
+
+ case SID_NAME_USE.SidTypeUser:
+ rv.ObjectClass = Strings.ObjectClassUser;
+ break;
+
+ default:
+ rv.ObjectClass = Strings.ObjectClassOther;
+ break;
+ }
+
+ return rv;
+ }
+
+ ///
+ /// Indicate whether a Status code is a successful value.
+ ///
+ ///
+ /// One of the NTSTATUS code values indicating the error, if any.
+ ///
+ ///
+ /// True if the Status code represents a success, false otherwise.
+ ///
+ private static bool Succeeded(UInt32 ntStatus)
+ {
+ return NtStatus.IsSuccess(ntStatus);
+ }
+
+ ///
+ /// Helper to throw an exception if the provided Status code
+ /// represents a failure.
+ ///
+ ///
+ /// One of the NTSTATUS code values indicating the error, if any.
+ ///
+ ///
+ /// A object containing information about the
+ /// current operation. If this parameter is null, the class's context
+ /// is used.
+ ///
+ private void ThrowOnFailure(UInt32 ntStatus, Context context = null)
+ {
+ if (NtStatus.IsError(ntStatus))
+ {
+ var ex = MakeException(ntStatus, context);
+
+ if (ex != null)
+ throw ex;
+ }
+ }
+
+ ///
+ /// Create an appropriate exception from the specified status code.
+ ///
+ ///
+ /// One of the NTSTATUS code values indicating the error, if any.
+ ///
+ ///
+ /// A object containing information about the
+ /// current operation. If this parameter is null, the class's context
+ /// is used.
+ ///
+ ///
+ /// An object, or an object derived from Exception,
+ /// appropriate to the error. If does not
+ /// indicate an error, the method returns null.
+ ///
+ private Exception MakeException(UInt32 ntStatus, Context context = null)
+ {
+ if (!NtStatus.IsError(ntStatus))
+ return null;
+
+ if (context == null)
+ context = this.context;
+
+ switch (ntStatus)
+ {
+ case NtStatus.STATUS_ACCESS_DENIED:
+ return new AccessDeniedException(context.target);
+
+ case NtStatus.STATUS_INVALID_ACCOUNT_NAME:
+ return new InvalidNameException(context.ObjectName, context.target);
+
+ case NtStatus.STATUS_USER_EXISTS:
+ if (context.operation == ContextOperation.New &&
+ context.type == ContextObjectType.User)
+ {
+ return new UserExistsException(context.ObjectName, context.target);
+ }
+ else
+ {
+ return new NameInUseException(context.ObjectName, context.target);
+ }
+
+ case NtStatus.STATUS_ALIAS_EXISTS:
+ if (context.operation == ContextOperation.New &&
+ context.type == ContextObjectType.Group)
+ {
+ return new GroupExistsException(context.ObjectName, context.target);
+ }
+ else
+ {
+ return new NameInUseException(context.ObjectName, context.target);
+ }
+
+ case NtStatus.STATUS_GROUP_EXISTS:
+ return new NameInUseException(context.ObjectName, context.target);
+
+ case NtStatus.STATUS_NO_SUCH_ALIAS:
+ case NtStatus.STATUS_NO_SUCH_GROUP:
+ return new GroupNotFoundException(context.ObjectName, context.target);
+
+ case NtStatus.STATUS_NO_SUCH_USER:
+ return new UserNotFoundException(context.ObjectName, context.target);
+
+ case NtStatus.STATUS_SPECIAL_GROUP: // The group specified is a special group and cannot be operated on in the requested fashion.
+ //case NtStatus.STATUS_SPECIAL_ALIAS: // referred to in source for SAM api, but not in ntstatus.h!!!
+
+ return new InvalidOperationException(StringUtil.Format(Strings.InvalidForGroup, context.ObjectName));
+
+ case NtStatus.STATUS_SPECIAL_USER: // The user specified is a special user and cannot be operated on in the requested fashion.
+ return new InvalidOperationException(StringUtil.Format(Strings.InvalidForUser, context.ObjectName));
+
+ case NtStatus.STATUS_NO_SUCH_MEMBER:
+ return new MemberNotFoundException(context.MemberName, context.ObjectName);
+
+ case NtStatus.STATUS_MEMBER_IN_ALIAS:
+ case NtStatus.STATUS_MEMBER_IN_GROUP:
+ if (context.operation == ContextOperation.Remove &&
+ context.type == ContextObjectType.Group)
+ {
+ return new InvalidOperationException(StringUtil.Format(Strings.GroupHasMembers, context.ObjectName));
+ }
+ else
+ {
+ return new MemberExistsException(context.MemberName, context.ObjectName, context.target);
+ }
+
+ case NtStatus.STATUS_MEMBER_NOT_IN_ALIAS:
+ case NtStatus.STATUS_MEMBER_NOT_IN_GROUP:
+ return new MemberNotFoundException(context.MemberName, context.ObjectName);
+
+ case NtStatus.STATUS_MEMBERS_PRIMARY_GROUP:
+ return new InvalidOperationException(StringUtil.Format(Strings.MembersPrimaryGroup, context.ObjectName));
+
+ case NtStatus.STATUS_LAST_ADMIN: // Cannot delete the last administrator.
+ return new InvalidOperationException(Strings.LastAdmin);
+
+ case NtStatus.STATUS_ILL_FORMED_PASSWORD:
+ case NtStatus.STATUS_PASSWORD_RESTRICTION:
+ return new InvalidPasswordException(Native.Win32.RtlNtStatusToDosError(ntStatus));
+
+ //TODO: do we want to handle these?
+ // they appear to be returned only in functions we are not calling
+ case NtStatus.STATUS_INVALID_SID: // member sid is corrupted
+ case NtStatus.STATUS_INVALID_MEMBER: // member has wrong account type
+ default:
+ return new InternalException(ntStatus, context.target);
+ }
+ }
+
+ ///
+ /// Create a DateTime object from a 64-bit value from one of the SAM
+ /// structures.
+ ///
+ ///
+ /// A signed 64-bit value representing a date and time.
+ ///
+ ///
+ /// A nullable DateTime object representing a date and time,
+ /// or null if the parameter is zero.
+ ///
+ private static DateTime? DateTimeFromSam(Int64 samValue)
+ {
+ if (samValue == 0 || samValue == 0X7FFFFFFFFFFFFFFF)
+ return null;
+
+ return DateTime.FromFileTime(samValue);
+ }
+
+ ///
+ /// Determine the source of a user or group. Either local, Active Directory,
+ /// or Azure AD.
+ ///
+ ///
+ /// A object identifying the user or group.
+ ///
+ ///
+ /// One of the enumerations identifying the
+ /// source of the object.
+ ///
+ private PrincipalSource? GetPrincipalSource(SecurityIdentifier sid)
+ {
+ var bSid = new byte[sid.BinaryLength];
+
+ sid.GetBinaryForm(bSid, 0);
+
+ var type = LSA_USER_ACCOUNT_TYPE.UnknownUserAccountType;
+
+ // Use LsaLookupUserAccountType for Windows 10 and later.
+ // Earlier versions of the OS will leave the property NULL because
+ // it is too error prone to attempt to replicate the decisions of
+ // LsaLookupUserAccountType.
+ var os = GetOperatingSystem();
+ if (os.Version.Major >= 10)
+ {
+ UInt32 status = Native.Win32.LsaLookupUserAccountType(bSid, out type);
+ if (NtStatus.IsError(status))
+ type = LSA_USER_ACCOUNT_TYPE.UnknownUserAccountType;
+
+ switch (type)
+ {
+ case LSA_USER_ACCOUNT_TYPE.ExternalDomainUserAccountType:
+ case LSA_USER_ACCOUNT_TYPE.PrimaryDomainUserAccountType:
+ return PrincipalSource.ActiveDirectory;
+
+ case LSA_USER_ACCOUNT_TYPE.LocalUserAccountType:
+ return PrincipalSource.Local;
+
+ case LSA_USER_ACCOUNT_TYPE.AADUserAccountType:
+ return PrincipalSource.AzureAD;
+
+ // Currently, there is no value returned by LsaLookupUserAccountType
+ // that corresponds to LSA_USER_ACCOUNT_TYPE.MSAUserAccountType,
+ // but there may be in the future, so we'll account for it here.
+ case LSA_USER_ACCOUNT_TYPE.MSAUserAccountType:
+ case LSA_USER_ACCOUNT_TYPE.LocalConnectedUserAccountType:
+ return PrincipalSource.MicrosoftAccount;
+
+ case LSA_USER_ACCOUNT_TYPE.InternetUserAccountType:
+ return sid.IsMsaAccount()
+ ? PrincipalSource.MicrosoftAccount
+ : PrincipalSource.Unknown;
+
+ case LSA_USER_ACCOUNT_TYPE.UnknownUserAccountType:
+ default:
+ return PrincipalSource.Unknown;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ /// Determine the source of a user or group. Either local, Active Directory,
+ /// or Azure AD.
+ ///
+ ///
+ /// An object containing information about the
+ /// user or group.
+ ///
+ ///
+ /// One of the enumerations identifying the
+ /// source of the object.
+ ///
+ private PrincipalSource? GetPrincipalSource(AccountInfo info)
+ {
+ return GetPrincipalSource(info.Sid);
+ }
+
+ ///
+ /// Determine the source of a user or group. Either local, Active Directory,
+ /// or Azure AD.
+ ///
+ ///
+ /// A object identifying the user or group.
+ ///
+ ///
+ /// One of the enumerations identifying the
+ /// source of the object.
+ ///
+ private PrincipalSource? GetPrincipalSource(SamRidEnumeration sre)
+ {
+ return GetPrincipalSource(RidToSid(sre.domainHandle, sre.RelativeId));
+ }
+
+#if CORECLR
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ internal struct OSVERSIONINFOEX
+ {
+ // The OSVersionInfoSize field must be set to Marshal.SizeOf(this)
+ public int OSVersionInfoSize;
+ public int MajorVersion;
+ public int MinorVersion;
+ public int BuildNumber;
+ public int PlatformId;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string CSDVersion;
+ public ushort ServicePackMajor;
+ public ushort ServicePackMinor;
+ public short SuiteMask;
+ public byte ProductType;
+ public byte Reserved;
+ }
+
+ [DllImport(PInvokeDllNames.GetVersionExDllName, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern bool GetVersionEx(ref OSVERSIONINFOEX osVerEx);
+
+ private static volatile OperatingSystem localOs;
+
+ ///
+ /// It only contains the properties that get used in powershell
+ ///
+ internal sealed class OperatingSystem
+ {
+ private Version _version;
+ private string _servicePack;
+ private string _versionString;
+
+ internal OperatingSystem(Version version, string servicePack)
+ {
+ if (version == null)
+ throw new ArgumentNullException("version");
+
+ _version = version;
+ _servicePack = servicePack;
+ }
+
+ ///
+ /// OS version
+ ///
+ public Version Version
+ {
+ get { return _version; }
+ }
+
+ ///
+ /// VersionString
+ ///
+ public string VersionString
+ {
+ get
+ {
+ if (_versionString != null)
+ {
+ return _versionString;
+ }
+
+ // It's always 'VER_PLATFORM_WIN32_NT' for NanoServer and IoT
+ const string os = "Microsoft Windows NT ";
+ if (string.IsNullOrEmpty(_servicePack))
+ {
+ _versionString = os + _version.ToString();
+ }
+ else
+ {
+ _versionString = os + _version.ToString(3) + " " + _servicePack;
+ }
+
+ return _versionString;
+ }
+ }
+ }
+#endif
+
+ // Wraps calls to acquire the OperatingSystem version
+ private OperatingSystem GetOperatingSystem()
+ {
+#if CORECLR
+ if (localOs == null)
+ {
+ OSVERSIONINFOEX osviex = new OSVERSIONINFOEX();
+ osviex.OSVersionInfoSize = Marshal.SizeOf(osviex);
+ if (!GetVersionEx(ref osviex))
+ {
+ int errorCode = Marshal.GetLastWin32Error();
+ throw new Win32Exception(errorCode);
+ }
+
+ Version ver = new Version(osviex.MajorVersion, osviex.MinorVersion, osviex.BuildNumber, (osviex.ServicePackMajor << 16) | osviex.ServicePackMinor);
+ localOs = new OperatingSystem(ver, osviex.CSDVersion);
+ }
+ return localOs;
+#else
+ return Environment.OSVersion;
+#endif
+ }
+#endregion Utility Methods
+#endregion Private Methods
+
+#region IDisposable Support
+ private bool disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ UInt32 status = 0;
+
+ if (disposing)
+ {
+ // no managed objects need disposing.
+ }
+
+ if (builtinDomainHandle != IntPtr.Zero)
+ {
+ status = SamApi.SamCloseHandle(builtinDomainHandle);
+ builtinDomainHandle = IntPtr.Zero;
+ }
+
+ if (localDomainHandle != IntPtr.Zero)
+ {
+ status = SamApi.SamCloseHandle(localDomainHandle);
+ localDomainHandle = IntPtr.Zero;
+ }
+
+ if (samHandle != IntPtr.Zero)
+ {
+ status = SamApi.SamCloseHandle(samHandle);
+ samHandle = IntPtr.Zero;
+ }
+
+ if (NtStatus.IsError(status))
+ {
+ // Do nothing to satisfy CA1806: Do not ignore method results. We want the dispose to proceed regardless of the handle close status.
+ }
+
+ disposedValue = true;
+ }
+ }
+
+ // override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
+ ~Sam()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(false);
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ // uncomment the following line if the finalizer is overridden above.
+ GC.SuppressFinalize(this);
+ }
+#endregion IDisposable Support
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs
new file mode 100644
index 000000000..680a28962
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/SamApi.cs
@@ -0,0 +1,530 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+//using System.Management.Automation.SecurityAccountsManager.Native;
+
+namespace System.Management.Automation.SecurityAccountsManager.Native.NtSam
+{
+ #region Enums
+ internal enum ALIAS_INFORMATION_CLASS
+ {
+ AliasGeneralInformation = 1,
+ AliasNameInformation,
+ AliasAdminCommentInformation,
+ AliasReplicationInformation,
+ AliasExtendedInformation,
+ }
+
+ internal enum GROUP_INFORMATION_CLASS
+ {
+ GroupGeneralInformation = 1,
+ GroupNameInformation,
+ GroupAttributeInformation,
+ GroupAdminCommentInformation,
+ GroupReplicationInformation
+ }
+
+ internal enum USER_INFORMATION_CLASS
+ {
+ UserGeneralInformation = 1,
+ UserPreferencesInformation,
+ UserLogonInformation,
+ UserLogonHoursInformation,
+ UserAccountInformation,
+ UserNameInformation,
+ UserAccountNameInformation,
+ UserFullNameInformation,
+ UserPrimaryGroupInformation,
+ UserHomeInformation,
+ UserScriptInformation,
+ UserProfileInformation,
+ UserAdminCommentInformation,
+ UserWorkStationsInformation,
+ UserSetPasswordInformation,
+ UserControlInformation,
+ UserExpiresInformation,
+ UserInternal1Information,
+ UserInternal2Information,
+ UserParametersInformation,
+ UserAllInformation,
+ UserInternal3Information,
+ UserInternal4Information,
+ UserInternal5Information,
+ UserInternal4InformationNew,
+ UserInternal5InformationNew,
+ UserInternal6Information,
+ UserExtendedInformation,
+ UserLogonUIInformation,
+ }
+
+ #endregion Enums
+
+ #region Structures
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SR_SECURITY_DESCRIPTOR
+ {
+ public UInt32 Length;
+ public IntPtr SecurityDescriptor;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct LOGON_HOURS
+ {
+ public UInt16 UnitsPerWeek;
+
+ //
+ // UnitsPerWeek is the number of equal length time units the week is
+ // divided into. This value is used to compute the length of the bit
+ // string in logon_hours. Must be less than or equal to
+ // SAM_UNITS_PER_WEEK (10080) for this release.
+ //
+ // LogonHours is a bit map of valid logon times. Each bit represents
+ // a unique division in a week. The largest bit map supported is 1260
+ // bytes (10080 bits), which represents minutes per week. In this case
+ // the first bit (bit 0, byte 0) is Sunday, 00:00:00 - 00-00:59; bit 1,
+ // byte 0 is Sunday, 00:01:00 - 00:01:59, etc. A NULL pointer means
+ // DONT_CHANGE for SamSetInformationUser() calls.
+ //
+
+ public IntPtr LogonHours;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack=4)]
+ internal struct USER_ALL_INFORMATION
+ {
+ public LARGE_INTEGER LastLogon;
+ public LARGE_INTEGER LastLogoff;
+ public LARGE_INTEGER PasswordLastSet;
+ public LARGE_INTEGER AccountExpires;
+ public LARGE_INTEGER PasswordCanChange;
+ public LARGE_INTEGER PasswordMustChange;
+ public UNICODE_STRING UserName;
+ public UNICODE_STRING FullName;
+ public UNICODE_STRING HomeDirectory;
+ public UNICODE_STRING HomeDirectoryDrive;
+ public UNICODE_STRING ScriptPath;
+ public UNICODE_STRING ProfilePath;
+ public UNICODE_STRING AdminComment;
+ public UNICODE_STRING WorkStations;
+ public UNICODE_STRING UserComment;
+ public UNICODE_STRING Parameters;
+ public UNICODE_STRING LmPassword;
+ public UNICODE_STRING NtPassword;
+ public UNICODE_STRING PrivateData;
+ public SR_SECURITY_DESCRIPTOR SecurityDescriptor;
+ public UInt32 UserId;
+ public UInt32 PrimaryGroupId;
+ public UInt32 UserAccountControl;
+ public UInt32 WhichFields;
+ public LOGON_HOURS LogonHours;
+ public UInt16 BadPasswordCount;
+ public UInt16 LogonCount;
+ public UInt16 CountryCode;
+ public UInt16 CodePage;
+ [MarshalAs(UnmanagedType.I1)]
+ public bool LmPasswordPresent;
+ [MarshalAs(UnmanagedType.I1)]
+ public bool NtPasswordPresent;
+ [MarshalAs(UnmanagedType.I1)]
+ public bool PasswordExpired;
+ [MarshalAs(UnmanagedType.I1)]
+ public bool PrivateDataSensitive;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack=4)]
+ internal struct USER_GENERAL_INFORMATION
+ {
+ public UNICODE_STRING UserName;
+ public UNICODE_STRING FullName;
+ public UInt32 PrimaryGroupId;
+ public UNICODE_STRING AdminComment;
+ public UNICODE_STRING UserComment;
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack=4)]
+ internal struct USER_LOGON_INFORMATION
+ {
+ public UNICODE_STRING UserName;
+ public UNICODE_STRING FullName;
+ public UInt32 UserId;
+ public UInt32 PrimaryGroupId;
+ public UNICODE_STRING HomeDirectory;
+ public UNICODE_STRING HomeDirectoryDrive;
+ public UNICODE_STRING ScriptPath;
+ public UNICODE_STRING ProfilePath;
+ public UNICODE_STRING WorkStations;
+ public LARGE_INTEGER LastLogon;
+ public LARGE_INTEGER LastLogoff;
+ public LARGE_INTEGER PasswordLastSet;
+ public LARGE_INTEGER PasswordCanChange;
+ public LARGE_INTEGER PasswordMustChange;
+ public LOGON_HOURS LogonHours;
+ public UInt16 BadPasswordCount;
+ public UInt16 LogonCount;
+ public UInt32 UserAccountControl;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_ACCOUNT_NAME_INFORMATION
+ {
+ public UNICODE_STRING UserName;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_FULL_NAME_INFORMATION
+ {
+ public UNICODE_STRING FullName;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_NAME_INFORMATION
+ {
+ public UNICODE_STRING UserName;
+ public UNICODE_STRING FullName;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_ADMIN_COMMENT_INFORMATION
+ {
+ UNICODE_STRING AdminComment;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_EXPIRES_INFORMATION
+ {
+ //LARGE_INTEGER AccountExpires;
+ public Int64 AccountExpires;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_SET_PASSWORD_INFORMATION
+ {
+ public UNICODE_STRING Password;
+ public bool PasswordExpired;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct USER_LOGON_HOURS_INFORMATION
+ {
+ public LOGON_HOURS LogonHours;
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct POLICY_PRIMARY_DOMAIN_INFO
+ {
+ public UNICODE_STRING Name;
+ public IntPtr Sid;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ALIAS_GENERAL_INFORMATION
+ {
+ public UNICODE_STRING Name;
+ public UInt32 MemberCount;
+ public UNICODE_STRING AdminComment;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ALIAS_NAME_INFORMATION
+ {
+ public UNICODE_STRING Name;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct ALIAS_ADM_COMMENT_INFORMATION
+ {
+ public UNICODE_STRING AdminComment;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SAMPR_GROUP_GENERAL_INFORMATION
+ {
+ public UNICODE_STRING Name;
+ public UInt32 Attributes;
+ public UInt32 MemberCount;
+ public UNICODE_STRING AdminComment;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SAMPR_GROUP_NAME_INFORMATION
+ {
+ public UNICODE_STRING Name;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SAM_RID_ENUMERATION
+ {
+ public UInt32 RelativeId;
+ public UNICODE_STRING Name;
+ }
+ #endregion Structures
+
+ ///
+ /// Provides methods for invoking functions in the Windows
+ /// Security Accounts Manager (SAM) API.
+ ///
+ internal static class SamApi
+ {
+ #region Constants
+ // Account enumeration filters, may be combined by bitwise OR
+ internal const UInt32 SAM_USER_ENUMERATION_FILTER_LOCAL = 0x00000001;
+ internal const UInt32 SAM_USER_ENUMERATION_FILTER_INTERNET = 0x00000002;
+ internal const UInt32 SAM_SERVER_LOOKUP_DOMAIN = 0x0020;
+
+
+ //
+ // Bits to be used in UserAllInformation's WhichFields field (to indicate
+ // which items were queried or set).
+ //
+ internal const UInt32 USER_ALL_USERNAME = 0x00000001;
+ internal const UInt32 USER_ALL_FULLNAME = 0x00000002;
+ internal const UInt32 USER_ALL_USERID = 0x00000004;
+ internal const UInt32 USER_ALL_PRIMARYGROUPID = 0x00000008;
+ internal const UInt32 USER_ALL_ADMINCOMMENT = 0x00000010;
+ internal const UInt32 USER_ALL_USERCOMMENT = 0x00000020;
+ internal const UInt32 USER_ALL_HOMEDIRECTORY = 0x00000040;
+ internal const UInt32 USER_ALL_HOMEDIRECTORYDRIVE = 0x00000080;
+ internal const UInt32 USER_ALL_SCRIPTPATH = 0x00000100;
+ internal const UInt32 USER_ALL_PROFILEPATH = 0x00000200;
+ internal const UInt32 USER_ALL_WORKSTATIONS = 0x00000400;
+ internal const UInt32 USER_ALL_LASTLOGON = 0x00000800;
+ internal const UInt32 USER_ALL_LASTLOGOFF = 0x00001000;
+ internal const UInt32 USER_ALL_LOGONHOURS = 0x00002000;
+ internal const UInt32 USER_ALL_BADPASSWORDCOUNT = 0x00004000;
+ internal const UInt32 USER_ALL_LOGONCOUNT = 0x00008000;
+ internal const UInt32 USER_ALL_PASSWORDCANCHANGE = 0x00010000;
+ internal const UInt32 USER_ALL_PASSWORDMUSTCHANGE = 0x00020000;
+ internal const UInt32 USER_ALL_PASSWORDLASTSET = 0x00040000;
+ internal const UInt32 USER_ALL_ACCOUNTEXPIRES = 0x00080000;
+ internal const UInt32 USER_ALL_USERACCOUNTCONTROL = 0x00100000;
+ internal const UInt32 USER_ALL_PARAMETERS = 0x00200000; // ntsubauth
+ internal const UInt32 USER_ALL_COUNTRYCODE = 0x00400000;
+ internal const UInt32 USER_ALL_CODEPAGE = 0x00800000;
+ internal const UInt32 USER_ALL_NTPASSWORDPRESENT = 0x01000000; // field AND boolean
+ internal const UInt32 USER_ALL_LMPASSWORDPRESENT = 0x02000000; // field AND boolean
+ internal const UInt32 USER_ALL_PRIVATEDATA = 0x04000000; // field AND boolean
+ internal const UInt32 USER_ALL_PASSWORDEXPIRED = 0x08000000;
+ internal const UInt32 USER_ALL_SECURITYDESCRIPTOR = 0x10000000;
+ internal const UInt32 USER_ALL_OWFPASSWORD = 0x20000000; // boolean
+
+ internal const UInt32 USER_ALL_UNDEFINED_MASK = 0xC0000000;
+
+
+ //
+ // Bit masks for the UserAccountControl member of the USER_ALL_INFORMATION structure
+ //
+ internal const UInt32 USER_ACCOUNT_DISABLED = 0x00000001;
+ internal const UInt32 USER_HOME_DIRECTORY_REQUIRED = 0x00000002;
+ internal const UInt32 USER_PASSWORD_NOT_REQUIRED = 0x00000004;
+ internal const UInt32 USER_TEMP_DUPLICATE_ACCOUNT = 0x00000008;
+ internal const UInt32 USER_NORMAL_ACCOUNT = 0x00000010;
+ internal const UInt32 USER_MNS_LOGON_ACCOUNT = 0x00000020;
+ internal const UInt32 USER_INTERDOMAIN_TRUST_ACCOUNT = 0x00000040;
+ internal const UInt32 USER_WORKSTATION_TRUST_ACCOUNT = 0x00000080;
+ internal const UInt32 USER_SERVER_TRUST_ACCOUNT = 0x00000100;
+ internal const UInt32 USER_DONT_EXPIRE_PASSWORD = 0x00000200;
+ internal const UInt32 USER_ACCOUNT_AUTO_LOCKED = 0x00000400;
+ internal const UInt32 USER_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0x00000800;
+ internal const UInt32 USER_SMARTCARD_REQUIRED = 0x00001000;
+ internal const UInt32 USER_TRUSTED_FOR_DELEGATION = 0x00002000;
+ internal const UInt32 USER_NOT_DELEGATED = 0x00004000;
+ internal const UInt32 USER_USE_DES_KEY_ONLY = 0x00008000;
+ internal const UInt32 USER_DONT_REQUIRE_PREAUTH = 0x00010000;
+ internal const UInt32 USER_PASSWORD_EXPIRED = 0x00020000;
+ internal const UInt32 USER_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0x00040000;
+ internal const UInt32 USER_NO_AUTH_DATA_REQUIRED = 0x00080000;
+ internal const UInt32 USER_PARTIAL_SECRETS_ACCOUNT = 0x00100000;
+ internal const UInt32 USER_USE_AES_KEYS = 0x00200000;
+
+ //
+ // Access rights for user object
+ //
+ internal const UInt16 USER_CHANGE_PASSWORD = 0x0040;
+ #endregion Constants
+
+ #region Sam Functions
+ [DllImport("samlib.dll")]
+ public static extern UInt32 SamConnect(ref UNICODE_STRING serverName,
+ out IntPtr serverHandle,
+ UInt32 desiredAccess,
+ ref OBJECT_ATTRIBUTES objectAttributes);
+
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamRidToSid(IntPtr objectHandle, UInt32 rid, out IntPtr sid);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamCloseHandle(IntPtr serverHandle);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamFreeMemory(IntPtr buffer);
+
+ #region Domain Functions
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamOpenDomain(IntPtr serverHandle,
+ UInt32 desiredAccess,
+ IntPtr domainId,
+ out IntPtr domainHandle);
+ #endregion Domain Functions
+
+ #region Alias Functions
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamEnumerateAliasesInDomain(IntPtr domainHandle,
+ ref UInt32 enumerationContext,
+ out IntPtr buffer,
+ UInt32 preferredMaximumLength,
+ out UInt32 countReturned);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamCreateAliasInDomain(IntPtr domainHandle,
+ IntPtr accountName, // PUNICODE_STRING
+ UInt32 desiredAccess,
+ out IntPtr aliasHandle,
+ out UInt32 relativeId); // PULONG
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamOpenAlias(IntPtr domainHandle,
+ UInt32 desiredAccess,
+ UInt32 aliasId,
+ out IntPtr aliasHandle);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamQueryInformationAlias(IntPtr aliasHandle,
+ ALIAS_INFORMATION_CLASS aliasInformationClass,
+ out IntPtr buffer);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamSetInformationAlias(IntPtr aliasHandle,
+ ALIAS_INFORMATION_CLASS aliasInformationClass,
+ IntPtr buffer);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamDeleteAlias(IntPtr aliasHandle);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamAddMemberToAlias(IntPtr aliasHandle,
+ byte[] memberId); // PSID
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamAddMultipleMembersToAlias(IntPtr aliasHandle,
+ IntPtr memberIds, // PSID
+ UInt32 memberCount);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamRemoveMemberFromAlias(IntPtr aliasHandle,
+ byte[] memberId); // PSID
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamRemoveMultipleMembersFromAlias(IntPtr aliasHandle,
+ IntPtr memberIds, // PSID
+ UInt32 memberCount);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamGetMembersInAlias(IntPtr aliasHandle,
+ out IntPtr memberIds, // PSID **
+ out UInt32 memberCount);
+ #endregion Alias Functions
+
+ #region Group Functions
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamEnumerateGroupsInDomain(IntPtr domainHandle,
+ ref UInt32 enumerationContext,
+ out IntPtr buffer,
+ UInt32 preferredMaximumLength,
+ out UInt32 countReturned);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamCreateGroupInDomain(IntPtr domainHandle,
+ ref UNICODE_STRING accountName,
+ UInt32 desiredAccess,
+ out IntPtr groupHandle,
+ out UInt32 relativeId);
+
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamOpenGroup(IntPtr domainHandle,
+ UInt32 desiredAccess,
+ UInt32 groupId,
+ out IntPtr groupHandle);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamQueryInformationGroup(IntPtr groupHandle,
+ GROUP_INFORMATION_CLASS groupInformationClass,
+ out IntPtr buffer);
+
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamSetInformationGroup(IntPtr groupHandle,
+ GROUP_INFORMATION_CLASS groupInformationClass,
+ IntPtr buffer);
+ #endregion Group Functions
+
+ #region User Functions
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamOpenUser(IntPtr domainHandle,
+ UInt32 desiredAccess,
+ UInt32 userID,
+ out IntPtr userHandle);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamDeleteUser(IntPtr aliasHandle);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamEnumerateUsersInDomain(IntPtr domainHandle,
+ ref UInt32 enumerationContext,
+ UInt32 userAccountControl,
+ out IntPtr buffer,
+ UInt32 preferredMaximumLength,
+ out UInt32 countReturned);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamEnumerateUsersInDomain2(IntPtr domainHandle,
+ ref UInt32 enumerationContext,
+ UInt32 userAccountControl,
+ UInt32 filter,
+ out IntPtr buffer,
+ UInt32 preferredMaximumLength,
+ out UInt32 countReturned);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamCreateUserInDomain(IntPtr domainHandle,
+ ref UNICODE_STRING accountName,
+ UInt32 desiredAccess,
+ out IntPtr userHandle,
+ out UInt32 relativeId);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamCreateUser2InDomain(IntPtr domainHandle,
+ ref UNICODE_STRING accountName,
+ Int32 accountType,
+ UInt32 desiredAccess,
+ out IntPtr userHandle,
+ out UInt32 grantedAccess,
+ out UInt32 relativeId);
+
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamQueryInformationUser(IntPtr userHandle,
+ USER_INFORMATION_CLASS userInformationClass,
+ out IntPtr buffer);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamSetInformationUser(IntPtr userHandle,
+ USER_INFORMATION_CLASS userInformationClass,
+ IntPtr buffer);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamQuerySecurityObject(IntPtr objectHandle,
+ UInt32 securityInformation,
+ out IntPtr securityDescriptor);
+
+ [DllImport("samlib.dll")]
+ internal static extern UInt32 SamSetSecurityObject(IntPtr objectHandle,
+ UInt32 SecurityInformation,
+ IntPtr SecurityDescriptor);
+ #endregion User Functions
+ #endregion Sam Functions
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs
new file mode 100644
index 000000000..b1c653c93
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/StringUtil.cs
@@ -0,0 +1,58 @@
+using System.Globalization;
+using System.Management.Automation.SecurityAccountsManager.Native;
+
+namespace System.Management.Automation.SecurityAccountsManager
+{
+ ///
+ /// Contains utility functions for formatting localizable strings
+ ///
+ internal class StringUtil
+ {
+ internal static string Format(string str)
+ {
+ return string.Format(CultureInfo.CurrentCulture, str);
+ }
+
+ internal static string Format(string fmt, string p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, fmt, p0);
+ }
+
+ internal static string Format(string fmt, string p0, string p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, fmt, p0, p1);
+ }
+
+ internal static string Format(string fmt, uint p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, fmt, p0);
+ }
+ internal static string Format(string fmt, int p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, fmt, p0);
+ }
+
+ internal static string FormatMessage(uint messageId, string[] args)
+ {
+ var message = new System.Text.StringBuilder(256);
+ UInt32 flags = Win32.FORMAT_MESSAGE_FROM_SYSTEM;
+
+ if (args == null)
+ flags |= Win32.FORMAT_MESSAGE_IGNORE_INSERTS;
+ else
+ flags |= Win32.FORMAT_MESSAGE_ARGUMENT_ARRAY;
+
+ var length = Win32.FormatMessage(flags, IntPtr.Zero, messageId, 0, message, 256, args);
+
+ if (length > 0)
+ return message.ToString();
+
+ return null;
+ }
+
+ internal static string GetSystemMessage(uint messageId)
+ {
+ return FormatMessage(messageId, null);
+ }
+ }
+}
diff --git a/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx b/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx
new file mode 100644
index 000000000..c24110925
--- /dev/null
+++ b/src/Microsoft.PowerShell.LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx
@@ -0,0 +1,234 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Access denied.
+
+
+ Account {0} was not found.
+
+
+ Add member {0}
+
+
+ Disable local user
+
+
+ Enable local user
+
+
+ Create new local group
+
+
+ Create new local user
+
+
+ Remove local group
+
+
+ Remove member {0}
+
+
+ Remove local user
+
+
+ Rename local group to {0}
+
+
+ Rename local user to {0}
+
+
+ Modify local group
+
+
+ Modify local user
+
+
+ Group {0} already exists.
+
+
+ The group {0} still has members.
+
+
+ Group {0} was not found.
+
+
+ The operation is not allowed for group {0}.
+
+
+ The operation is not allowed for user {0}.
+
+
+ The name {0} is invalid.
+
+
+ Parameter {0} and parameter {1} may not be used together.
+
+
+ Invalid password.
+
+
+ Cannot remove the last Administrator
+
+
+ {0} is already a member of group {1}.
+
+
+ Member {0} was not found in group {1}.
+
+
+ User {0} may not be removed from its primary group.
+
+
+ The name {0} is already in use.
+
+
+ Group
+
+
+ Other
+
+
+ User
+
+
+ The password cannot be set because a password restriction is in place.
+
+
+ Principal {0} was not found.
+
+
+ RID {0} was not found.
+
+
+ An unspecified error occurred.
+
+
+ An unspecified error occurred: status = {0}
+
+
+ An unspecified error occurred: error code = {0}
+
+
+ User {0} already exists.
+
+
+ User {0} was not found.
+
+
\ No newline at end of file
diff --git a/src/powershell/Modules/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml b/src/powershell/Modules/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml
new file mode 100644
index 000000000..91b3b358a
--- /dev/null
+++ b/src/powershell/Modules/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml
@@ -0,0 +1,93 @@
+
+
+
+
+ Microsoft.PowerShell.Commands.LocalUser
+
+ Microsoft.PowerShell.Commands.LocalUser
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ Enabled
+
+
+ Description
+
+
+
+
+
+
+
+ Microsoft.PowerShell.Commands.LocalGroup
+
+ Microsoft.PowerShell.Commands.LocalGroup
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+
+
+ Description
+
+
+
+
+
+
+
+ Microsoft.PowerShell.Commands.LocalPrincipal
+
+ Microsoft.PowerShell.Commands.LocalPrincipal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ObjectClass
+
+
+ Name
+
+
+ PrincipalSource
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1 b/src/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1
new file mode 100644
index 000000000..4145c314a
Binary files /dev/null and b/src/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1 differ