diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 0f5585dc4..5a5602608 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -1,37 +1,12 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "")] # To be removed when username/password changed to a credential +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] # User name and password needed for this resource +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] # Write-Verbose Used in helper functions param () -# A global variable that contains localized messages. -data LocalizedData -{ -# culture="en-US" -ConvertFrom-StringData @' -UserWithName=User: {0} -RemoveOperation=Remove -AddOperation=Add -SetOperation=Set -ConfigurationStarted=Configuration of user {0} started. -ConfigurationCompleted=Configuration of user {0} completed successfully. -UserCreated=User {0} created successfully. -UserUpdated=User {0} properties updated successfully. -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. -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. -PropertyMismatch=The value of the {0} property is expected to be {1} but it is {2}. -PasswordPropertyMismatch=The value of the {0} property does not match. -AllUserPropertisMatch=All {0} {1} properties match. -ConnectionError = There could be a possible connection error while trying to use the System.DirectoryServices API's. -MultipleMatches = There could be a possible multiple matches exception while trying to use the System.DirectoryServices API's. -'@ -} +Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') -# Commented-out until more languages are supported -# Import-LocalizedData LocalizedData -FileName MSFT_xUserResource.strings.psd1 - -Import-Module "$PSScriptRoot\..\CommonResourceHelper.psm1" +# Localized messages for Write-Verbose statements in this resource +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xUserResource' if (-not (Test-IsNanoServer)) { @@ -40,11 +15,15 @@ if (-not (Test-IsNanoServer)) <# .SYNOPSIS - The Get-TargetResource cmdlet. + Retrieves the user with the given username + + .PARAMETER UserName + The name of the user to retrieve. #> function Get-TargetResource { - [OutputType([Hashtable])] + [OutputType([System.Collections.Hashtable])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -65,11 +44,49 @@ function Get-TargetResource <# .SYNOPSIS - The Set-TargetResource cmdlet. + Creates, modifies, or deletes a user. + + .PARAMETER UserName + The name of the user to create, modify, or delete. + + .PARAMETER Ensure + Specifies whether the user should exist or not. + By default this is set to Present. + + .PARAMETER FullName + The (optional) full name or display name of the user. + If not provided this value will remain blank. + + .PARAMETER Description + Optional description for the user. + + .PARAMETER Password + The desired password for the user. + + .PARAMETER Disabled + Specifies whether the user should be disabled or not. + By default this is set to $false + + .PARAMETER PasswordNeverExpires + Specifies whether the password should ever expire or not. + By default this is set to $false + + .PARAMETER PasswordChangeRequired + Specifies whether the user must reset their password or not. + By default this is set to $false + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user is allowed to change their password or not. + By default this is set to $false + + .NOTES + If Ensure is set to 'Present' then the password parameter is required. #> function Set-TargetResource { - [CmdletBInding(SupportsShouldProcess = $true)] + # Should process is called in a helper functions but not directly in Set-TargetResource + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] @@ -77,9 +94,9 @@ function Set-TargetResource [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -89,6 +106,7 @@ function Set-TargetResource [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -116,21 +134,52 @@ function Set-TargetResource <# .SYNOPSIS - The Test-TargetResource cmdlet is used to validate if the resource is in a state as expected in the instance document. + Tests if a user is in the desired state. + + .PARAMETER UserName + The name of the user to test the state of. + + .PARAMETER Ensure + Specifies whether the user should exist or not. + By default this is set to Present + + .PARAMETER FullName + The full name/display name that the user should have. + If not provided, this value will not be tested. + + .PARAMETER Description + The description that the user should have. + If not provided, this value will not be tested. + + .PARAMETER Password + The password the user should have. + + .PARAMETER Disabled + Specifies whether the user account should be disabled or not. + + .PARAMETER PasswordNeverExpires + Specifies whether the password should ever expire or not. + + .PARAMETER PasswordChangeRequired + Not used in Test-TargetResource as there is no easy way to test this value. + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user should be allowed to change their password or not. #> function Test-TargetResource { - [OutputType([Boolean])] + [OutputType([System.Boolean])] + [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -140,6 +189,7 @@ function Test-TargetResource [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -168,10 +218,15 @@ function Test-TargetResource <# .SYNOPSIS - The Get-TargetResource cmdlet on a full server. + Retrieves the user with the given username when on a full server + + .PARAMETER UserName + The name of the user to retrieve. #> function Get-TargetResourceOnFullSKU { + [OutputType([System.Collections.Hashtable])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -182,55 +237,94 @@ function Get-TargetResourceOnFullSKU Set-StrictMode -Version Latest - ValidateUserName -UserName $UserName + Assert-UserNameValid -UserName $UserName - # Try to find a user by a name. - $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) + # Try to find a user by a name + $principalContext = New-Object ` + -TypeName System.DirectoryServices.AccountManagement.PrincipalContext ` + -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) try { - $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName); - if($user -ne $null) + Write-Verbose -Message 'Starting Get-TargetResource on FullSKU' + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName) + if ($null -ne $user) { - # The user is found. Return all user properties and Ensure="Present". + # The user is found. Return all user properties and Ensure='Present'. $returnValue = @{ - UserName = $user.Name; - Ensure = "Present"; - FullName = $user.DisplayName; - Description = $user.Description; - Disabled = -not $user.Enabled; - PasswordNeverExpires = $user.PasswordNeverExpires; - PasswordChangeRequired = $null; - PasswordChangeNotAllowed = $user.UserCannotChangePassword; + UserName = $user.Name + Ensure = 'Present' + FullName = $user.DisplayName + Description = $user.Description + Disabled = -not $user.Enabled + PasswordNeverExpires = $user.PasswordNeverExpires + PasswordChangeRequired = $null + PasswordChangeNotAllowed = $user.UserCannotChangePassword } - return $returnValue; + return $returnValue } - # The user is not found. Return Ensure=Absent. + # The user is not found. Return Ensure = Absent. return @{ - UserName = $UserName; - Ensure = "Absent"; + UserName = $UserName + Ensure = 'Absent' } } catch { - ThrowExceptionDueToDirectoryServicesError -ErrorId "MultipleMatches" -ErrorMessage ($LocalizedData.MultipleMatches + $_) + New-ConnectionException -ErrorId 'MultipleMatches' -ErrorMessage ($script:localizedData.MultipleMatches + $_) } finally { - if($user -ne $null) + if ($null -ne $user) { - $user.Dispose(); + $user.Dispose() } - $principalContext.Dispose(); + $principalContext.Dispose() } } <# .SYNOPSIS - The Set-TargetResource cmdlet on a full server. + Creates, modifies, or deletes a user when on a full server. + + .PARAMETER UserName + The name of the user to create, modify, or delete. + + .PARAMETER Ensure + Specifies whether the user should exist or not. + By default this is set to Present + + .PARAMETER FullName + The (optional) full name or display name of the user. + If not provided this value will remain blank. + + .PARAMETER Description + Optional description for the user. + + .PARAMETER Password + The desired password for the user. + + .PARAMETER Disabled + Specifies whether the user should be disabled or not. + By default this is set to $false + + .PARAMETER PasswordNeverExpires + Specifies whether the password should ever expire or not. + By default this is set to $false + + .PARAMETER PasswordChangeRequired + Specifies whether the user must reset their password or not. + By default this is set to $false + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user is allowed to change their password or not. + By default this is set to $false + + .NOTES + If Ensure is set to 'Present' then the Password parameter is required. #> function Set-TargetResourceOnFullSKU { @@ -242,9 +336,9 @@ function Set-TargetResourceOnFullSKU [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -254,6 +348,7 @@ function Set-TargetResourceOnFullSKU [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -271,169 +366,203 @@ function Set-TargetResourceOnFullSKU Set-StrictMode -Version Latest - Write-Verbose -Message ($LocalizedData.ConfigurationStarted -f $UserName) + Write-Verbose -Message ($script:localizedData.ConfigurationStarted -f $UserName) - ValidateUserName -UserName $UserName + Assert-UserNameValid -UserName $UserName - # Try to find a user by a name. - $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) + # Try to find a user by name. + $principalContext = New-Object ` + -TypeName System.DirectoryServices.AccountManagement.PrincipalContext ` + -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) try { - $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName); - if($Ensure -eq "Present") + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName) + if ($Ensure -eq 'Present') { - # Ensure is set to "Present". + $whatIfShouldProcess = $true + $userExists = $false + $saveChanges = $false - $whatIfShouldProcess = $true; - $userExists = $false; - $saveChanges = $false; - - if($user -eq $null) + if ($null -eq $user) { - # A user does not exist. Check WhatIf for adding a user. - $whatIfShouldProcess = $pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.AddOperation); + # A user does not exist. Check WhatIf for adding a user + $whatIfShouldProcess = $pscmdlet.ShouldProcess($script:localizedData.UserWithName -f $UserName, $script:localizedData.AddOperation) } else { - # A user exists. - $userExists = $true; + # A user exists + $userExists = $true - # Check WhatIf for setting a user. - $whatIfShouldProcess = $pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.SetOperation); + # Check WhatIf for setting a user + $whatIfShouldProcess = $pscmdlet.ShouldProcess($script:localizedData.UserWithName -f $UserName, $script:localizedData.SetOperation) } - if($whatIfShouldProcess) + if ($whatIfShouldProcess) { - if(-not $userExists) + if (-not $userExists) { - # The user with the provided name does not exist. Add a new user. - $user = New-Object System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $principalContext - $user.Name = $UserName; - $saveChanges = $true; + # The user with the provided name does not exist so add a new user + $user = New-Object ` + -TypeName System.DirectoryServices.AccountManagement.UserPrincipal ` + -ArgumentList $principalContext + $user.Name = $UserName + $saveChanges = $true } # Set user properties. - if($PSBoundParameters.ContainsKey('FullName') -and (-not $userExists -or $FullName -ne $user.DisplayName)) + if ($PSBoundParameters.ContainsKey('FullName') -and ((-not $userExists) -or ($FullName -ne $user.DisplayName))) { - $user.DisplayName = $FullName; - $saveChanges = $true; + $user.DisplayName = $FullName + $saveChanges = $true } else { - if(-not $userExists) + if (-not $userExists) { - # For a newly created user, set the DisplayName property to an empty string. By default DisplayName is set to user's name. - $user.DisplayName = [String]::Empty; + # For a newly created user, set the DisplayName property to an empty string since by default DisplayName is set to user's name + $user.DisplayName = [String]::Empty } } - if($PSBoundParameters.ContainsKey('Description') -and (-not $userExists -or $Description -ne $user.Description)) + if ($PSBoundParameters.ContainsKey('Description') -and ((-not $userExists) -or ($Description -ne $user.Description))) { - $user.Description = $Description; - $saveChanges = $true; + $user.Description = $Description + $saveChanges = $true } - # Password. Set the password regardless of the state of the user. - if($PSBoundParameters.ContainsKey('Password')) + # Set the password regardless of the state of the user + if ($PSBoundParameters.ContainsKey('Password')) { - $user.SetPassword($Password.GetNetworkCredential().Password); - $saveChanges = $true; + $user.SetPassword($Password.GetNetworkCredential().Password) + $saveChanges = $true } - if($PSBoundParameters.ContainsKey('Disabled') -and (-not $userExists -or $Disabled -eq $user.Enabled)) + if ($PSBoundParameters.ContainsKey('Disabled') -and ((-not $userExists) -or ($Disabled -eq $user.Enabled))) { - $user.Enabled = -not $Disabled; - $saveChanges = $true; + $user.Enabled = -not $Disabled + $saveChanges = $true } - if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and (-not $userExists -or $PasswordNeverExpires -ne $user.PasswordNeverExpires)) + if ($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and ((-not $userExists) -or ($PasswordNeverExpires -ne $user.PasswordNeverExpires))) { - $user.PasswordNeverExpires = $PasswordNeverExpires; - $saveChanges = $true; + $user.PasswordNeverExpires = $PasswordNeverExpires + $saveChanges = $true } - if($PSBoundParameters.ContainsKey('PasswordChangeRequired')) + if ($PSBoundParameters.ContainsKey('PasswordChangeRequired')) { - if($PasswordChangeRequired) + if ($PasswordChangeRequired) { - # Expire the password. This will force the user to change the password at the next logon. - $user.ExpirePasswordNow(); - $saveChanges = $true; + # Expire the password which will force the user to change the password at the next logon + $user.ExpirePasswordNow() + $saveChanges = $true } } - if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and (-not $userExists -or $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword)) + if ($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and ((-not $userExists) -or ($PasswordChangeNotAllowed -ne $user.UserCannotChangePassword))) { - $user.UserCannotChangePassword = $PasswordChangeNotAllowed; - $saveChanges = $true; + $user.UserCannotChangePassword = $PasswordChangeNotAllowed + $saveChanges = $true } - if($saveChanges) + if ($saveChanges) { - $user.Save(); + $user.Save() - # Send an operation success verbose message. - if($userExists) + # Send an operation success verbose message + if ($userExists) { - Write-Verbose -Message ($LocalizedData.UserUpdated -f $UserName) + Write-Verbose -Message ($script:localizedData.UserUpdated -f $UserName) } else { - Write-Verbose -Message ($LocalizedData.UserCreated -f $UserName) + Write-Verbose -Message ($script:localizedData.UserCreated -f $UserName) } } else { - Write-Verbose -Message ($LocalizedData.NoConfigurationRequired -f $UserName) + Write-Verbose -Message ($script:localizedData.NoConfigurationRequired -f $UserName) } } } else { - # Ensure is set to "Absent". - if($user -ne $null) + # Ensure is set to 'Absent' + if ($user -ne $null) { - # The user exists. - if($pscmdlet.ShouldProcess($LocalizedData.UserWithName -f $UserName, $LocalizedData.RemoveOperation)) + # The user exists + if ($pscmdlet.ShouldProcess($script:localizedData.UserWithName -f $UserName, $script:localizedData.RemoveOperation)) { - # Remove the user by the provided name. - $user.Delete(); + # Remove the user + $user.Delete() } - Write-Verbose -Message ($LocalizedData.UserRemoved -f $UserName) + Write-Verbose -Message ($script:localizedData.UserRemoved -f $UserName) } else { - Write-Verbose -Message ($LocalizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName) + Write-Verbose -Message ($script:localizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName) } } } catch { - ThrowExceptionDueToDirectoryServicesError -ErrorId "MultipleMatches" -ErrorMessage ($LocalizedData.MultipleMatches + $_) + New-InvalidOperationException -Message ($script:localizedData.MultipleMatches + $_) } finally { - if($user -ne $null) + if ($null -ne $user) { - $user.Dispose(); + $user.Dispose() } - $principalContext.Dispose(); + $principalContext.Dispose() } - Write-Verbose -Message ($LocalizedData.ConfigurationCompleted -f $UserName) + Write-Verbose -Message ($script:localizedData.ConfigurationCompleted -f $UserName) } <# .SYNOPSIS - The Test-TargetResource cmdlet on a full server. + Tests if a user is in the desired state when on a full server. + + .PARAMETER UserName + The name of the user to test the state of. + + .PARAMETER Ensure + Specifies whether the user should exist or not. + By default this is set to Present + + .PARAMETER FullName + The full name/display name that the user should have. + If not provided, this value will not be tested. + + .PARAMETER Description + The description that the user should have. + If not provided, this value will not be tested. + + .PARAMETER Password + The password the user should have. + + .PARAMETER Disabled + Specifies whether the user account should be disabled or not. + + .PARAMETER PasswordNeverExpires + Specifies whether the password should ever expire or not. + + .PARAMETER PasswordChangeRequired + Not used in Test-TargetResource as there is no easy way to test this value. + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user should be allowed to change their password or not. #> function Test-TargetResourceOnFullSKU { + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -441,9 +570,9 @@ function Test-TargetResourceOnFullSKU [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -453,6 +582,7 @@ function Test-TargetResourceOnFullSKU [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -470,107 +600,120 @@ function Test-TargetResourceOnFullSKU Set-StrictMode -Version Latest - ValidateUserName -UserName $UserName + Assert-UserNameValid -UserName $UserName - # Try to find a user by a name. - $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) + # Try to find a user by a name + $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext ` + -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) try { - $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName); - if($user -eq $null) + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName) + if ($null -eq $user) { - # A user with the provided name does not exist. - Write-Log -Message ($LocalizedData.UserDoesNotExist -f $UserName) + # A user with the provided name does not exist + Write-Verbose -Message ($script:localizedData.UserDoesNotExist -f $UserName) - if($Ensure -eq "Absent") + if ($Ensure -eq 'Absent') { - return $true; + return $true } else { - return $false; + return $false } } - # A user with the provided name exists. - Write-Log -Message ($LocalizedData.UserExists -f $UserName) + # A user with the provided name exists + Write-Verbose -Message ($script:localizedData.UserExists -f $UserName) - # Validate separate properties. - if($Ensure -eq "Absent") + # Validate separate properties + if ($Ensure -eq 'Absent') { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "Ensure", "Absent", "Present") - return $false; # The Ensure property does not match. Return $false; + # The Ensure property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'Ensure', 'Absent', 'Present') + return $false } - if($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.DisplayName) + if ($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.DisplayName) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "FullName", $FullName, $user.DisplayName) - return $false; # The FullName property does not match. Return $false; + # The FullName property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'FullName', $FullName, $user.DisplayName) + return $false } - if($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description) + if ($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "Description", $Description, $user.Description) - return $false; # The Description property does not match. Return $false; + # The Description property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'Description', $Description, $user.Description) + return $false } # Password - if($PSBoundParameters.ContainsKey('Password')) + if ($PSBoundParameters.ContainsKey('Password')) { - if(-not $principalContext.ValidateCredentials($UserName, $Password.GetNetworkCredential().Password)) + if (-not $principalContext.ValidateCredentials($UserName, $Password.GetNetworkCredential().Password)) { - Write-Log -Message ($LocalizedData.PasswordPropertyMismatch -f "Password") - return $false; # The Password property does not match. Return $false; + # The Password property does not match + Write-Verbose -Message ($script:localizedData.PasswordPropertyMismatch -f 'Password') + return $false } } - if($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled) + if ($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "Disabled", $Disabled, $user.Enabled) - return $false; # The Disabled property does not match. Return $false; + # The Disabled property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'Disabled', $Disabled, $user.Enabled) + return $false } - if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $user.PasswordNeverExpires) + if ($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $user.PasswordNeverExpires) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordNeverExpires", $PasswordNeverExpires, $user.PasswordNeverExpires) - return $false; # The PasswordNeverExpires property does not match. Return $false; + # The PasswordNeverExpires property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'PasswordNeverExpires', $PasswordNeverExpires, $user.PasswordNeverExpires) + return $false } - if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword) + if ($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne $user.UserCannotChangePassword) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordChangeNotAllowed", $PasswordChangeNotAllowed, $user.UserCannotChangePassword) - return $false; # The PasswordChangeNotAllowed property does not match. Return $false; + # The PasswordChangeNotAllowed property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'PasswordChangeNotAllowed', $PasswordChangeNotAllowed, $user.UserCannotChangePassword) + return $false } } catch { - ThrowExceptionDueToDirectoryServicesError -ErrorId "ConnectionError" -ErrorMessage ($LocalizedData.ConnectionError + $_) + New-ConnectionException -ErrorId 'ConnectionError' -ErrorMessage ($script:localizedData.ConnectionError + $_) } finally { - if($user -ne $null) + if ($null -ne $user) { - $user.Dispose(); + $user.Dispose() } - $principalContext.Dispose(); + $principalContext.Dispose() } - # All properties match. Return $true. - Write-Log -Message ($LocalizedData.AllUserPropertisMatch -f "User", $UserName) - return $true; + # All properties match + Write-Verbose -Message ($script:localizedData.AllUserPropertisMatch -f 'User', $UserName) + return $true } <# -.Synopsys -The Get-TargetResource cmdlet. + .SYNOPSIS + Retrieves the user with the given username when on Nano Server. + + .PARAMETER UserName + The name of the user to retrieve. #> function Get-TargetResourceOnNanoServer { + [OutputType([System.Collections.Hashtable])] + [CmdletBinding()] param ( [parameter(Mandatory = $true)] @@ -581,35 +724,36 @@ function Get-TargetResourceOnNanoServer Set-StrictMode -Version Latest - ValidateUserName -UserName $UserName + Assert-UserNameValid -UserName $UserName - # Try to find a user by a name. + # Try to find a user by a name try { + Write-Verbose -Message 'Starting Get-TargetResource on NanoServer' [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop } catch [System.Exception] { if ($_.CategoryInfo.ToString().Contains('UserNotFoundException')) { - # The user is not found. Return Ensure=Absent. + # The user is not found return @{ - UserName = $UserName; - Ensure = "Absent"; + UserName = $UserName + Ensure = 'Absent' } } - Throw-TerminatingError -ErrorRecord $_ + New-InvalidOperationException -ErrorRecord $_ } - # The user is found. Return all user properties and Ensure="Present". + # The user is found. Return all user properties and Ensure = 'Present'. $returnValue = @{ - UserName = $user.Name; - Ensure = "Present"; - FullName = $user.FullName; - Description = $user.Description; - Disabled = -not $user.Enabled; - PasswordChangeRequired = $null; - PasswordChangeNotAllowed = -not $user.UserMayChangePassword; + UserName = $user.Name + Ensure = 'Present' + FullName = $user.FullName + Description = $user.Description + Disabled = -not $user.Enabled + PasswordChangeRequired = $null + PasswordChangeNotAllowed = -not $user.UserMayChangePassword } if ($user.PasswordExpires) @@ -621,16 +765,51 @@ function Get-TargetResourceOnNanoServer $returnValue.Add('PasswordNeverExpires', $true) } - return $returnValue; + return $returnValue } <# .SYNOPSIS - The Set-TargetResource cmdlet on a Nano server. + Creates, modifies, or deletes a user when on Nano Server. + + .PARAMETER UserName + The name of the user to create, modify, or delete. + + .PARAMETER Ensure + Specifies whether the user should exist or not. + By default this is set to Present + + .PARAMETER FullName + The (optional) full name or display name of the user. + If not provided this value will remain blank. + + .PARAMETER Description + Optional description for the user. + + .PARAMETER Password + The desired password for the user. + + .PARAMETER Disabled + Specifies whether the user should be disabled or not. + By default this is set to $false + + .PARAMETER PasswordNeverExpires + Specifies whether the password should ever expire or not. + By default this is set to $false + + .PARAMETER PasswordChangeRequired + Specifies whether the user must reset their password or not. + By default this is set to $false + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user is allowed to change their password or not. + By default this is set to $false + + .NOTES + If Ensure is set to 'Present' then the Password parameter is required. #> function Set-TargetResourceOnNanoServer { - [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] @@ -638,9 +817,9 @@ function Set-TargetResourceOnNanoServer [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -650,6 +829,7 @@ function Set-TargetResourceOnNanoServer [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -667,43 +847,44 @@ function Set-TargetResourceOnNanoServer Set-StrictMode -Version Latest - Write-Verbose -Message ($LocalizedData.ConfigurationStarted -f $UserName) + Write-Verbose -Message ($script:localizedData.ConfigurationStarted -f $UserName) - ValidateUserName -UserName $UserName + Assert-UserNameValid -UserName $UserName - ## Try to find a user by a name. - [bool] $userExists = $false + # Try to find a user by a name. + $userExists = $false + try { [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop - $userExists = $true; + $userExists = $true } catch [System.Exception] { if ($_.CategoryInfo.ToString().Contains('UserNotFoundException')) { # The user is not found. - Write-Log -Message ($LocalizedData.UserDoesNotExist -f $UserName) + Write-Verbose -Message ($script:localizedData.UserDoesNotExist -f $UserName) } else { - Throw-TerminatingError -ErrorRecord $_ + New-InvalidOperationException -ErrorRecord $_ } } - if($Ensure -eq "Present") + if ($Ensure -eq 'Present') { - # Ensure is set to "Present". + # Ensure is set to 'Present' - if(-not $userExists) + if (-not $userExists) { - # The user with the provided name does not exist. Add a new user. + # The user with the provided name does not exist so add a new user New-LocalUser -Name $UserName -NoPassword - Write-Verbose -Message ($LocalizedData.UserCreated -f $UserName) + Write-Verbose -Message ($script:localizedData.UserCreated -f $UserName) } - # Set user properties. - if($PSBoundParameters.ContainsKey('FullName')) + # Set user properties + if ($PSBoundParameters.ContainsKey('FullName')) { if (-not $userExists -or $FullName -ne $user.FullName) { @@ -721,14 +902,14 @@ function Set-TargetResourceOnNanoServer { if (-not $userExists) { - # For a newly created user, set the DisplayName property to an empty string. By default DisplayName is set to user's name. + # For a newly created user, set the DisplayName property to an empty string since by default DisplayName is set to user's name. Set-LocalUser -Name $UserName -FullName ([String]::Empty) } } - if($PSBoundParameters.ContainsKey('Description') -and (-not $userExists -or $Description -ne $user.Description)) + if ($PSBoundParameters.ContainsKey('Description') -and ((-not $userExists) -or ($Description -ne $user.Description))) { - if ($Description -eq $null) + if ($null -eq $Description) { Set-LocalUser -Name $UserName -Description ([String]::Empty) } @@ -738,13 +919,13 @@ function Set-TargetResourceOnNanoServer } } - # Password. Set the password regardless of the state of the user. - if($PSBoundParameters.ContainsKey('Password')) + # Set the password regardless of the state of the user + if ($PSBoundParameters.ContainsKey('Password')) { Set-LocalUser -Name $UserName -Password $Password.Password } - if($PSBoundParameters.ContainsKey('Disabled') -and (-not $userExists -or $Disabled -eq $user.Enabled)) + if ($PSBoundParameters.ContainsKey('Disabled') -and ((-not $userExists) -or ($Disabled -eq $user.Enabled))) { if ($Disabled) { @@ -756,54 +937,88 @@ function Set-TargetResourceOnNanoServer } } - $existingUserPasswordNeverExpires = (($userExists) -and ($user.PasswordExpires -eq $null)) - if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and (-not $userExists -or ($PasswordNeverExpires -ne $existingUserPasswordNeverExpires))) + $existingUserPasswordNeverExpires = (($userExists) -and ($null -eq $user.PasswordExpires)) + if ($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and ((-not $userExists) -or ($PasswordNeverExpires -ne $existingUserPasswordNeverExpires))) { Set-LocalUser -Name $UserName -PasswordNeverExpires:$passwordNeverExpires } - if($PSBoundParameters.ContainsKey('PasswordChangeRequired') -and ($PasswordChangeRequired)) + if ($PSBoundParameters.ContainsKey('PasswordChangeRequired') -and ($PasswordChangeRequired)) { - Set-LocalUser -Name $UserName -PasswordChangeableDate ([datetime]::Now) + Set-LocalUser -Name $UserName -AccountExpires ([DateTime]::Now) } # NOTE: The parameter name and the property name have opposite meaning. - [bool] $expected = -not $PasswordChangeNotAllowed - [bool] $actual = $expected - if($userExists) { + [System.Boolean] $expected = -not $PasswordChangeNotAllowed + $actual = $expected + + if ($userExists) + { $actual = $user.UserMayChangePassword } - if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and (-not $userExists -or $expected -ne $actual)) + + if ($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and ((-not $userExists) -or ($expected -ne $actual))) { Set-LocalUser -Name $UserName -UserMayChangePassword $expected } } else { - # Ensure is set to "Absent". - if($userExists) + # Ensure is set to 'Absent' + if ($userExists) { - # The user exists. + # The user exists Remove-LocalUser -Name $UserName - Write-Verbose -Message ($LocalizedData.UserRemoved -f $UserName) + Write-Verbose -Message ($script:localizedData.UserRemoved -f $UserName) } else { - Write-Verbose -Message ($LocalizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName) + Write-Verbose -Message ($script:localizedData.NoConfigurationRequiredUserDoesNotExist -f $UserName) } } - - Write-Verbose -Message ($LocalizedData.ConfigurationCompleted -f $UserName) + Write-Verbose -Message ($script:localizedData.ConfigurationCompleted -f $UserName) } <# .SYNOPSIS - The Test-TargetResource cmdlet on a Nano server. + Tests if a user is in the desired state when on Nano Server. + + .PARAMETER UserName + The name of the user to test the state of. + + .PARAMETER Ensure + Specifies whether the user should exist or not. + By default this is set to Present + + .PARAMETER FullName + The full name/display name that the user should have. + If not provided, this value will not be tested. + + .PARAMETER Description + The description that the user should have. + If not provided, this value will not be tested. + + .PARAMETER Password + The password the user should have. + + .PARAMETER Disabled + Specifies whether the user account should be disabled or not. + + .PARAMETER PasswordNeverExpires + Specifies whether the password should ever expire or not. + + .PARAMETER PasswordChangeRequired + Not used in Test-TargetResource as there is no easy way to test this value. + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user should be allowed to change their password or not. #> function Test-TargetResourceOnNanoServer { + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -811,9 +1026,9 @@ function Test-TargetResourceOnNanoServer [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -823,6 +1038,7 @@ function Test-TargetResourceOnNanoServer [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -840,9 +1056,9 @@ function Test-TargetResourceOnNanoServer Set-StrictMode -Version Latest - ValidateUserName -UserName $UserName + Assert-UserNameValid -UserName $UserName - # Try to find a user by a name. + # Try to find a user by a name try { [Microsoft.PowerShell.Commands.LocalUser] $user = Get-LocalUser -Name $UserName -ErrorAction Stop @@ -851,8 +1067,8 @@ function Test-TargetResourceOnNanoServer { if ($_.CategoryInfo.ToString().Contains('UserNotFoundException')) { - # The user is not found. Return Ensure=Absent. - if($Ensure -eq "Absent") + # The user is not found + if ($Ensure -eq 'Absent') { return $true } @@ -861,70 +1077,81 @@ function Test-TargetResourceOnNanoServer return $false } } - Throw-TerminatingError -ErrorRecord $_ + New-InvalidOperationException -ErrorRecord $_ } - # A user with the provided name exists. - Write-Log -Message ($LocalizedData.UserExists -f $UserName) + # A user with the provided name exists + Write-Verbose -Message ($script:localizedData.UserExists -f $UserName) - # Validate separate properties. - if($Ensure -eq "Absent") + # Validate separate properties + if ($Ensure -eq 'Absent') { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "Ensure", "Absent", "Present") - return $false; # The Ensure property does not match. Return $false; + # The Ensure property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'Ensure', 'Absent', 'Present') + return $false } - if($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.FullName) + if ($PSBoundParameters.ContainsKey('FullName') -and $FullName -ne $user.FullName) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "FullName", $FullName, $user.FullName) - return $false; # The FullName property does not match. Return $false; + # The FullName property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'FullName', $FullName, $user.FullName) + return $false } - if($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description) + if ($PSBoundParameters.ContainsKey('Description') -and $Description -ne $user.Description) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "Description", $Description, $user.Description) - return $false; # The Description property does not match. Return $false; + # The Description property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'Description', $Description, $user.Description) + return $false } - if($PSBoundParameters.ContainsKey('Password')) + if ($PSBoundParameters.ContainsKey('Password')) { - if(-not (ValidateCredentialsOnNanoServer -UserName $UserName -Password $Password.Password)) + if(-not (Test-CredentialsValidOnNanoServer -UserName $UserName -Password $Password.Password)) { - Write-Log -Message ($LocalizedData.PasswordPropertyMismatch -f "Password") - return $false; # The Password property does not match. Return $false; + # The Password property does not match + Write-Verbose -Message ($script:localizedData.PasswordPropertyMismatch -f 'Password') + return $false } } - if($PSBoundParameters.ContainsKey('Disabled') -and $Disabled -eq $user.Enabled) + if ($PSBoundParameters.ContainsKey('Disabled') -and ($Disabled -eq $user.Enabled)) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "Disabled", $Disabled, $user.Enabled) - return $false; # The Disabled property does not match. Return $false; + # The Disabled property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'Disabled', $Disabled, $user.Enabled) + return $false } - $existingUserPasswordNeverExpires = ($user.PasswordExpires -eq $null) - if($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $existingUserPasswordNeverExpires) + $existingUserPasswordNeverExpires = ($null -eq $user.PasswordExpires) + if ($PSBoundParameters.ContainsKey('PasswordNeverExpires') -and $PasswordNeverExpires -ne $existingUserPasswordNeverExpires) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordNeverExpires", $PasswordNeverExpires, $existingUserPasswordNeverExpires) - return $false; # The PasswordNeverExpires property does not match. Return $false; + # The PasswordNeverExpires property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'PasswordNeverExpires', $PasswordNeverExpires, $existingUserPasswordNeverExpires) + return $false } - if($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne (-not $user.UserMayChangePassword)) + if ($PSBoundParameters.ContainsKey('PasswordChangeNotAllowed') -and $PasswordChangeNotAllowed -ne (-not $user.UserMayChangePassword)) { - Write-Log -Message ($LocalizedData.PropertyMismatch -f "PasswordChangeNotAllowed", $PasswordChangeNotAllowed, (-not $user.UserMayChangePassword)) - return $false; # The PasswordChangeNotAllowed property does not match. Return $false; + # The PasswordChangeNotAllowed property does not match + Write-Verbose -Message ($script:localizedData.PropertyMismatch -f 'PasswordChangeNotAllowed', $PasswordChangeNotAllowed, (-not $user.UserMayChangePassword)) + return $false } # All properties match. Return $true. - Write-Log -Message ($LocalizedData.AllUserPropertisMatch -f "User", $UserName) - return $true; + Write-Verbose -Message ($script:localizedData.AllUserPropertisMatch -f 'User', $UserName) + return $true } <# .SYNOPSIS - Validates the User name for invalid charecters. + Checks that the username does not contain invalid characters. + + .PARAMETER UserName + The username to validate. #> -function ValidateUserName +function Assert-UserNameValid { + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -933,58 +1160,46 @@ function ValidateUserName $UserName ) - # Check if the name consists of only periods and/or white spaces. - $wrongName = $true; - for($i = 0; $i -lt $UserName.Length; $i++) + # Check if the name consists of only periods and/or white spaces + $wrongName = $true + + for ($i = 0; $i -lt $UserName.Length; $i++) { - if(-not [Char]::IsWhiteSpace($UserName, $i) -and $UserName[$i] -ne '.') + if (-not [Char]::IsWhiteSpace($UserName, $i) -and $UserName[$i] -ne '.') { - $wrongName = $false; - break; + $wrongName = $false + break } } $invalidChars = @('\','/','"','[',']',':','|','<','>','+','=',';',',','?','*','@') - if($wrongName) + if ($wrongName) { - ThrowInvalidArgumentError -ErrorId "UserNameHasOnlyWhiteSpacesAndDots" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars)) + New-InvalidArgumentException ` + -Message ($script:localizedData.InvalidUserName -f $UserName, [String]::Join(' ', $invalidChars)) ` + -ArgumentName 'UserName' } - if($UserName.IndexOfAny($invalidChars) -ne -1) + if ($UserName.IndexOfAny($invalidChars) -ne -1) { - ThrowInvalidArgumentError -ErrorId "UserNameHasInvalidCharachter" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars)) + New-InvalidArgumentException ` + -Message ($script:localizedData.InvalidUserName -f $UserName, [String]::Join(' ', $invalidChars)) ` + -ArgumentName 'UserName' } } <# .SYNOPSIS - Throws an argument error. -#> -function ThrowInvalidArgumentError -{ - [CmdletBinding()] - param - ( - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorMessage - ) + Creates a new Connection error record and throws it. - $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument - $exception = New-Object System.ArgumentException $ErrorMessage; - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null - throw $errorRecord -} + .PARAMETER ErrorId + The ID for the error record to be thrown. -function ThrowExceptionDueToDirectoryServicesError + .PARAMETER ErrorMessage + Message to be included in the error record to be thrown. +#> +function New-ConnectionException { [CmdletBinding()] param @@ -1001,58 +1216,29 @@ function ThrowExceptionDueToDirectoryServicesError ) $errorCategory = [System.Management.Automation.ErrorCategory]::ConnectionError - $exception = New-Object System.ArgumentException $ErrorMessage - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null - throw $errorRecord -} - -function Throw-TerminatingError -{ - param( - [string] $Message, - [System.Management.Automation.ErrorRecord] $ErrorRecord - ) - - - if ($ErrorRecord -ne $null) - { - $exception = new-object "System.InvalidOperationException" $Message,$ErrorRecord.Exception - } - else - { - $exception = new-object "System.InvalidOperationException" $Message - } - $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception,"MachineStateIncorrect","InvalidOperation",$null + $exception = New-Object ` + -TypeName System.ArgumentException ` + -ArgumentList $ErrorMessage + $errorRecord = New-Object ` + -TypeName System.Management.Automation.ErrorRecord ` + -ArgumentList @($exception, $ErrorId, $errorCategory, $null) throw $errorRecord } <# .SYNOPSIS - Writes either to Verbose or ShouldProcess channel. -#> -function Write-Log -{ - [CmdletBinding(SupportsShouldProcess=$true)] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message - ) - - if ($PSCmdlet.ShouldProcess($Message, $null, $null)) - { - Write-Verbose $Message - } -} + Tests the local user's credentials on the local machine. + + .PARAMETER UserName + The username to validate the credentials of. -<# - .SYNOPSIS - Validates the local user's credentials on the local machine. + .PARAMETER Password + The password of the given user. #> -function ValidateCredentialsOnNanoServer +function Test-CredentialsValidOnNanoServer { + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -1061,7 +1247,7 @@ function ValidateCredentialsOnNanoServer $UserName, [ValidateNotNullOrEmpty()] - [securestring] + [SecureString] $Password ) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.schema.mof b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.schema.mof index 8cef25232..8e6c4fdf4 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.schema.mof +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.schema.mof @@ -1,13 +1,13 @@ [ClassVersion("1.0.0"), FriendlyName("xUser")] class MSFT_xUserResource : OMI_BaseResource { - [Key] string UserName; - [write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] string Ensure; - [write] string FullName; - [write] string Description; - [write,EmbeddedInstance("MSFT_Credential")] string Password; - [write] boolean Disabled; - [write] boolean PasswordNeverExpires; - [write] boolean PasswordChangeRequired; - [write] boolean PasswordChangeNotAllowed; + [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("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; + [Write,Description("Value used to set whether a user's password expires or not")] Boolean PasswordNeverExpires; + [Write,Description("Value used to require a user to change their password")] Boolean PasswordChangeRequired; + [Write,Description("Value used to set whether a user can/cannot change their password")] Boolean PasswordChangeNotAllowed; }; diff --git a/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.schema.mfl b/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.schema.mfl index 962252e7c..92809e050 100644 Binary files a/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.schema.mfl and b/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.schema.mfl differ diff --git a/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.strings.psd1 b/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.strings.psd1 index 76f0bd6f9..aff537e01 100644 --- a/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.strings.psd1 +++ b/DSCResources/MSFT_xUserResource/en-US/MSFT_xUserResource.strings.psd1 @@ -1,26 +1,23 @@ -# Localized resources for MSFT_xUserResource +# Localized resources for xUser ConvertFrom-StringData @' -###PSLOC -UserWithName=User: {0} -RemoveOperation=Remove -AddOperation=Add -SetOperation=Set -ConfigurationStarted=Configuration of user {0} started. -ConfigurationCompleted=Configuration of user {0} completed successfully. -UserCreated=User {0} created successfully. -UserUpdated=User {0} properties updated successfully. -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. -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. -PropertyMismatch=The value of the {0} property is expected to be {1} but it is {2}. -PasswordPropertyMismatch=The value of the {0} property does not match. -AllUserPropertisMatch=All {0} {1} properties match. -ConnectionError = There could be a possible connection error while trying to use the System.DirectoryServices API's. -MultipleMatches = There could be a possible multiple matches exception while trying to use the System.DirectoryServices API's. -###PSLOC - + UserWithName = User: {0} + RemoveOperation = Remove + AddOperation = Add + SetOperation = Set + ConfigurationStarted = Configuration of user {0} started. + ConfigurationCompleted = Configuration of user {0} completed successfully. + UserCreated = User {0} created successfully. + UserUpdated = User {0} properties updated successfully. + 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. + 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. + PropertyMismatch = The value of the {0} property is expected to be {1} but it is {2}. + PasswordPropertyMismatch = The value of the {0} property does not match. + AllUserPropertisMatch = All {0} {1} properties match. + ConnectionError = There could be a possible connection error while trying to use the System.DirectoryServices API's. + MultipleMatches = There could be a possible multiple matches exception while trying to use the System.DirectoryServices API's. '@ diff --git a/Examples/Sample_xUser.ps1 b/Examples/Sample_xUser_CreateUser.ps1 similarity index 100% rename from Examples/Sample_xUser.ps1 rename to Examples/Sample_xUser_CreateUser.ps1 diff --git a/Examples/Sample_xUser_Generic.ps1 b/Examples/Sample_xUser_Generic.ps1 new file mode 100644 index 000000000..c62e9d242 --- /dev/null +++ b/Examples/Sample_xUser_Generic.ps1 @@ -0,0 +1,68 @@ +param +( + [Parameter(Mandatory)] + [System.String] + $ConfigurationName +) + +<# + Create a custom configuration by passing in whatever + values you need. $Password is the only param that is + required since it must be a PSCredential object. + If you want to create a user with minimal attributes, + every param except username can be deleted since they + are optional. +#> + +Configuration $ConfigurationName +{ + param + ( + [System.String] + $UserName = 'Test UserName', + + [System.String] + $Description = 'Test Description', + + [System.String] + $FullName = 'Test Full Name', + + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory)] + [System.Management.Automation.PSCredential] + $Password, + + [System.Boolean] + $Disabled = $false, + + [System.Boolean] + $PasswordNeverExpires = $false, + + [System.Boolean] + $PasswordChangeRequired = $false, + + [System.Boolean] + $PasswordChangeNotAllowed = $false + ) + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + Node Localhost { + + xUser UserResource1 + { + UserName = $UserName + Ensure = $Ensure + FullName = $FullName + Description = $Description + Password = $Password + Disabled = $Disabled + PasswordNeverExpires = $PasswordNeverExpires + PasswordChangeRequired = $PasswordChangeRequired + PasswordChangeNotAllowed = $PasswordChangeNotAllowed + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index fa0d851f3..3898f3db3 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c * **xGroupSet** configures multiple xGroups with common settings but different names. * **xProcessSet** allows starting and stopping of a group of windows processes with no arguments. * **xServiceSet** allows starting, stopping and change in state or account type for a group of services. +* **xUser** provides a mechanism to manage local users on the target node. * **xWindowsFeatureSet** allows installation and uninstallation of a group of Windows features and their subfeatures. * **xWindowsOptionalFeature** provides a mechanism to enable or disable optional features on a target node. * **xWindowsOptionalFeatureSet** allows installation and uninstallation of a group of optional Windows features. @@ -38,6 +39,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c Resources that work on Nano Server: * xWindowsOptionalFeature +* xUser ### xArchive @@ -212,25 +214,37 @@ xRegistry provides a mechanism to manage registry keys and values on a target no * **Credential**: Indicates the credentials to use for running this script, if credentials are required. ### xUser -* **UserName**: Indicates the account name for which you want to ensure a specific state. -* **Description**: Indicates the description you want to use for the user account. -* **Disabled**: Indicates if the account is enabled. Set this property to $true to ensure that this account is disabled, and set it to $false to ensure that it is enabled. +Provides a mechanism to manage local users on a target node. + +#### Requirements + +* Target machine must be running a windows client operating system, Windows Server 2012 or later, or Nano Server. + +#### Parameters + +* **[String] UserName** _(Key)_: Indicates the account name for which you want to ensure a specific state. +* **[String] Description** _(Write)_: Indicates the description you want to use for the user account. +* **[Boolean] Disabled** _(Write)_: Indicates if the account is enabled. Set this property to $true to ensure that this account is disabled, and set it to $false to ensure that it is enabled. - Suported values: $true, $false - Default value: $false -* **Ensure**: Ensures that the feature is present or absent. +* **[String] Ensure** _(Write)_: Ensures that the feature is present or absent. - Supported values: Present, Absent - Default Value: Present -* **FullName**: Represents a string with the full name you want to use for the user account. -* **Password**: Indicates the password you want to use for this account. -* **PasswordChangeNotAllowed**: Indicates if the user can change the password. Set this property to $true to ensure that the user cannot change the password, and set it to $false to allow the user to change the password. +* **[String] FullName** _(Write)_: Represents a string with the full name you want to use for the user account. +* **[PSCredential] Password** _(Write)_: Indicates the password you want to use for this account. +* **[Boolean] PasswordChangeNotAllowed** _(Write)_: Indicates if the user can change the password. Set this property to $true to ensure that the user cannot change the password, and set it to $false to allow the user to change the password. - Suported values: $true, $false - Default value: $false -* **PasswordChangeRequired**: Indicates if the user must change the password at the next sign in. Set this property to $true if the user must change the password. +* **[Boolean] PasswordChangeRequired** _(Write)_: Indicates if the user must change the password at the next sign in. Set this property to $true if the user must change the password. - Suported values: $true, $false - Default value: $true -* **PasswordNeverExpires**: Indicates if the password will expire. To ensure that the password for this account will never expire, set this property to $true, and set it to $false if the password will expire. +* **[Boolean] PasswordNeverExpires** _(Write)_: Indicates if the password will expire. To ensure that the password for this account will never expire, set this property to $true, and set it to $false if the password will expire. - Suported values: $true, $false - Default value: $false + +#### Examples + +* [Create a new User](https://github.com/PowerShell/xPSDesiredStateConfiguration/blob/dev/Examples/Sample_xUser_CreateUser.ps1) ### xGroupSet * **GroupName**: Defines the names of the groups in the set. @@ -365,8 +379,10 @@ These parameters will be the same for each Windows optional feature in the set. * Formatting updated as per style guidelines * Missing comment-based help added for Get-/Set-/Test-TargetResource * Typos fixed in Unit test script - * Unit test 'Get-TargetResource/Should return hashtable with correct values when group - has no members' updated to handle the expected empty Members array correctly + * Unit test 'Get-TargetResource/Should return hashtable with correct values when group has no members' updated to handle the expected empty Members array correctly +* xUser: + * Fixed PSSA/Style violations + * Added/Updated Tests and Examples ### 4.0.0.0 diff --git a/Tests/Integration/MSFT_xUserResource.Integration.Tests.ps1 b/Tests/Integration/MSFT_xUserResource.Integration.Tests.ps1 new file mode 100644 index 000000000..00f280069 --- /dev/null +++ b/Tests/Integration/MSFT_xUserResource.Integration.Tests.ps1 @@ -0,0 +1,198 @@ +<# + To run these tests, the currently logged on user must have rights to create a user. + These integration tests cover creating a brand new user, updating values + of a user that already exists, and deleting a user that exists. +#> + +# Suppressing this rule since we need to create a plaintext password to test this resource +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () + +Import-Module -Name "$PSScriptRoot\..\CommonTestHelper.psm1" + +$script:testEnvironment = Enter-DscResourceTestEnvironment ` + -DscResourceModuleName 'xPSDesiredStateConfiguration' ` + -DscResourceName 'MSFT_xUserResource' ` + -TestType 'Integration' + +try { + + $configFile = Join-Path -Path $PSScriptRoot -ChildPath 'MSFT_xUserResource.config.ps1' + + + Describe 'xUserResource Integration Tests' { + $ConfigData = @{ + AllNodes = @( + @{ + NodeName = '*' + PSDscAllowPlainTextPassword = $true + } + @{ + NodeName = 'localhost' + } + ) + } + + Context 'Should create a new user' { + $configurationName = 'MSFT_xUser_NewUser' + $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName + + $logPath = Join-Path -Path $TestDrive -ChildPath 'NewUser.log' + + $testUserName = 'TestUserName12345' + $testUserPassword = 'StrongOne7.' + $testDescription = 'Test Description' + $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force + $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + + try + { + It 'Should compile without throwing' { + { + . $configFile -ConfigurationName $configurationName + & $configurationName -UserName $testUserName ` + -Password $testCredential ` + -Description $testDescription ` + -OutputPath $configurationPath ` + -ConfigurationData $ConfigData ` + -ErrorAction Stop + Start-DscConfiguration -Path $configurationPath -Wait -Force + } | Should Not Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not Throw + } + + It 'Should return the correct configuration' { + $currentConfig = Get-DscConfiguration -Verbose -ErrorAction Stop + $currentConfig.UserName | Should Be $testUserName + $currentConfig.Ensure | Should Be 'Present' + $currentConfig.Description | Should Be $TestDescription + $currentConfig.Disabled | Should Be $false + $currentConfig.PasswordChangeRequired | Should Be $null + } + } + finally + { + if (Test-Path -Path $logPath) { + Remove-Item -Path $logPath -Recurse -Force + } + + if (Test-Path -Path $configurationPath) + { + Remove-Item -Path $configurationPath -Recurse -Force + } + } + } + + Context 'Should update an existing user' { + $configurationName = 'MSFT_xUser_UpdateUser' + $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName + + $logPath = Join-Path -Path $TestDrive -ChildPath 'UpdateUser.log' + + $testUserName = 'TestUserName12345' + $testUserPassword = 'StrongOne7.' + $testDescription = 'New Test Description' + $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force + $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + + try + { + It 'Should compile without throwing' { + { + . $configFile -ConfigurationName $configurationName + & $configurationName -UserName $testUserName ` + -Password $testCredential ` + -Description $testDescription ` + -OutputPath $configurationPath ` + -ConfigurationData $ConfigData ` + -ErrorAction Stop + Start-DscConfiguration -Path $configurationPath -Wait -Force + } | Should Not Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not Throw + } + + It 'Should return the correct configuration' { + $currentConfig = Get-DscConfiguration -Verbose -ErrorAction Stop + $currentConfig.UserName | Should Be $testUserName + $currentConfig.Ensure | Should Be 'Present' + $currentConfig.Description | Should Be $TestDescription + $currentConfig.Disabled | Should Be $false + $currentConfig.PasswordChangeRequired | Should Be $null + } + } + finally + { + if (Test-Path -Path $logPath) { + Remove-Item -Path $logPath -Recurse -Force + } + + if (Test-Path -Path $configurationPath) + { + Remove-Item -Path $configurationPath -Recurse -Force + } + } + } + + Context 'Should delete an existing user' { + $configurationName = 'MSFT_xUser_DeleteUser' + $configurationPath = Join-Path -Path $TestDrive -ChildPath $configurationName + + $logPath = Join-Path -Path $TestDrive -ChildPath 'DeleteUser.log' + + $testUserName = 'TestUserName12345' + $testUserPassword = 'StrongOne7.' + $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force + $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + + try + { + It 'Should compile without throwing' { + { + . $configFile -ConfigurationName $configurationName + & $configurationName -UserName $testUserName ` + -Password $testCredential ` + -OutputPath $configurationPath ` + -ConfigurationData $ConfigData ` + -Ensure 'Absent' ` + -ErrorAction Stop + Start-DscConfiguration -Path $configurationPath -Wait -Force + } | Should Not Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { Get-DscConfiguration -Verbose -ErrorAction Stop } | Should Not Throw + } + + It 'Should return the correct configuration' { + $currentConfig = Get-DscConfiguration -Verbose -ErrorAction Stop + $currentConfig.UserName | Should Be $testUserName + $currentConfig.Ensure | Should Be 'Absent' + } + } + finally + { + if (Test-Path -Path $logPath) { + Remove-Item -Path $logPath -Recurse -Force + } + + if (Test-Path -Path $configurationPath) + { + Remove-Item -Path $configurationPath -Recurse -Force + } + } + } + + } +} +finally +{ + Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment +} + + diff --git a/Tests/Integration/MSFT_xUserResource.config.ps1 b/Tests/Integration/MSFT_xUserResource.config.ps1 new file mode 100644 index 000000000..63deb8d7b --- /dev/null +++ b/Tests/Integration/MSFT_xUserResource.config.ps1 @@ -0,0 +1,46 @@ + +# Integration Test Config Template Version 1.0.0 +param +( + [Parameter(Mandatory)] + [System.String] + $ConfigurationName +) + + +Configuration $ConfigurationName +{ + param + ( + [System.String] + $UserName = 'Test UserName', + + [System.String] + $Description = 'Test Description', + + [System.String] + $FullName = 'Test Full Name', + + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory)] + [System.Management.Automation.PSCredential] + $Password + ) + + Import-DscResource -ModuleName 'xPSDesiredStateConfiguration' + + Node Localhost { + + xUser UserResource1 + { + UserName = $UserName + Ensure = $Ensure + FullName = $FullName + Description = $Description + Password = $Password + } + } +} diff --git a/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 b/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 index 0ff21254b..1ed7d5a3d 100644 --- a/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 +++ b/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 @@ -2,24 +2,24 @@ <# .SYNOPSIS - Tests if a scope represents the current machine. + Tests if a scope represents the current machine. .PARAMETER Scope - The scope to test. + The scope to test. #> function Test-IsLocalMachine { - [OutputType([Boolean])] + [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string] + [System.String] $Scope ) Set-StrictMode -Version latest - if ($scope -eq ".") + if ($scope -eq '.') { return $true } @@ -29,14 +29,14 @@ function Test-IsLocalMachine return $true } - if ($scope -eq "localhost") + if ($scope -eq 'localhost') { return $true } - if ($scope.Contains(".")) + if ($scope.Contains('.')) { - if ($scope -eq "127.0.0.1") + if ($scope -eq '127.0.0.1') { return $true } @@ -64,36 +64,36 @@ function Test-IsLocalMachine <# .SYNOPSIS - Creates a user account. + Creates a user account. .DESCRIPTION - This function creates a user on the local or remote machine. + This function creates a user on the local or remote machine. .PARAMETER Credential - The credential containing the username and password to use to create the account. + The credential containing the username and password to use to create the account. .PARAMETER Description - The optional description to set on the user account. + The optional description to set on the user account. .PARAMETER ComputerName - The optional name of the computer to update. Omit to create a user on the local machine. + The optional name of the computer to update. Omit to create a user on the local machine. .NOTES - For remote machines, the currently logged on user must have rights to create a user. + For remote machines, the currently logged on user must have rights to create a user. #> function New-User { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [PSCredential] - [System.Management.Automation.CredentialAttribute()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, - [string] + [System.String] $Description, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) @@ -109,36 +109,36 @@ function New-User <# .SYNOPSIS - Creates a user account on a full server. + Creates a user account on a full server. .DESCRIPTION - This function creates a user on the local or remote machine running a full server. + This function creates a user on the local or remote machine running a full server. .PARAMETER Credential - The credential containing the username and password to use to create the account. + The credential containing the username and password to use to create the account. .PARAMETER Description - The optional description to set on the user account. + The optional description to set on the user account. .PARAMETER ComputerName - The optional name of the computer to update. Omit to create a user on the local machine. + The optional name of the computer to update. Omit to create a user on the local machine. .NOTES - For remote machines, the currently logged on user must have rights to create a user. + For remote machines, the currently logged on user must have rights to create a user. #> function New-UserOnFullSKU { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [PSCredential] - [System.Management.Automation.CredentialAttribute()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Credential, - [string] + [System.String] $Description, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) @@ -151,12 +151,12 @@ function New-UserOnFullSKU Remove-User $userName $ComputerName $adComputerEntry = [ADSI] "WinNT://$ComputerName" - $adUserEntry = $adComputerEntry.Create("User", $userName) + $adUserEntry = $adComputerEntry.Create('User', $userName) $null = $adUserEntry.SetPassword($password) - if ($PSBoundParameters.ContainsKey("Description")) + if ($PSBoundParameters.ContainsKey('Description')) { - $null = $adUserEntry.Put("Description", $Description) + $null = $adUserEntry.Put('Description', $Description) } $null = $adUserEntry.SetInfo() @@ -164,43 +164,43 @@ function New-UserOnFullSKU <# .SYNOPSIS - Creates a user account on a Nano server. + Creates a user account on a Nano server. .DESCRIPTION - This function creates a user on the local machine running a Nano server. + This function creates a user on the local machine running a Nano server. .PARAMETER Credential - The credential containing the username and password to use to create the account. + The credential containing the username and password to use to create the account. .PARAMETER Description - The optional description to set on the user account. + The optional description to set on the user account. .PARAMETER ComputerName - This parameter should not be used on NanoServer. + This parameter should not be used on NanoServer. #> function New-UserOnNanoServer { param ( [Parameter(Mandatory = $true)] - [PSCredential] + [System.Management.Automation.PSCredential] [System.Management.Automation.CredentialAttribute()] $Credential, - [string] + [System.String] $Description, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) Set-StrictMode -Version Latest - if ($PSBoundParameters.ContainsKey("ComputerName")) + if ($PSBoundParameters.ContainsKey('ComputerName')) { if (-not (Test-IsLocalMachine -Scope $ComputerName)) { - throw "Do not specify the ComputerName arguments when running on NanoServer unless it is local machine." + throw 'Do not specify the ComputerName arguments when running on NanoServer unless it is local machine.' } } @@ -212,7 +212,7 @@ function New-UserOnNanoServer New-LocalUser -Name $userName -Password $securePassword - if ($PSBoundParameters.ContainsKey("Description")) + if ($PSBoundParameters.ContainsKey('Description')) { Set-LocalUser -Name $userName -Description $Description } @@ -220,29 +220,29 @@ function New-UserOnNanoServer <# .SYNOPSIS - Removes a user account. + Removes a user account. .DESCRIPTION - This function removes a local user from the local or remote machine. + This function removes a local user from the local or remote machine. .PARAMETER UserName - The name of the user to remove. + The name of the user to remove. .PARAMETER ComputerName - The optional name of the computer to update. Omit to remove the user on the local machine. + The optional name of the computer to update. Omit to remove the user on the local machine. .NOTES - For remote machines, the currently logged on user must have rights to remove a user. + For remote machines, the currently logged on user must have rights to remove a user. #> function Remove-User { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [string] + [System.String] $UserName, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) @@ -258,29 +258,29 @@ function Remove-User <# .SYNOPSIS - Removes a user account on a full server. + Removes a user account on a full server. .DESCRIPTION - This function removes a local user from the local or remote machine running a full server. + This function removes a local user from the local or remote machine running a full server. .PARAMETER UserName - The name of the user to remove. + The name of the user to remove. .PARAMETER ComputerName - The optional name of the computer to update. Omit to remove the user on the local machine. + The optional name of the computer to update. Omit to remove the user on the local machine. .NOTES - For remote machines, the currently logged on user must have rights to remove a user. + For remote machines, the currently logged on user must have rights to remove a user. #> function Remove-UserOnFullSKU { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [string] + [System.String] $UserName, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) @@ -296,36 +296,36 @@ function Remove-UserOnFullSKU <# .SYNOPSIS - Removes a local user account on a Nano server. + Removes a local user account on a Nano server. .DESCRIPTION - This function removes a local user from the local machine running a Nano Server. + This function removes a local user from the local machine running a Nano Server. .PARAMETER UserName - The name of the user to remove. + The name of the user to remove. .PARAMETER ComputerName - This parameter should not be used on NanoServer. + This parameter should not be used on NanoServer. #> function Remove-UserOnNanoServer { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] - [string] + [System.String] $UserName, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) Set-StrictMode -Version Latest - if ($PSBoundParameters.ContainsKey("ComputerName")) + if ($PSBoundParameters.ContainsKey('ComputerName')) { if (-not (Test-IsLocalMachine -Scope $ComputerName)) { - throw "Do not specify the ComputerName arguments when running on NanoServer unless it is local machine." + throw 'Do not specify the ComputerName arguments when running on NanoServer unless it is local machine.' } } @@ -334,26 +334,26 @@ function Remove-UserOnNanoServer <# .SYNOPSIS - Determines if a user exists.. + Determines if a user exists.. .DESCRIPTION - This function determines if a user exists on a local or remote machine running. + This function determines if a user exists on a local or remote machine running. .PARAMETER UserName - The name of the user to test. + The name of the user to test. .PARAMETER ComputerName - The optional name of the computer to update. + The optional name of the computer to update. #> function Test-User { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [string] + [System.String] $UserName, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) @@ -369,26 +369,27 @@ function Test-User <# .SYNOPSIS - Determines if a user exists on a full server. + Determines if a user exists on a full server. .DESCRIPTION - This function determines if a user exists on a local or remote machine running a full server. + This function determines if a user exists on a local or remote machine running a full server. .PARAMETER UserName - The name of the user to test. + The name of the user to test. .PARAMETER ComputerName - The optional name of the computer to update. + The optional name of the computer to update. #> function Test-UserOnFullSKU { + [OutputType([System.Boolean])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [string] + [System.String] $UserName, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) @@ -405,34 +406,35 @@ function Test-UserOnFullSKU <# .SYNOPSIS - Determines if a user exists on a Nano server. + Determines if a user exists on a Nano server. .DESCRIPTION - This function determines if a user exists on a local or remote machine running a Nano server. + This function determines if a user exists on a local or remote machine running a Nano server. .PARAMETER UserName - The name of the user to test. + The name of the user to test. .PARAMETER ComputerName - This parameter should not be used on NanoServer. + This parameter should not be used on NanoServer. #> function Test-UserOnNanoServer { + [OutputType([System.Boolean])] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] - [string] + [System.String] $UserName, - [string] + [System.String] $ComputerName = $env:COMPUTERNAME ) - if ($PSBoundParameters.ContainsKey("ComputerName")) + if ($PSBoundParameters.ContainsKey('ComputerName')) { if (-not (Test-IsLocalMachine -Scope $ComputerName)) { - throw "Do not specify the ComputerName arguments when running on NanoServer unless it is local machine." + throw 'Do not specify the ComputerName arguments when running on NanoServer unless it is local machine.' } } @@ -451,13 +453,16 @@ function Test-UserOnNanoServer } throw $_.Exception } + finally + { + Remove-LocalUser -Name $UserName + } return $false - - Remove-LocalUser -Name $UserName } Export-ModuleMember -Function ` New-User, ` Remove-User, ` + Test-IsLocalMachine, ` Test-User diff --git a/Tests/Unit/MSFT_xUserResource.Tests.ps1 b/Tests/Unit/MSFT_xUserResource.Tests.ps1 index 2ffb69e57..3db55547e 100644 --- a/Tests/Unit/MSFT_xUserResource.Tests.ps1 +++ b/Tests/Unit/MSFT_xUserResource.Tests.ps1 @@ -1,149 +1,448 @@ -#To run these tests, the currently logged on user must have rights to create a user -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] +# To run these tests, the currently logged on user must have rights to create a user +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName 'xPSDesiredStateConfiguration' ` +Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonTestHelper.psm1') ` + -Force + +$script:testEnvironment = Enter-DscResourceTestEnvironment ` + -DSCResourceModuleName 'xPSDesiredStateConfiguration' ` -DSCResourceName 'MSFT_xUserResource' ` -TestType Unit -InModuleScope 'MSFT_xUserResource' { - Describe 'xUser Unit Tests' { - BeforeAll { - Import-Module "$PSScriptRoot\..\CommonTestHelper.psm1" -Force - Import-Module "$PSScriptRoot\MSFT_xUserResource.TestHelper.psm1" -Force - } - - It 'Get-TargetResource user present' { - $testUserName = "TestUserName12345" - $testUserPassword = "StrongOne7." - $testUserDescription = "Some Description" +try { - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) ` + -ChildPath 'MSFT_xUserResource.TestHelper.psm1') ` + -Force - New-User -Credential $testCredential -Description $testUserDescription + InModuleScope 'MSFT_xUserResource' { + # Used to skip the Nano server tests for the time being since they are not working on AppVeyor + + $script:skipMe = $true + + $existingUserName = 'TestUserName12345' + $existingUserPassword = 'StrongOne7.' + $existingDescription = 'Some Description' + $existingSecurePassword = ConvertTo-SecureString $existingUserPassword -AsPlainText -Force + $existingTestCredential = New-Object PSCredential ($existingUserName, $existingSecurePassword) + + New-User -Credential $existingTestCredential -Description $existingDescription + + $newUserName1 = 'NewTestUserName12345' + $newUserPassword1 = 'NewStrongOne123.' + $newFullName1 = 'Fullname1' + $newUserDescription1 = 'New Description1' + $newSecurePassword1 = ConvertTo-SecureString $newUserPassword1 -AsPlainText -Force + $newCredential1 = New-Object PSCredential ($newUserName1, $newSecurePassword1) + + $newUserName2 = 'newUser1234' + $newPassword2 = 'ThisIsAStrongPassword543!' + $newFullName2 = 'Fullname2' + $newUserDescription2 = 'New Description2' + $newSecurePassword2 = ConvertTo-SecureString $newPassword2 -AsPlainText -Force + $newCredential2 = New-Object PSCredential ($newUserName2, $newSecurePassword2) + + try { - try - { - $getTargetResourceResult = Get-TargetResource $testUserName + Describe 'xUserResource/Get-TargetResource' { - $getTargerResourceResultProperties = @('UserName', 'Ensure', 'Description', 'Disabled', 'PasswordNeverExpires', 'PasswordChangeNotAllowed') - - Test-GetTargetResourceResult -GetTargetResourceResult $getTargetResourceResult -GetTargetResourceResultProperties $getTargerResourceResultProperties - - $getTargetResourceResult["UserName"] | Should Be $testUserName - $getTargetResourceResult["Ensure"] | Should Be "Present" - $getTargetResourceResult["Description"] | Should Be $testUserDescription - $getTargetResourceResult["PasswordChangeRequired"] | Should Be $null - } - finally - { - Remove-User -UserName $testUserName - } - } + Context 'Tests on FullSKU' { + Mock -CommandName Test-IsNanoServer -MockWith { return $false } - It 'Get-TargetResource user absent' { - $testUserName = "AbsentUserUserName123456789" + It 'Should return the user as Present' { + $getTargetResourceResult = Get-TargetResource $existingUserName - $getTargetResourceResult = Get-TargetResource $testUserName + $getTargetResourceResult['UserName'] | Should Be $existingUserName + $getTargetResourceResult['Ensure'] | Should Be 'Present' + $getTargetResourceResult['Description'] | Should Be $existingDescription + $getTargetResourceResult['PasswordChangeRequired'] | Should Be $null + } - $getTargerResourceResultProperties = @('UserName', 'Ensure') + It 'Should return the user as Absent' { + $getTargetResourceResult = Get-TargetResource 'NotAUserName' - Test-GetTargetResourceResult -GetTargetResourceResult $getTargetResourceResult -GetTargetResourceResultProperties $getTargerResourceResultProperties + $getTargetResourceResult['UserName'] | Should Be 'NotAUserName' + $getTargetResourceResult['Ensure'] | Should Be 'Absent' + } + } - $getTargetResourceResult["UserName"] | Should Be $TestUserName - $getTargetResourceResult["Ensure"] | Should Be "Absent" - } + Context 'Tests on Nano Server' { + Mock -CommandName Test-IsNanoServer -MockWith { return $true } - It 'Test-TargetResource user present and correct description' { - $testUserName = "TestUserName12345" - $testUserPassword = "StrongOne7." - $testUserDescription = "Some Description" + It 'Should return the user as Present on Nano Server' -Skip:$script:skipMe { + $getTargetResourceResult = Get-TargetResource $existingUserName - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + $getTargetResourceResult['UserName'] | Should Be $existingUserName + $getTargetResourceResult['Ensure'] | Should Be 'Present' + $getTargetResourceResult['Description'] | Should Be $existingDescription + $getTargetResourceResult['PasswordChangeRequired'] | Should Be $null + } - New-User -Credential $testCredential -Description $testUserDescription + It 'Should return the user as Absent' -Skip:$script:skipMe { + $getTargetResourceResult = Get-TargetResource 'NotAUserName' - try - { - $testTargetResourceResult = Test-TargetResource $testUserName -Description $testUserDescription - $testTargetResourceResult | Should Be $true + $getTargetResourceResult['UserName'] | Should Be 'NotAUserName' + $getTargetResourceResult['Ensure'] | Should Be 'Absent' + } + } } - finally - { - Remove-User -UserName $testUserName - } - } - - It 'Test-TargetResource user present and wrong description' { - $testUserName = "TestUserName12345" - $testUserPassword = "StrongOne7." - $testUserDescription = "Some Description" - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + Describe 'xUserResource/Set-TargetResource' { + Context 'Tests on FullSKU' { + Mock -CommandName Test-IsNanoServer -MockWith { return $false } + + try + { + New-User -Credential $newCredential1 -Description $newUserDescription1 + + It 'Should remove the user' { + Test-User -UserName $newUserName1 | Should Be $true + Set-TargetResource -UserName $newUserName1 -Ensure 'Absent' + Test-User -UserName $newUserName1 | Should Be $false + } + + It 'Should add the new user' { + Set-TargetResource -UserName $newUserName2 -Password $newCredential2 -Ensure 'Present' + Test-User -UserName $newUserName2 | Should Be $true + } + + It 'Should update the user' { + $disabled = $false + $passwordNeverExpires = $true + $passwordChangeRequired = $false + $passwordChangeNotAllowed = $true + + Set-TargetResource -UserName $newUserName2 ` + -Password $newCredential2 ` + -Ensure 'Present' ` + -FullName $newFullName1 ` + -Description $newUserDescription1 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeRequired $passwordChangeRequired ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + + Test-User -UserName $newUserName2 | Should Be $true + $testTargetResourceResult1 = + Test-TargetResource -UserName $newUserName2 ` + -Password $newCredential2 ` + -Ensure 'Present' ` + -FullName $newFullName1 ` + -Description $newUserDescription1 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + $testTargetResourceResult1 | Should Be $true + } + It 'Should update the user again with different values' { + $disabled = $false + $passwordNeverExpires = $false + $passwordChangeRequired = $true + $passwordChangeNotAllowed = $false + + Set-TargetResource -UserName $newUserName2 ` + -Password $newCredential1 ` + -Ensure 'Present' ` + -FullName $newFullName2 ` + -Description $newUserDescription2 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeRequired $passwordChangeRequired ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + + Test-User -UserName $newUserName2 | Should Be $true + $testTargetResourceResult2 = + Test-TargetResource -UserName $newUserName2 ` + -Password $newCredential1 ` + -Ensure 'Present' ` + -FullName $newFullName2 ` + -Description $newUserDescription2 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + $testTargetResourceResult2 | Should Be $true + } + } + finally + { + Remove-User -UserName $newUserName1 + Remove-User -UserName $newUserName2 + } + } - New-User -Credential $testCredential -Description $testUserDescription - - try - { - $testTargetResourceResult = Test-TargetResource $testUserName -Description "Wrong description" - $testTargetResourceResult | Should Be $false - } - finally - { - Remove-User -UserName $testUserName + Context 'Tests on Nano Server' { + Mock -CommandName Test-IsNanoServer -MockWith { return $true } + Mock -CommandName Test-CredentialsValidOnNanoServer { return $true } + + try + { + New-User -Credential $newCredential1 -Description $newUserDescription1 + + It 'Should remove the user' -Skip:$script:skipMe { + Test-User -UserName $newUserName1 | Should Be $true + Set-TargetResource -UserName $newUserName1 -Ensure 'Absent' + Test-User -UserName $newUserName1 | Should Be $false + } + + It 'Should add the new user' -Skip:$script:skipMe { + Set-TargetResource -UserName $newUserName2 -Password $newCredential2 -Ensure 'Present' + Test-User -UserName $newUserName2 | Should Be $true + } + + It 'Should update the user' -Skip:$script:skipMe { + $disabled = $false + $passwordNeverExpires = $true + $passwordChangeRequired = $false + $passwordChangeNotAllowed = $true + + Set-TargetResource -UserName $newUserName2 ` + -Password $newCredential2 ` + -Ensure 'Present' ` + -FullName $newFullName1 ` + -Description $newUserDescription1 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeRequired $passwordChangeRequired ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + + Test-User -UserName $newUserName2 | Should Be $true + $testTargetResourceResult1 = + Test-TargetResource -UserName $newUserName2 ` + -Password $newCredential2 ` + -Ensure 'Present' ` + -FullName $newFullName1 ` + -Description $newUserDescription1 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + $testTargetResourceResult1 | Should Be $true + } + It 'Should update the user again with different values' -Skip:$script:skipMe { + $disabled = $false + $passwordNeverExpires = $false + $passwordChangeRequired = $true + $passwordChangeNotAllowed = $false + + Set-TargetResource -UserName $newUserName2 ` + -Password $newCredential1 ` + -Ensure 'Present' ` + -FullName $newFullName2 ` + -Description $newUserDescription2 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeRequired $passwordChangeRequired ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + + Test-User -UserName $newUserName2 | Should Be $true + $testTargetResourceResult2 = + Test-TargetResource -UserName $newUserName2 ` + -Password $newCredential1 ` + -Ensure 'Present' ` + -FullName $newFullName2 ` + -Description $newUserDescription2 ` + -Disabled $disabled ` + -PasswordNeverExpires $passwordNeverExpires ` + -PasswordChangeNotAllowed $passwordChangeNotAllowed + $testTargetResourceResult2 | Should Be $true + } + } + finally + { + Remove-User -UserName $newUserName1 + Remove-User -UserName $newUserName2 + } + } } - } - It 'Test-TargetResource user absent' { - $absentUserName = "AbsentUserUserName123456789" - $testTargetResourceResult = Test-TargetResource $absentUserName - $testTargetResourceResult | Should Be $false - } + Describe 'xUserResource/Test-TargetResource' { + Context 'Tests on FullSKU' { + Mock -CommandName Test-IsNanoServer -MockWith { return $false } + $absentUserName = 'AbsentUserUserName123456789' + + It 'Should return true when user Present and correct values' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Description $existingDescription ` + -Password $existingTestCredential ` + -Disabled $false ` + -PasswordNeverExpires $false ` + -PasswordChangeNotAllowed $false + $testTargetResourceResult | Should Be $true + } + + It 'Should return true when user Absent and Ensure = Absent' { + $testTargetResourceResult = Test-TargetResource -UserName $absentUserName ` + -Ensure 'Absent' + $testTargetResourceResult | Should Be $true + } - It 'Set-TargetResource user present and ensure present' { - $testUserName = "TestUserName12345" - $testUserPassword = "StrongOne7." - $testUserDescription = "Some Description" + It 'Should return false when user Absent and Ensure = Present' { + $testTargetResourceResult = Test-TargetResource -UserName $absentUserName ` + -Ensure 'Present' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when user Present and Ensure = Absent' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Ensure 'Absent' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when Password is wrong' { + $badPassword = 'WrongPassword' + $secureBadPassword = ConvertTo-SecureString $badPassword -AsPlainText -Force + $badTestCredential = New-Object PSCredential ($existingUserName, $secureBadPassword) + + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Password $badTestCredential + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when user Present and wrong Description' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Description 'Wrong description' + $testTargetResourceResult | Should Be $false + } - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + It 'Should return false when FullName is incorrect' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -FullName 'Wrong FullName' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when Disabled is incorrect' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Disabled $true + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when PasswordNeverExpires is incorrect' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -PasswordNeverExpires $true + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when PasswordChangeNotAllowed is incorrect' { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -PasswordChangeNotAllowed $true + $testTargetResourceResult | Should Be $false + } + } + + Context 'Tests on Nano Server' { + Mock -CommandName Test-IsNanoServer -MockWith { return $true } + + $absentUserName = 'AbsentUserUserName123456789' + + It 'Should return true when user Present and correct values' -Skip:$script:skipMe { + Mock -CommandName Test-CredentialsValidOnNanoServer { return $true } + + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Description $existingDescription ` + -Password $existingTestCredential ` + -Disabled $false ` + -PasswordNeverExpires $false ` + -PasswordChangeNotAllowed $false + $testTargetResourceResult | Should Be $true + } + + It 'Should return true when user Absent and Ensure = Absent' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $absentUserName ` + -Ensure 'Absent' + $testTargetResourceResult | Should Be $true + } - New-User -Credential $testCredential -Description $testUserDescription + It 'Should return false when user Absent and Ensure = Present' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $absentUserName ` + -Ensure 'Present' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when user Present and Ensure = Absent' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Ensure 'Absent' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when Password is wrong' -Skip:$script:skipMe { + Mock -CommandName Test-CredentialsValidOnNanoServer { return $false } + + $badPassword = 'WrongPassword' + $secureBadPassword = ConvertTo-SecureString $badPassword -AsPlainText -Force + $badTestCredential = New-Object PSCredential ($existingUserName, $secureBadPassword) + + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Password $badTestCredential + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when user Present and wrong Description' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Description 'Wrong description' + $testTargetResourceResult | Should Be $false + } - try - { - $setTargetResourceResult = Set-TargetResource $testUserName -Ensure Present - Test-User -UserName $testUserName | Should Be $true + It 'Should return false when FullName is incorrect' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -FullName 'Wrong FullName' + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when Disabled is incorrect' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -Disabled $true + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when PasswordNeverExpires is incorrect' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -PasswordNeverExpires $true + $testTargetResourceResult | Should Be $false + } + + It 'Should return false when PasswordChangeNotAllowed is incorrect' -Skip:$script:skipMe { + $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` + -PasswordChangeNotAllowed $true + $testTargetResourceResult | Should Be $false + } + } } - finally - { - Remove-User -UserName $testUserName + + Describe 'xUserResource/Assert-UserNameValid' { + It 'Should not throw when username contains all valid chars' { + { Assert-UserNameValid -UserName 'abc123456!f_t-l098s' } | Should Not Throw + } + + It 'Should throw InvalidArgumentError when username contains only whitespace and dots' { + $invalidName = ' . .. . ' + $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $errorId = 'UserNameHasOnlyWhiteSpacesAndDots' + $errorMessage = "The name $invalidName cannot be used." + $exception = New-Object System.ArgumentException $errorMessage; + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null + { Assert-UserNameValid -UserName $invalidName } | Should Throw $errorRecord + } + + It 'Should throw InvalidArgumentError when username contains an invalid char' { + $invalidName = 'user|name' + $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $errorId = 'UserNameHasInvalidCharachter' + $errorMessage = "The name $invalidName cannot be used." + $exception = New-Object System.ArgumentException $errorMessage; + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $errorId, $errorCategory, $null + { Assert-UserNameValid -UserName $invalidName } | Should Throw $errorRecord + } } } - - It 'Set-TargetResource user present and ensure absent' { - $testUserName = "TestUserName12345" - $testUserPassword = "StrongOne7." - $testUserDescription = "Some Description" - - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) - - New-User -Credential $testCredential -Description $testUserDescription - - try - { - $setTargetResourceResult = Set-TargetResource $testUserName -Ensure Absent - Test-User -UserName $testUserName | Should Be $false - } - finally - { - Remove-User -UserName $testUserName - } + finally + { + Remove-User -UserName $existingUserName } } } +finally +{ + Exit-DscResourceTestEnvironment -TestEnvironment $script:testEnvironment +} +