diff --git a/assets/files.wxs b/assets/files.wxs index 9e40745ec69..669c1a8f7f4 100644 --- a/assets/files.wxs +++ b/assets/files.wxs @@ -1964,6 +1964,11 @@ + + + + + @@ -3074,6 +3079,15 @@ + + + + + + + + + @@ -4064,6 +4078,12 @@ + + + + + + diff --git a/src/Microsoft.PowerShell.AccountManagement/Microsoft.PowerShell.AccountManagement.csproj b/src/Microsoft.PowerShell.AccountManagement/Microsoft.PowerShell.AccountManagement.csproj new file mode 100644 index 00000000000..a672921a3d5 --- /dev/null +++ b/src/Microsoft.PowerShell.AccountManagement/Microsoft.PowerShell.AccountManagement.csproj @@ -0,0 +1,21 @@ + + + + PowerShell's Microsoft.PowerShell.AccountManagement project + $(NoWarn);CS1570 + Microsoft.PowerShell.AccountManagement + + + + + + + + $(DefineConstants);CORECLR + + + + + + + diff --git a/src/Microsoft.PowerShell.AccountManagement/commands/GetUserCommand.cs b/src/Microsoft.PowerShell.AccountManagement/commands/GetUserCommand.cs new file mode 100644 index 00000000000..978ad1102a8 --- /dev/null +++ b/src/Microsoft.PowerShell.AccountManagement/commands/GetUserCommand.cs @@ -0,0 +1,383 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.DirectoryServices.AccountManagement; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// The New-User cmdlet creates a new local user account. + /// + [Cmdlet(VerbsCommon.Get, "User", + HelpUri = "")] + public class GetUserCommand : PSCmdlet + { + /// + /// Gets or sets the type of store to which the account belongs. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public ContextType AccountStore + { + get; + set; + } = ContextType.Domain; + + /// + /// Gets or sets options that are used for binding to the Server (domain controller or local machine). + /// + [Parameter] + public ContextOptions ContextOptions + { + get; + set; + } + + /// + /// Gets or sets an user credential that are used for binding to the Server (domain controller or local machine). + /// + [Parameter] + public PSCredential Credential + { + get; + set; + } + + /// + /// Gets or sets a value that specifies whether the account may be delegated. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter DelegationPermitted + { + get; + set; + } + + /// + /// Gets or sets a descriptive comment for this user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Description + { + get; + set; + } + + /// + /// Gets or sets a switch to specify whether this user account is enabled or disabled. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Enabled + { + get; + set; + } + + /// + /// Gets or sets a display name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string DisplayName + { + get; + set; + } + + /// + /// Gets or sets a E-mail address of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string EmailAddress + { + get; + set; + } + + /// + /// Gets or sets a employee ID of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string EmployeeId + { + get; + set; + } + + /// + /// Gets or sets a given name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string GivenName + { + get; + set; + } + + /// + /// Gets or sets a Home directory of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string HomeDirectory + { + get; + set; + } + + /// + /// Gets or sets a Home drive of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string HomeDrive + { + get; + set; + } + + /// + /// Gets or sets a middle name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string MiddleName + { + get; + set; + } + + /// + /// Gets or sets a user name for the user account. + /// + [Parameter( + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [ValidateLength(1, 20)] + public string Name + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the new User account has no password. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter PasswordNotRequired + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the password will not expire. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter PasswordNeverExpires + { + get; + set; + } + + /// + /// Gets or sets a container on the store to use as the root of the context. + /// All queries are performed under this root, and all inserts are performed into this container. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Path + { + get; + set; + } + + /// + /// Gets or sets a SAM account name for the user account. + /// + [Parameter( + Position = 0, + ValueFromPipelineByPropertyName = true)] + public string SamAccountName + { + get; + set; + } + + /// + /// Gets or sets a server/machine or domain controller where the user account will be searched from. + /// + [Parameter] + public string Server + { + get; + set; + } + + /// + /// Gets or sets a surname of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string SurName + { + get; + set; + } + + /// + /// Gets or sets a switch to specify whether the user is allowed to change the password on the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter UserCannotChangePassword + { + get; + set; + } + + /// + /// Gets or sets a user principal name (UPN) associated with the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string UserPrincipalName + { + get; + set; + } + + /// + /// ProcessRecord method. + /// + protected override void ProcessRecord() + { + try + { + var bindingUserName = Credential?.UserName; + var bindingUserPassword = Credential == null ? null : new System.Net.NetworkCredential(string.Empty, Credential.Password).Password; + var options = this.MyInvocation.BoundParameters.ContainsKey(nameof(ContextOptions)) ? ContextOptions : DefaultContextOptions.GetDefaultOptionForStore(AccountStore); + using var principalContext = new PrincipalContext(contextType: AccountStore, name: Server, container: Path, options, bindingUserName, bindingUserPassword); + UserPrincipal user; + + if (AccountStore == ContextType.Machine) + { + user = new UserPrincipal(principalContext); + SetMachineUserPrincipalProperties(user); + } + else + { + user = new UserPrincipal(principalContext); + SetADUserPrincipalProperties(user); + } + + PrincipalSearcher ps = new PrincipalSearcher(user); + + PrincipalSearchResult results = ps.FindAll(); + + foreach (var principal in results) + { + WriteObject(principal); + } + } + catch (Exception ex) + { + WriteError( + new ErrorRecord( + ex, + "InvalidValue", + ErrorCategory.InvalidOperation, + null)); + } + } + + private void SetMachineUserPrincipalProperties(UserPrincipal userPrincipal) + { + foreach (var parameter in this.MyInvocation.BoundParameters) + { + switch (parameter.Key) + { + case "Name": + userPrincipal.Name = Name; + break; + case "Description": + userPrincipal.Description = Description; + break; + case "DisplayName": + userPrincipal.DisplayName = DisplayName; + break; + case "Enabled": + userPrincipal.Enabled = Enabled; + break; + case "PasswordNotRequired": + userPrincipal.PasswordNotRequired = PasswordNotRequired; + break; + case "PasswordNeverExpires": + userPrincipal.PasswordNeverExpires = PasswordNeverExpires; + break; + case "SamAccountName": + userPrincipal.SamAccountName = SamAccountName; + break; + case "UserCannotChangePassword": + userPrincipal.UserCannotChangePassword = UserCannotChangePassword; + break; + } + } + } + + private void SetADUserPrincipalProperties(UserPrincipal userPrincipal) + { + foreach (var parameter in this.MyInvocation.BoundParameters) + { + switch (parameter.Key) + { + case "Name": + userPrincipal.Name = Name; + break; + case "DelegationPermitted": + userPrincipal.DelegationPermitted = DelegationPermitted; + break; + case "Description": + userPrincipal.Description = Description; + break; + case "DisplayName": + userPrincipal.DisplayName = DisplayName; + break; + case "Enabled": + userPrincipal.Enabled = Enabled; + break; + case "EmployeeId": + userPrincipal.EmployeeId = EmployeeId; + break; + case "EmailAddress": + userPrincipal.EmailAddress = EmailAddress; + break; + case "GivenName": + userPrincipal.GivenName = GivenName; + break; + case "HomeDirectory": + userPrincipal.HomeDirectory = HomeDirectory; + break; + case "HomeDrive": + userPrincipal.HomeDrive = HomeDrive; + break; + case "MiddleName": + userPrincipal.MiddleName = MiddleName; + break; + case "PasswordNotRequired": + userPrincipal.PasswordNotRequired = PasswordNotRequired; + break; + case "PasswordNeverExpires": + userPrincipal.PasswordNeverExpires = PasswordNeverExpires; + break; + case "SamAccountName": + userPrincipal.SamAccountName = SamAccountName; + break; + case "SurName": + userPrincipal.Surname = SurName; + break; + case "UserCannotChangePassword": + userPrincipal.UserCannotChangePassword = UserCannotChangePassword; + break; + case "UserPrincipalName": + userPrincipal.UserPrincipalName = UserPrincipalName; + break; + } + } + } + } +} diff --git a/src/Microsoft.PowerShell.AccountManagement/commands/NewUserCommand.cs b/src/Microsoft.PowerShell.AccountManagement/commands/NewUserCommand.cs new file mode 100644 index 00000000000..779c086f330 --- /dev/null +++ b/src/Microsoft.PowerShell.AccountManagement/commands/NewUserCommand.cs @@ -0,0 +1,462 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.DirectoryServices.AccountManagement; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// The New-User cmdlet creates a new local user account. + /// + [Cmdlet(VerbsCommon.New, "User", + DefaultParameterSetName = "Password", + SupportsShouldProcess = true, + HelpUri = "")] + public class NewUserCommand : PSCmdlet + { + /// + /// Gets or sets a DateTime that specifies the date and time that the account expires. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public DateTime AccountExpires + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the account will not expire. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AccountNeverExpires + { + get; + set; + } + + /// + /// Gets or sets the type of store to which the account belongs. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public ContextType AccountStore + { + get; + set; + } = ContextType.Domain; + + /// + /// Gets or sets options that are used for binding to the Server (domain controller or local machine). + /// + [Parameter] + public ContextOptions ContextOptions + { + get; + set; + } + + /// + /// Gets or sets an user credential that are used for binding to the Server (domain controller or local machine). + /// + [Parameter] + public PSCredential Credential + { + get; + set; + } + + /// + /// Gets or sets a value that specifies whether the account may be delegated. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public SwitchParameter DelegationPermitted + { + get; + set; + } + + /// + /// Gets or sets a descriptive comment for this user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string Description + { + get; + set; + } + + /// + /// Gets or sets a switch to specify whether this user account is enabled or disabled. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Enabled + { + get; + set; + } + + /// + /// Gets or sets a display name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string DisplayName + { + get; + set; + } + + /// + /// Gets or sets a E-mail address of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string EmailAddress + { + get; + set; + } + + /// + /// Gets or sets a employee ID of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string EmployeeId + { + get; + set; + } + + /// + /// Gets or sets a given name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string GivenName + { + get; + set; + } + + /// + /// Gets or sets a Home directory of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string HomeDirectory + { + get; + set; + } + + /// + /// Gets or sets a Home drive of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string HomeDrive + { + get; + set; + } + + /// + /// Gets or sets a middle name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string MiddleName + { + get; + set; + } + + /// + /// Gets or sets a user name for the user account. + /// + [Parameter(Mandatory = true, + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [ValidateLength(1, 20)] + public string Name + { + get; + set; + } + + /// + /// Gets or sets a password for the user account. A password can contain up to 127 characters. + /// Ex.: new-User -Name qw -Password $((new-object Net.NetworkCredential("", "Qwerty")).SecurePassword). + /// + [Parameter(Mandatory = true, + ParameterSetName = "Password", + ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public System.Security.SecureString Password + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the new User account has no password. + /// + [Parameter(Mandatory = true, + ParameterSetName = "NoPassword", + ValueFromPipelineByPropertyName = true)] + public SwitchParameter PasswordNotRequired + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the password will not expire. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter PasswordNeverExpires + { + get; + set; + } + + /// + /// Gets or sets a container on the store to use as the root of the context. + /// All queries are performed under this root, and all inserts are performed into this container. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Path + { + get; + set; + } + + /// + /// Gets or sets a SAM account name for the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string SamAccountName + { + get; + set; + } + + /// + /// Gets or sets a server/machine or domain controller where the user account will be created. + /// + [Parameter] + public string Server + { + get; + set; + } + + /// + /// Gets or sets a surname of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string SurName + { + get; + set; + } + + /// + /// Gets or sets a switch to specify whether the user is allowed to change the password on the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter UserCannotChangePassword + { + get; + set; + } + + /// + /// Gets or sets a user principal name (UPN) associated with the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string UserPrincipalName + { + get; + set; + } + + /// + /// ProcessRecord method. + /// + protected override void ProcessRecord() + { + try + { + if (ShouldProcess(Name, NewUserStrings.ActionNewUser)) + { + var bindingUserName = Credential?.UserName; + var bindingUserPassword = Credential == null ? null : new System.Net.NetworkCredential(string.Empty, Credential.Password).Password; + var options = this.MyInvocation.BoundParameters.ContainsKey(nameof(ContextOptions)) ? ContextOptions : DefaultContextOptions.GetDefaultOptionForStore(AccountStore); + using var principalContext = new PrincipalContext(contextType: AccountStore, name: Server, container: Path, options, bindingUserName, bindingUserPassword); + UserPrincipal user; + + if (AccountStore == ContextType.Machine) + { + user = new UserPrincipal(principalContext); + SetMachineUserPrincipalProperties(user); + } + else + { + user = new UserPrincipal(principalContext); + SetADUserPrincipalProperties(user); + } + + if (this.MyInvocation.BoundParameters.ContainsKey(nameof(SamAccountName))) + { + user.SamAccountName = SamAccountName; + } + else + { + // Name is mandatory and always presents. + user.SamAccountName = Name; + } + + if (PasswordNotRequired.IsPresent) + { + user.Enabled = false; + user.PasswordNeverExpires = true; + user.PasswordNotRequired = true; + } + else + { + user.SetPassword(new System.Net.NetworkCredential(string.Empty, Password).Password); + } + + user.Save(); + + WriteObject(user); + } + } + catch (Exception ex) + { + WriteError( + new ErrorRecord( + ex, + "InvalidValue", + ErrorCategory.InvalidOperation, + null)); + } + } + + private void SetMachineUserPrincipalProperties(UserPrincipal userPrincipal) + { + foreach (var parameter in this.MyInvocation.BoundParameters) + { + switch (parameter.Key) + { + case "AccountExpirationDate": + userPrincipal.AccountExpirationDate = !AccountNeverExpires.IsPresent ? AccountExpires : (DateTime?)null; + break; + case "Name": + userPrincipal.Name = Name; + break; + case "Description": + userPrincipal.Description = Description; + break; + case "DisplayName": + userPrincipal.DisplayName = DisplayName; + break; + case "Enabled": + userPrincipal.Enabled = Enabled; + break; + case "PasswordNeverExpires": + userPrincipal.PasswordNeverExpires = PasswordNeverExpires; + break; + case "UserCannotChangePassword": + userPrincipal.UserCannotChangePassword = UserCannotChangePassword; + break; + } + } + } + + private void SetADUserPrincipalProperties(UserPrincipal userPrincipal) + { + foreach (var parameter in this.MyInvocation.BoundParameters) + { + switch (parameter.Key) + { + case "AccountExpirationDate": + userPrincipal.AccountExpirationDate = !AccountNeverExpires.IsPresent ? AccountExpires : (DateTime?)null; + break; + case "Name": + userPrincipal.Name = Name; + break; + case "DelegationPermitted": + userPrincipal.DelegationPermitted = DelegationPermitted; + break; + case "Description": + userPrincipal.Description = Description; + break; + case "DisplayName": + userPrincipal.DisplayName = DisplayName; + break; + case "Enabled": + userPrincipal.Enabled = Enabled; + break; + case "EmployeeId": + userPrincipal.EmployeeId = EmployeeId; + break; + case "EmailAddress": + userPrincipal.EmailAddress = EmailAddress; + break; + case "GivenName": + userPrincipal.GivenName = GivenName; + break; + case "HomeDirectory": + userPrincipal.HomeDirectory = HomeDirectory; + break; + case "HomeDrive": + userPrincipal.HomeDrive = HomeDrive; + break; + case "MiddleName": + userPrincipal.MiddleName = MiddleName; + break; + case "PasswordNeverExpires": + userPrincipal.PasswordNeverExpires = PasswordNeverExpires; + break; + case "UserCannotChangePassword": + userPrincipal.UserCannotChangePassword = UserCannotChangePassword; + break; + case "SurName": + userPrincipal.Surname = SurName; + break; + case "UserPrincipalName": + userPrincipal.UserPrincipalName = UserPrincipalName; + break; + } + } + } + } + + internal static class DefaultContextOptions + { + internal static readonly ContextOptions MachineDefaultContextOption = ContextOptions.Negotiate; + internal static readonly ContextOptions ADDefaultContextOption = ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing; + + internal static ContextOptions GetDefaultOptionForStore(ContextType storeType) + { + if (storeType == ContextType.Machine) + { + return DefaultContextOptions.MachineDefaultContextOption; + } + else + { + return DefaultContextOptions.ADDefaultContextOption; + } + } + } +} diff --git a/src/Microsoft.PowerShell.AccountManagement/commands/RemoveUserCommand.cs b/src/Microsoft.PowerShell.AccountManagement/commands/RemoveUserCommand.cs new file mode 100644 index 00000000000..0737d44ee8d --- /dev/null +++ b/src/Microsoft.PowerShell.AccountManagement/commands/RemoveUserCommand.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.DirectoryServices.AccountManagement; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// The Remove-User cmdlet creates a new local user account. + /// + [Cmdlet(VerbsCommon.Remove, "User", + SupportsShouldProcess = true, + HelpUri = "")] + public class RemoveUserCommand : PSCmdlet + { + /// + /// Gets or sets an user identity to remove. + /// + [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true)] + public UserPrincipal Identity + { + get; + set; + } + + /// + /// ProcessRecord method. + /// + protected override void ProcessRecord() + { + try + { + if (ShouldProcess(Identity.ToString(), NewUserStrings.ActionNewUser)) + { + Identity.Delete(); + } + } + catch (UnauthorizedAccessException exc) + { + WriteError(new ErrorRecord(exc, "RemoveUserFailure", ErrorCategory.PermissionDenied, Identity)); + } + catch (InvalidOperationException exc) + { + WriteError(new ErrorRecord(exc, "UserAlreadyRemoved", ErrorCategory.InvalidOperation, Identity)); + } + } + } +} diff --git a/src/Microsoft.PowerShell.AccountManagement/commands/SearchAccountCommand.cs b/src/Microsoft.PowerShell.AccountManagement/commands/SearchAccountCommand.cs new file mode 100644 index 00000000000..fb1c9cc967b --- /dev/null +++ b/src/Microsoft.PowerShell.AccountManagement/commands/SearchAccountCommand.cs @@ -0,0 +1,481 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.DirectoryServices.AccountManagement; +using System.Management.Automation; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// The Search-Account cmdlet creates a new local user account. + /// + [Cmdlet(VerbsCommon.Search, "Account", + HelpUri = "")] + public class SearchAccountCommand : PSCmdlet + { + /// + /// Gets or sets the type of store to which the account belongs. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public ContextType AccountStore + { + get; + set; + } = ContextType.Domain; + + /// + /// Gets or sets options that are used for binding to the Server (domain controller or local machine). + /// + [Parameter] + public ContextOptions ContextOptions + { + get; + set; + } + + /// + /// Gets or sets an user credential that are used for binding to the Server (domain controller or local machine). + /// + [Parameter] + public PSCredential Credential + { + get; + set; + } + + /// + /// Gets or sets an user identity to remove. + /// + [Parameter] + public SwitchParameter ComputersOnly + { + get; + set; + } + + /// + /// Gets or sets a value that specifies whether the account may be delegated. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter DelegationPermitted + { + get; + set; + } + + /// + /// Gets or sets a descriptive comment for this user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Description + { + get; + set; + } + + /// + /// Gets or sets a switch to specify whether this user account is enabled or disabled. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Enabled + { + get; + set; + } + + /// + /// Gets or sets a display name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string DisplayName + { + get; + set; + } + + /// + /// Gets or sets a E-mail address of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string EmailAddress + { + get; + set; + } + + /// + /// Gets or sets a employee ID of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string EmployeeId + { + get; + set; + } + + /// + /// Gets or sets a given name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string GivenName + { + get; + set; + } + + /// + /// Gets or sets a Home directory of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string HomeDirectory + { + get; + set; + } + + /// + /// Gets or sets a Home drive of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string HomeDrive + { + get; + set; + } + + /// + /// Gets or sets a middle name of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string MiddleName + { + get; + set; + } + + /// + /// Gets or sets a user name for the user account. + /// + [Parameter( + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateNotNullOrEmpty] + [ValidateLength(1, 20)] + public string Name + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the new User account has no password. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter PasswordNotRequired + { + get; + set; + } + + /// + /// Gets or sets a switch to specify that the password will not expire. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter PasswordNeverExpires + { + get; + set; + } + + /// + /// Gets or sets a container on the store to use as the root of the context. + /// All queries are performed under this root, and all inserts are performed into this container. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Path + { + get; + set; + } + + /// + /// Gets or sets a SAM account name for the user account. + /// + [Parameter( + Position = 0, + ValueFromPipelineByPropertyName = true)] + public string SamAccountName + { + get; + set; + } + + /// + /// Gets or sets a server/machine or domain controller where the user account will be searched from. + /// + [Parameter] + public string Server + { + get; + set; + } + + /// + /// Gets or sets a surname of the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string SurName + { + get; + set; + } + + /// + /// Gets or sets a switch to specify whether the user is allowed to change the password on the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter UserCannotChangePassword + { + get; + set; + } + + /// + /// Gets or sets a user principal name (UPN) associated with the user account. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string UserPrincipalName + { + get; + set; + } + + /// + /// Gets or sets a switch to search expired user accounts. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AccountExpired + { + get; + set; + } + + /// + /// ProcessRecord method. + /// + protected override void ProcessRecord() + { + try + { + var bindingUserName = Credential?.UserName; + var bindingUserPassword = Credential == null ? null : new System.Net.NetworkCredential(string.Empty, Credential.Password).Password; + var options = this.MyInvocation.BoundParameters.ContainsKey(nameof(ContextOptions)) ? ContextOptions : DefaultContextOptions.GetDefaultOptionForStore(AccountStore); + using var principalContext = new PrincipalContext(contextType: AccountStore, name: Server, container: Path, options, bindingUserName, bindingUserPassword); + UserPrincipal user = new UserPrincipal(principalContext); + + if (AccountStore == ContextType.Machine) + { + user = new UserPrincipal(principalContext); + SetMachineUserPrincipalProperties(user); + + if (AccountExpired.IsPresent) + { + user.AdvancedSearchFilter.AccountExpirationDate(DateTime.Now, MatchType.LessThan); + } + } + else + { + var psuser = new PowerShellUserPrincipal(principalContext); + SetADUserPrincipalProperties(psuser); + + if (AccountExpired.IsPresent) + { + psuser.AdvancedSearchFilter.AccountExpired(); + } + + user = psuser; + } + + PrincipalSearcher ps = new PrincipalSearcher(user); + + PrincipalSearchResult results = ps.FindAll(); + + foreach (var principal in results) + { + WriteObject(principal); + } + } + catch (Exception ex) + { + WriteError( + new ErrorRecord( + ex, + "InvalidValue", + ErrorCategory.InvalidOperation, + null)); + } + } + + private void SetMachineUserPrincipalProperties(UserPrincipal userPrincipal) + { + foreach (var parameter in this.MyInvocation.BoundParameters) + { + switch (parameter.Key) + { + case "Name": + userPrincipal.Name = Name; + break; + case "Description": + userPrincipal.Description = Description; + break; + case "DisplayName": + userPrincipal.DisplayName = DisplayName; + break; + case "Enabled": + userPrincipal.Enabled = Enabled; + break; + case "PasswordNotRequired": + userPrincipal.PasswordNotRequired = PasswordNotRequired; + break; + case "PasswordNeverExpires": + userPrincipal.PasswordNeverExpires = PasswordNeverExpires; + break; + case "SamAccountName": + userPrincipal.SamAccountName = SamAccountName; + break; + case "UserCannotChangePassword": + userPrincipal.UserCannotChangePassword = UserCannotChangePassword; + break; + } + } + } + + private void SetADUserPrincipalProperties(UserPrincipal userPrincipal) + { + foreach (var parameter in this.MyInvocation.BoundParameters) + { + switch (parameter.Key) + { + case "Name": + userPrincipal.Name = Name; + break; + case "DelegationPermitted": + userPrincipal.DelegationPermitted = DelegationPermitted; + break; + case "Description": + userPrincipal.Description = Description; + break; + case "DisplayName": + userPrincipal.DisplayName = DisplayName; + break; + case "Enabled": + userPrincipal.Enabled = Enabled; + break; + case "EmployeeId": + userPrincipal.EmployeeId = EmployeeId; + break; + case "EmailAddress": + userPrincipal.EmailAddress = EmailAddress; + break; + case "GivenName": + userPrincipal.GivenName = GivenName; + break; + case "HomeDirectory": + userPrincipal.HomeDirectory = HomeDirectory; + break; + case "HomeDrive": + userPrincipal.HomeDrive = HomeDrive; + break; + case "MiddleName": + userPrincipal.MiddleName = MiddleName; + break; + case "PasswordNotRequired": + userPrincipal.PasswordNotRequired = PasswordNotRequired; + break; + case "PasswordNeverExpires": + userPrincipal.PasswordNeverExpires = PasswordNeverExpires; + break; + case "SamAccountName": + userPrincipal.SamAccountName = SamAccountName; + break; + case "SurName": + userPrincipal.Surname = SurName; + break; + case "UserCannotChangePassword": + userPrincipal.UserCannotChangePassword = UserCannotChangePassword; + break; + case "UserPrincipalName": + userPrincipal.UserPrincipalName = UserPrincipalName; + break; + } + } + } + } + + /// + /// + /// + public class PowerShellPrincipalSearchFilter : AdvancedFilters + { + /// + /// + /// + public PowerShellPrincipalSearchFilter(Principal p) : base(p) + { + } + + /// + /// + /// + public void AccountExpired() + { + this.AdvancedFilterSet("accountExpires", DateTimeToADString(DateTime.Now), typeof(int), MatchType.LessThan); + //this.AdvancedFilterSet("accountExpires", 0, typeof(int), MatchType.Equals); + } + + internal static string DateTimeToADString(DateTime dateTime) + { + // DateTime --> FILETIME --> stringized FILETIME + + long fileTime = dateTime.ToFileTimeUtc(); + + return fileTime.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + } + + /// + /// + /// + [DirectoryObjectClass("user")] + [DirectoryRdnPrefix("CN")] + public class PowerShellUserPrincipal : UserPrincipal + { + /// + /// + /// + public PowerShellUserPrincipal(PrincipalContext context) + : base(context) + { } + + /// + /// + /// + public PowerShellUserPrincipal(PrincipalContext context, + string samAccountName, + string password, + bool enabled) + : base(context, samAccountName, password, enabled) + { } + + PowerShellPrincipalSearchFilter _searchFilter; + + /// + /// + /// + new public PowerShellPrincipalSearchFilter AdvancedSearchFilter + { + get { return _searchFilter ?? (_searchFilter = new PowerShellPrincipalSearchFilter(this)); } + } + } +} diff --git a/src/Microsoft.PowerShell.AccountManagement/resources/NewUserStrings.resx b/src/Microsoft.PowerShell.AccountManagement/resources/NewUserStrings.resx new file mode 100644 index 00000000000..0c6fa1de8f7 --- /dev/null +++ b/src/Microsoft.PowerShell.AccountManagement/resources/NewUserStrings.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. + + diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 3d1e7d3e92e..9ff53066ef8 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Modules/Windows/Microsoft.PowerShell.AccountManagement/Microsoft.PowerShell.AccountManagement.psd1 b/src/Modules/Windows/Microsoft.PowerShell.AccountManagement/Microsoft.PowerShell.AccountManagement.psd1 new file mode 100644 index 00000000000..6bcfaa6d546 --- /dev/null +++ b/src/Modules/Windows/Microsoft.PowerShell.AccountManagement/Microsoft.PowerShell.AccountManagement.psd1 @@ -0,0 +1,14 @@ +@{ +GUID="149658ad-65f1-4949-a657-1eebcce72656" +Author="PowerShell" +CompanyName="Microsoft Corporation" +Copyright="Copyright (c) Microsoft Corporation. All rights reserved." +ModuleVersion="1.0.0.0" +CompatiblePSEditions = @("Core") +PowerShellVersion="6.0" +FunctionsToExport = @() +CmdletsToExport= "Get-User", "Remove-User", "New-User", "Search-Account" +AliasesToExport = @() +NestedModules="Microsoft.PowerShell.AccountManagement.dll" +HelpInfoURI = '' +} diff --git a/src/System.Management.Automation/engine/ParameterBinderBase.cs b/src/System.Management.Automation/engine/ParameterBinderBase.cs index 8edc637b3cf..9f3dbd2cf0e 100644 --- a/src/System.Management.Automation/engine/ParameterBinderBase.cs +++ b/src/System.Management.Automation/engine/ParameterBinderBase.cs @@ -1267,9 +1267,20 @@ private object CoerceTypeAsNeeded( } } + string str = "null"; + try + { + str = (result == null) ? str : result.ToString(); + } + catch + { + // Sometimes ToString() can throw. + // This is not a reason to break the binding. + } + bindingTracer.WriteLine( "CONVERT SUCCESSFUL using LanguagePrimitives.ConvertTo: [{0}]", - (result == null) ? "null" : result.ToString()); + str); } while (false); } catch (NotSupportedException notSupported) diff --git a/test/powershell/Modules/Microsoft.PowerShell.AccountManagement/AccountManagement.User.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.AccountManagement/AccountManagement.User.Tests.ps1 new file mode 100644 index 00000000000..4548fe43e56 --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.AccountManagement/AccountManagement.User.Tests.ps1 @@ -0,0 +1,184 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +try { + #skip all tests on non-windows platform + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = !$IsWindows + + Describe "Validate AccountManagement user cmdlets" -Tags @('CI', 'RequireAdminOnWindows') { + + BeforeAll { + $Password = ([char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9 | Sort-Object {Get-Random})[0..12] -join '' + $pwd = (New-Object -TypeName Net.NetworkCredential("", $Password)).SecurePassword + } + + Context "Validate New-User" { + + AfterEach { + if ($IsWindows) { + $null = net user TestUserNew /delete *>1 + } + } + + It "Can create New-User with only name" { + $result = New-User -Name TestUserNew -PasswordNotRequired -AccountStore Machine + + $result.Name | Should -BeExactly TestUserNew + $result.Description | Should -BeNullOrEmpty + + # New account is disabled by default + $result.Enabled | Should -BeFalse + $result.SID | Should -Not -BeNullOrEmpty + } + + It "Can create New-User with password" { + $result = New-User -Name TestUserNew -AccountStore Machine -Password $pwd + + $result.Name | Should -BeExactly TestUserNew + } + + It "Can create New-User with explicit properties in local machine" { + $result = New-User -AccountStore Machine ` + -Name TestUserNew ` + -PasswordNotRequired ` + -AccountNeverExpires ` + -Description "desc" ` + -DisplayName "disp" ` + -SamAccountName "samq2" ` + -UserCannotChangePassword ` + -PasswordNeverExpires ` + -Enabled + + # For AccountStore == Machine if we set SamAccountName we get Name the same. + $result.Name | Should -BeExactly "samq2" + $result.PasswordNotRequired | Should -BeTrue + $result.AccountExpirationDate | Should -Be $null + $result.Description | Should -BeExactly "desc" + $result.DisplayName | Should -BeExactly "disp" + $result.SamAccountName | Should -BeExactly "samq2" + $result.UserCannotChangePassword | Should -BeTrue + $result.PasswordNeverExpires | Should -BeTrue + # Account is disable because PasswordNotRequired is set. + $result.Enabled | Should -BeFalse + } + + It "Can create New-User with explicit properties in a domain" -Pending:$true { + # The test works only with ActiveDirectory so skip it. + $result = New-User -AccountStore Machine ` + -Name TestUserNew ` + -PasswordNotRequired ` + -AccountNeverExpires ` + -AccountStore Domain ` + -DelegationPermitted ` + -Description "desc" ` + -DisplayName "disp" ` + -EmailAddress "email@domain.com" ` + -EmployeeId "empl" ` + -GivenName "gn" ` + -HomeDirectory "hd" ` + -HomeDrive "hdrv" ` + -MiddleName "mn" ` + -SamAccountName "samq2" ` + -SurName "sn" ` + -UserCannotChangePassword ` + -PasswordNeverExpires ` + -Enabled + + $result.Name | Should -BeExactly TestUserNew + $result.PasswordNotRequired | Should -BeTrue + $result.AccountExpirationDate | Should -Be $null + $result.DelegationPermitted | Should -BeTrue + $result.Description | Should -BeExactly "desc" + $result.DisplayName | Should -BeExactly "disp" + $result.EmailAddress | Should -BeExactly "email@domain.com" + $result.EmployeeId | Should -BeExactly "empl" + $result.GivenName | Should -BeExactly "gn" + $result.HomeDirectory | Should -BeExactly "hd" + $result.HomeDrive | Should -BeExactly "hdrv" + $result.MiddleName | Should -BeExactly "mn" + $result.SamAccountName | Should -BeExactly "samq2" + $result.SurName | Should -BeExactly "sn" + $result.UserCannotChangePassword | Should -BeTrue + $result.PasswordNeverExpires | Should -BeTrue + $result.Enabled | Should -BeTrue + } + + It "Errors on Name argument of empty string, null, spaces" { + { New-User -Name "" -PasswordNotRequired -ErrorAction Stop } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewUserCommand" + { New-User -Name $null -PasswordNotRequired -ErrorAction Stop } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.NewUserCommand" + { New-User -Name " " -PasswordNotRequired -ErrorAction Stop } | Should -Throw -ErrorId "InvalidValue,Microsoft.PowerShell.Commands.NewUserCommand" + } + + It "Error on user exists" { + $result = New-User -Name TestUserNew -PasswordNotRequired -AccountStore Machine + $exc = { New-User -Name TestUserNew -PasswordNotRequired -AccountStore Machine -ErrorAction Stop } | Should -PassThru -Throw -ErrorId "InvalidValue,Microsoft.PowerShell.Commands.NewUserCommand" + $exc.Exception | Should -BeOfType "System.DirectoryServices.AccountManagement.PrincipalExistsException" + } + } + + Context "Validate Remove-User" { + AfterAll { + if ($IsWindows) { + $null = net user TestUserNew /delete *>1 + } + } + + BeforeEach { + if ($IsWindows) { + $userIdentity = New-User -Name TestUserNew -AccountStore Machine -Password $pwd + } + } + + It "Can remove user account with pipeline" { + { $userIdentity | Remove-User -ErrorAction Stop } | Should -Not -Throw + $err = net user TestUserNew *>&1 | Out-String + $err | Should -BeLike "The user name could not be found*" + { $userIdentity | Remove-User -ErrorAction Stop } | Should -Throw -ErrorId "UserAlreadyRemoved,Microsoft.PowerShell.Commands.RemoveUserCommand" + } + + It "Can remove user account with parameter" { + { Remove-User -Identity $userIdentity -ErrorAction Stop } | Should -Not -Throw + $err = net user TestUserNew *>&1 | Out-String + $err | Should -BeLike "The user name could not be found*" + { Remove-User -Identity $userIdentity -ErrorAction Stop } | Should -Throw -ErrorId "UserAlreadyRemoved,Microsoft.PowerShell.Commands.RemoveUserCommand" + } + } + + Context "Validate Get-User" { + BeforeAll { + if ($IsWindows) { + $users = Get-User -AccountStore Machine + $userPrincipal = $users[0] + } + } + + It "Can get user accounts" { + $users.Count | Should -BeGreaterThan 0 + $users[0].Name | Should -Not -BeNullOrEmpty + $users[0].Sid | Should -Not -BeNullOrEmpty + $users[0].SamAccountName | Should -Not -BeNullOrEmpty + } + + It "Can get user account with a parameter: " -TestCases: @( + @{ paramName = "Name"; argument = @{ Name = $userPrincipal.Name } } + @{ paramName = "SamAccountName"; argument = @{ SamAccountName = $userPrincipal.SamAccountName } } + ){ + param ($paramName, $argument) + + $users.Count | Should -BeGreaterThan 0 + + $u = Get-User @argument -AccountStore Machine + + $u.Name | Should -BeExactly $userPrincipal.Name + $u.Sid | Should -BeExactly $userPrincipal.Sid + $u.SamAccountName | Should -BeExactly $userPrincipal.SamAccountName + $u.Description | Should -BeExactly $userPrincipal.Description + $u.DisplayName | Should -BeExactly $userPrincipal.DisplayName + } + } + } +} +finally { + $global:PSDefaultParameterValues = $originalDefaultParameterValues +}