diff --git a/build.psm1 b/build.psm1 index 518d65709..a82565c9a 100644 --- a/build.psm1 +++ b/build.psm1 @@ -871,14 +871,26 @@ function Start-ResGen "Microsoft.PowerShell.Commands.Utility", "Microsoft.PowerShell.ConsoleHost", "Microsoft.PowerShell.CoreCLR.Eventing", +"Microsoft.PowerShell.LocalAccounts", "Microsoft.PowerShell.Security", "System.Management.Automation") | % { $module = $_ ls "$PSScriptRoot/src/$module/resources" | % { $className = $_.Name.Replace('.resx', '') $xml = [xml](cat -raw $_.FullName) - $genSource = Get-StronglyTypeCsFileForResx -xml $xml -ModuleName $module -ClassName $className - $outPath = "$PSScriptRoot/src/windows-build/gen/$module/$className.cs" + + $fileName = $className + $namespace = '' + + $lastIndexOfDot = $className.LastIndexOf(".") + if ($lastIndexOfDot -ne -1) + { + $namespace = $className.Substring(0, $lastIndexOfDot) + $className = $className.Substring($lastIndexOfDot + 1) + } + + $genSource = Get-StronglyTypeCsFileForResx -xml $xml -ModuleName $module -ClassName $className -NamespaceName $namespace + $outPath = "$PSScriptRoot/src/windows-build/gen/$module/$fileName.cs" log "ResGen for $outPath" mkdir -ErrorAction SilentlyContinue (Split-Path $outPath) > $null Set-Content -Encoding Ascii -Path $outPath -Value $genSource @@ -975,8 +987,9 @@ function script:Start-NativeExecution([scriptblock]$sb) function script:Get-StronglyTypeCsFileForResx { - param($xml, $ModuleName, $ClassName) -$body = @' + param($xml, $ModuleName, $ClassName, $NamespaceName = '') + +$banner = @' //------------------------------------------------------------------------------ // // This code was generated by a Start-ResGen funciton from build.psm1. @@ -987,6 +1000,16 @@ $body = @' // //------------------------------------------------------------------------------ +{0} +'@ + +$namespace = @' +namespace {0} {{ +{1} +}} +'@ + +$body = @' using System; using System.Reflection; @@ -1056,7 +1079,16 @@ internal class {0} {{ $entry -f $name,$val } } | Out-String - $resultCode = $body -f $ClassName,$ModuleName,$entries + + $bodyCode = $body -f $ClassName,$ModuleName,$entries + + if ($NamespaceName -ne '') + { + $bodyCode = $namespace -f $NamespaceName, $bodyCode + } + + $resultCode = $banner -f $bodyCode + return $resultCode -replace "`r`n?|`n","`r`n" } diff --git a/src/Microsoft.PowerShell.ConsoleHost/project.json b/src/Microsoft.PowerShell.ConsoleHost/project.json index 22a12273e..b0c78a535 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/project.json +++ b/src/Microsoft.PowerShell.ConsoleHost/project.json @@ -30,7 +30,8 @@ "dependencies": { "Microsoft.PowerShell.PSReadLine": "1.0.0-*", "Microsoft.PowerShell.Commands.Management": "1.0.0-*", - "Microsoft.PowerShell.Commands.Utility": "1.0.0-*" + "Microsoft.PowerShell.Commands.Utility": "1.0.0-*", + "Microsoft.PowerShell.LocalAccounts": "1.0.0-*" }, "frameworks": { 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/map.json b/src/Microsoft.PowerShell.LocalAccounts/map.json new file mode 100644 index 000000000..1f5b5deb2 --- /dev/null +++ b/src/Microsoft.PowerShell.LocalAccounts/map.json @@ -0,0 +1,30 @@ +{ + "monad/src/LocalAccounts/Exceptions.cs": "LocalAccounts/Exceptions.cs", + "monad/src/LocalAccounts/Extensions.cs": "LocalAccounts/Extensions.cs", + "monad/src/LocalAccounts/LocalGroup.cs": "LocalAccounts/LocalGroup.cs", + "monad/src/LocalAccounts/LocalPrincipal.cs": "LocalAccounts/LocalPrincipal.cs", + "monad/src/LocalAccounts/LocalUser.cs": "LocalAccounts/LocalUser.cs", + "monad/src/LocalAccounts/Native.cs": "LocalAccounts/Native.cs", + "monad/src/LocalAccounts/NtStatus.cs": "LocalAccounts/NtStatus.cs", + "monad/src/LocalAccounts/PInvokeDllNames.cs": "LocalAccounts/PInvokeDllNames.cs", + "monad/src/LocalAccounts/Sam.cs": "LocalAccounts/Sam.cs", + "monad/src/LocalAccounts/SamApi.cs": "LocalAccounts/SamApi.cs", + "monad/src/LocalAccounts/StringUtil.cs": "LocalAccounts/StringUtil.cs", + "monad/src/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs": "LocalAccounts/Commands/AddLocalGroupMemberCommand.cs", + "monad/src/LocalAccounts/Commands/DisableLocalUserCommand.cs": "LocalAccounts/Commands/DisableLocalUserCommand.cs", + "monad/src/LocalAccounts/Commands/EnableLocalUserCommand.cs": "LocalAccounts/Commands/EnableLocalUserCommand.cs", + "monad/src/LocalAccounts/Commands/GetLocalGroupCommand.cs": "LocalAccounts/Commands/GetLocalGroupCommand.cs", + "monad/src/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs": "LocalAccounts/Commands/GetLocalGroupMemberCommand.cs", + "monad/src/LocalAccounts/Commands/GetLocalUserCommand.cs": "LocalAccounts/Commands/GetLocalUserCommand.cs", + "monad/src/LocalAccounts/Commands/NewLocalGroupCommand.cs": "LocalAccounts/Commands/NewLocalGroupCommand.cs", + "monad/src/LocalAccounts/Commands/NewLocalUserCommand.cs": "LocalAccounts/Commands/NewLocalUserCommand.cs", + "monad/src/LocalAccounts/Commands/RemoveLocalGroupCommand.cs": "LocalAccounts/Commands/RemoveLocalGroupCommand.cs", + "monad/src/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs": "LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs", + "monad/src/LocalAccounts/Commands/RemoveLocalUserCommand.cs": "LocalAccounts/Commands/RemoveLocalUserCommand.cs", + "monad/src/LocalAccounts/Commands/RenameLocalGroupCommand.cs": "LocalAccounts/Commands/RenameLocalGroupCommand.cs", + "monad/src/LocalAccounts/Commands/RenameLocalUserCommand.cs": "LocalAccounts/Commands/RenameLocalUserCommand.cs", + "monad/src/LocalAccounts/Commands/SetLocalGroupCommand.cs": "LocalAccounts/Commands/SetLocalGroupCommand.cs", + "monad/src/LocalAccounts/Commands/SetLocalUserCommand.cs": "LocalAccounts/Commands/SetLocalUserCommand.cs", + "monad/src/LocalAccounts/Properties/AssemblyInfo.cs": "LocalAccounts/Properties/AssemblyInfo.cs", + "monad/src/LocalAccounts/resources/Microsoft.PowerShell.LocalAccounts.Strings.resx": "resources/Microsoft.PowerShell.LocalAccounts.Strings.resx" +} diff --git a/src/Microsoft.PowerShell.LocalAccounts/project.json b/src/Microsoft.PowerShell.LocalAccounts/project.json new file mode 100644 index 000000000..7a1f812da --- /dev/null +++ b/src/Microsoft.PowerShell.LocalAccounts/project.json @@ -0,0 +1,28 @@ +{ + "name": "Microsoft.PowerShell.LocalAccounts", + "version": "1.0.0-*", + "authors": [ "OPS" ], + + "buildOptions": { + "warningsAsErrors": true, + "compile": [ "../windows-build/gen/Microsoft.PowerShell.LocalAccounts/*.cs" ] + }, + + "dependencies": { + "System.Management.Automation": "1.0.0-*" + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ "dnxcore50" ], + "buildOptions": { + "define": [ "CORECLR" ] + }, + "dependencies": { + "System.Net.NameResolution": "4.0.0-rc2-24103" + } + }, + "net451": { + } + } +} 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 diff --git a/src/powershell/map.json b/src/powershell/map.json index bde38359e..244c3606d 100644 --- a/src/powershell/map.json +++ b/src/powershell/map.json @@ -1,4 +1,6 @@ { "monad/miscfiles/modules/Microsoft.PowerShell.Diagnostics/CoreClr/Microsoft.PowerShell.Diagnostics.psd1": "Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1", - "monad/miscfiles/modules/Microsoft.PowerShell.Utility/CoreClr/Microsoft.PowerShell.Utility.psd1": "Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1" + "monad/miscfiles/modules/Microsoft.PowerShell.Utility/CoreClr/Microsoft.PowerShell.Utility.psd1": "Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1", + "monad/src/LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1": "Modules/Microsoft.PowerShell.LocalAccounts/Microsoft.PowerShell.LocalAccounts.psd1", + "monad/src/LocalAccounts/LocalAccounts.format.ps1xml": "Modules/Microsoft.PowerShell.LocalAccounts/LocalAccounts.format.ps1xml" } diff --git a/src/windows-build b/src/windows-build index 4011f04c0..9598137e1 160000 --- a/src/windows-build +++ b/src/windows-build @@ -1 +1 @@ -Subproject commit 4011f04c040c88441dd93ec53191e5540832ee21 +Subproject commit 9598137e1ec976c23328880534a8f49bdce32cf0