diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8381064..5d0f68eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Updated build to use `Sampler.GitHubTasks` - Fixes [Issue #711](https://github.com/dsccommunity/xPSDesiredStateConfiguration/issues/711). - Added support for publishing code coverage to `CodeCov.io` and Azure Pipelines - Fixes [Issue #711](https://github.com/dsccommunity/xPSDesiredStateConfiguration/issues/711). +- Added NewName property to xUser. ## [9.1.0] - 2020-02-19 diff --git a/README.md b/README.md index 6a9f90e6e..59d74294d 100644 --- a/README.md +++ b/README.md @@ -886,6 +886,8 @@ None * Default Value: Present * **[String] FullName** _(Write)_: Represents a string with the full name you want to use for the user account. +* **[String] NewName** _(Write)_: Represents a string with the new name you + want to use for the user account. When setting this property, the UserName property must be set appropriately to uniquely identify the user both before and after setting the SamAccountName, e.g. by SID. * **[PSCredential] Password** _(Write)_: Indicates the password you want to use for this account. * **[Boolean] PasswordChangeNotAllowed** _(Write)_: Indicates if the user can diff --git a/source/DSCResources/DSC_xUserResource/DSC_xUserResource.psm1 b/source/DSCResources/DSC_xUserResource/DSC_xUserResource.psm1 index efd8962e5..1552c928a 100644 --- a/source/DSCResources/DSC_xUserResource/DSC_xUserResource.psm1 +++ b/source/DSCResources/DSC_xUserResource/DSC_xUserResource.psm1 @@ -67,6 +67,9 @@ function Get-TargetResource The (optional) full name or display name of the user. If not provided this value will remain blank. + .PARAMETER NewName + Specifies the new name of the user. + .PARAMETER Description Optional description for the user. @@ -113,6 +116,10 @@ function Set-TargetResource [System.String] $FullName, + [Parameter()] + [System.String] + $NewName, + [Parameter()] [System.String] $Description, @@ -165,6 +172,9 @@ function Set-TargetResource The full name/display name that the user should have. If not provided, this value will not be tested. + .PARAMETER NewName + Specifies the new name of the user. + .PARAMETER Description The description that the user should have. If not provided, this value will not be tested. @@ -204,6 +214,10 @@ function Test-TargetResource [System.String] $FullName, + [Parameter()] + [System.String] + $NewName, + [Parameter()] [System.String] $Description, @@ -328,6 +342,9 @@ function Get-TargetResourceOnFullSKU The (optional) full name or display name of the user. If not provided this value will remain blank. + .PARAMETER NewName + Specifies the new name of the user. + .PARAMETER Description Optional description for the user. @@ -372,6 +389,10 @@ function Set-TargetResourceOnFullSKU [System.String] $FullName, + [Parameter()] + [System.String] + $NewName, + [Parameter()] [System.String] $Description, @@ -404,6 +425,10 @@ function Set-TargetResourceOnFullSKU Write-Verbose -Message ($script:localizedData.ConfigurationStarted -f $UserName) Assert-UserNameValid -UserName $UserName + if ($newName) + { + Assert-UserNameValid -UserName $NewName + } # Try to find a user by name. $principalContext = New-Object ` @@ -426,6 +451,7 @@ function Set-TargetResourceOnFullSKU $whatIfShouldProcess = $true $userExists = $false $saveChanges = $false + $needsRename = $false if ($null -eq $user) { @@ -446,6 +472,15 @@ function Set-TargetResourceOnFullSKU if (-not $userExists) { # The user with the provided name does not exist so add a new user + foreach ($incompatibleParameterName in @( 'NewName' )) + { + if ($PSBoundParameters.ContainsKey($incompatibleParameterName)) + { + New-InvalidArgumentException -ArgumentName $incompatibleParameterName ` + -Message ($script:localizedData.NewUserNewNameConflict -f 'NewName', $incompatibleParameterName) + } + } + $user = New-Object ` -TypeName System.DirectoryServices.AccountManagement.UserPrincipal ` -ArgumentList $principalContext @@ -474,6 +509,13 @@ function Set-TargetResourceOnFullSKU $saveChanges = $true } + if ($PSBoundParameters.ContainsKey('NewName') -and (($userExists) -and ($NewName -ne $user.SamAccountName))) + { + $user.SamAccountName = $NewName + $saveChanges = $true + $needsRename = $true + } + # Set the password regardless of the state of the user if ($PSBoundParameters.ContainsKey('Password')) { @@ -514,6 +556,13 @@ function Set-TargetResourceOnFullSKU { $user.Save() + if ($needsRename) + { + $dirEntry = [System.DirectoryServices.DirectoryEntry]($user.GetUnderlyingObject()) + $dirEntry.Rename($newName); + $dirEntry.CommitChanges(); + } + # Send an operation success verbose message if ($userExists) { @@ -582,6 +631,9 @@ function Set-TargetResourceOnFullSKU The full name/display name that the user should have. If not provided, this value will not be tested. + .PARAMETER NewName + Specifies the new name of the user. + .PARAMETER Description The description that the user should have. If not provided, this value will not be tested. @@ -621,6 +673,10 @@ function Test-TargetResourceOnFullSKU [System.String] $FullName, + [Parameter()] + [System.String] + $NewName, + [Parameter()] [System.String] $Description, @@ -651,6 +707,10 @@ function Test-TargetResourceOnFullSKU Set-StrictMode -Version Latest Assert-UserNameValid -UserName $UserName + if ($newName) + { + Assert-UserNameValid -UserName $NewName + } # Try to find a user by a name $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext ` @@ -699,6 +759,13 @@ function Test-TargetResourceOnFullSKU return $false } + if ($PSBoundParameters.ContainsKey('NewName') -and $NewName -ne $user.SamAccountName) + { + # The Name property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'NewName', $NewName, $user.SamAccountName) + return $false + } + # Password if ($PSBoundParameters.ContainsKey('Password')) { @@ -833,6 +900,9 @@ function Get-TargetResourceOnNanoServer The (optional) full name or display name of the user. If not provided this value will remain blank. + .PARAMETER NewName + Specifies the new name of the user. + .PARAMETER Description Optional description for the user. @@ -876,6 +946,10 @@ function Set-TargetResourceOnNanoServer [System.String] $FullName, + [Parameter()] + [System.String] + $NewName, + [Parameter()] [System.String] $Description, @@ -908,6 +982,10 @@ function Set-TargetResourceOnNanoServer Write-Verbose -Message ($script:localizedData.ConfigurationStarted -f $UserName) Assert-UserNameValid -UserName $UserName + if ($newName) + { + Assert-UserNameValid -UserName $NewName + } # Try to find a user by a name. $userExists = $false @@ -977,6 +1055,14 @@ function Set-TargetResourceOnNanoServer } } + if ($PSBoundParameters.ContainsKey('NewName') -and ((-not $userExists) -or ($NewName -ne $user.SamAccountName))) + { + if ($null -ne $NewName) + { + Rename-LocalUser -Sid $UserName -NewName $NewName + } + } + # Set the password regardless of the state of the user if ($PSBoundParameters.ContainsKey('Password')) { @@ -1054,6 +1140,9 @@ function Set-TargetResourceOnNanoServer The full name/display name that the user should have. If not provided, this value will not be tested. + .PARAMETER NewName + Specifies the new name of the user. + .PARAMETER Description The description that the user should have. If not provided, this value will not be tested. @@ -1093,6 +1182,10 @@ function Test-TargetResourceOnNanoServer [System.String] $FullName, + [Parameter()] + [System.String] + $NewName, + [Parameter()] [System.String] $Description, @@ -1123,6 +1216,10 @@ function Test-TargetResourceOnNanoServer Set-StrictMode -Version Latest Assert-UserNameValid -UserName $UserName + if ($newName) + { + Assert-UserNameValid -UserName $NewName + } # Try to find a user by a name try @@ -1171,6 +1268,13 @@ function Test-TargetResourceOnNanoServer return $false } + if ($PSBoundParameters.ContainsKey('NewName') -and $NewName -ne $user.SamAccountName) + { + # The Name property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'NewName', $NewName, $user.SamAccountName) + return $false + } + if ($PSBoundParameters.ContainsKey('Password')) { if (-not (Test-CredentialsValidOnNanoServer -UserName $UserName -Password $Password.Password)) diff --git a/source/DSCResources/DSC_xUserResource/DSC_xUserResource.schema.mof b/source/DSCResources/DSC_xUserResource/DSC_xUserResource.schema.mof index 5bb01911b..51d492e59 100644 --- a/source/DSCResources/DSC_xUserResource/DSC_xUserResource.schema.mof +++ b/source/DSCResources/DSC_xUserResource/DSC_xUserResource.schema.mof @@ -4,6 +4,7 @@ class DSC_xUserResource : OMI_BaseResource [Key,Description("The name of the User to Create/Modify/Delete")] String UserName; [Write,Description("An enumerated value that describes if the user is expected to exist on the machine"),ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; [Write,Description("The display name of the user")] String FullName; + [Write,Description("Specifies the new name of the user")] String NewName; [Write,Description("A description for the user")] String Description; [Write,Description("The password for the user"),EmbeddedInstance("MSFT_Credential")] String Password; [Write,Description("Value used to disable/enable a user account")] Boolean Disabled; diff --git a/source/DSCResources/DSC_xUserResource/en-US/DSC_xUserResource.strings.psd1 b/source/DSCResources/DSC_xUserResource/en-US/DSC_xUserResource.strings.psd1 index ee860bb72..092faf738 100644 --- a/source/DSCResources/DSC_xUserResource/en-US/DSC_xUserResource.strings.psd1 +++ b/source/DSCResources/DSC_xUserResource/en-US/DSC_xUserResource.strings.psd1 @@ -12,6 +12,7 @@ ConvertFrom-StringData @' UserRemoved = User {0} removed successfully. NoConfigurationRequired = User {0} exists on this node with the desired properties. No action required. NoConfigurationRequiredUserDoesNotExist = User {0} does not exist on this node. No action required. + NewUserNewNameConflict = The {0} parameter cannot be used when creating a new user. InvalidUserName = The name {0} cannot be used. Names may not consist entirely of periods and/or spaces, or contain these characters: {1} UserExists = A user with the name {0} exists. UserDoesNotExist = A user with the name {0} does not exist. diff --git a/tests/Unit/DSC_xUserResource.Tests.ps1 b/tests/Unit/DSC_xUserResource.Tests.ps1 index 4c4eeebd8..e2b0233c9 100644 --- a/tests/Unit/DSC_xUserResource.Tests.ps1 +++ b/tests/Unit/DSC_xUserResource.Tests.ps1 @@ -120,6 +120,7 @@ try } Mock -CommandName Test-IsNanoServer -MockWith { return $false } + #Mock -CommandName New-Object New-User -Credential $script:newCredential1 -Description $script:newUserDescription1 @@ -134,6 +135,20 @@ try Test-User -UserName $script:newUserName2 | Should -BeTrue } + It 'Should rename the user' { + Test-User -UserName $script:newUserName1 | Should -BeFalse + Set-TargetResource -UserName $script:newUserName2 ` + -NewName $script:newUserName1 + Test-User -UserName $script:newUserName1 | Should -BeTrue + } + + It 'Should rename the user again' { + Test-User -UserName $script:newUserName2 | Should -BeFalse + Set-TargetResource -UserName $script:newUserName1 ` + -NewName $script:newUserName2 + Test-User -UserName $script:newUserName2 | Should -BeTrue + } + It 'Should update the user' { $disabled = $false $passwordNeverExpires = $true @@ -212,6 +227,20 @@ try Test-User -UserName $script:newUserName2 | Should -BeTrue } + It 'Should rename the user' -Skip:$script:skipMe { + Test-User -UserName $script:newUserName1 | Should -BeFalse + Set-TargetResource -UserName $script:newUserName2 ` + -NewName $script:newUserName1 + Test-User -UserName $script:newUserName1 | Should -BeTrue + } + + It 'Should rename the user again' -Skip:$script:skipMe { + Test-User -UserName $script:newUserName2 | Should -BeFalse + Set-TargetResource -UserName $script:newUserName1 ` + -NewName $script:newUserName2 + Test-User -UserName $script:newUserName2 | Should -BeTrue + } + It 'Should update the user' -Skip:$script:skipMe { $disabled = $false $passwordNeverExpires = $true