From e856a9e78187f6c8b74074763931d530b6b7a4a5 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Tue, 27 Sep 2016 17:54:10 -0700 Subject: [PATCH 1/9] updating changes to xUser --- .../MSFT_xUserResource.psm1 | 745 ++++++++++-------- .../MSFT_xUserResource.schema.mof | 18 +- .../en-US/MSFT_xUserResource.schema.mfl | Bin 650 -> 2418 bytes .../en-US/MSFT_xUserResource.strings.psd1 | 43 +- ..._xUser.ps1 => Sample_xUser_CreateUser.ps1} | 0 Examples/Sample_xUser_Generic.ps1 | 68 ++ .../MSFT_xUserResource.Integration.Tests.ps1 | 198 +++++ .../Integration/MSFT_xUserResource.config.ps1 | 46 ++ Tests/Unit/MSFT_xUserResource.TestHelper.psm1 | 178 +++-- Tests/Unit/MSFT_xUserResource.Tests.ps1 | 523 +++++++++--- 10 files changed, 1269 insertions(+), 550 deletions(-) rename Examples/{Sample_xUser.ps1 => Sample_xUser_CreateUser.ps1} (100%) create mode 100644 Examples/Sample_xUser_Generic.ps1 create mode 100644 Tests/Integration/MSFT_xUserResource.Integration.Tests.ps1 create mode 100644 Tests/Integration/MSFT_xUserResource.config.ps1 diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 0f5585dc4..1a5758e25 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -1,38 +1,13 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUserNameAndPassWordParams", "")] # To be removed when username/password changed to a credential +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] # To be removed when username/password changed to a credential +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] # Write-Verbose Used in helper functions +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] # Should process is called in a helper functions but not directly in Set-TargetResource 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. -'@ -} - -# 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)) { Add-Type -AssemblyName 'System.DirectoryServices.AccountManagement' @@ -40,11 +15,15 @@ if (-not (Test-IsNanoServer)) <# .SYNOPSIS - The Get-TargetResource cmdlet. + Retrieves the state of a Windows optional feature resource. + + .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,44 @@ function Get-TargetResource <# .SYNOPSIS - The Set-TargetResource cmdlet. + Creates, Modifies, or Deletes a User. + + .PARAMETER UserName + The name of the user to Create/Modify/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 + Password for the user account. + + .PARAMETER Disabled + Specifies whether the accound should be disabled or not. + By default this is set to $false + + .PARAMETER PasswordNeverExpires + Specifies whether the Password expires 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. + By default this is set to $false #> function Set-TargetResource { - [CmdletBInding(SupportsShouldProcess = $true)] + [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] @@ -77,9 +89,9 @@ function Set-TargetResource [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -89,6 +101,7 @@ function Set-TargetResource [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -116,11 +129,44 @@ 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. + The Test-TargetResource cmdlet is used to validate if the resource + is in the specified 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 FullName of the user being tested. + If you don't care about what the FullName is, don't include this. + + .PARAMETER Description + The Description of the user being tested. + If you don't care about what the description is, don't include this. + + .PARAMETER Password + Password for the user account to be tested + + .PARAMETER Disabled + Specifies whether the accound should be disabled or not. + + .PARAMETER PasswordNeverExpires + Specifies whether the Password should expire or not. + + .PARAMETER PasswordChangeRequired + There's no easy way to check whether the PasswordChangeRequired is set + to true or false, so this value is not tested here. + + .PARAMETER PasswordChangeNotAllowed + Specifies whether the user should be allowed to change their password. #> function Test-TargetResource { - [OutputType([Boolean])] + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [parameter(Mandatory = $true)] @@ -128,9 +174,9 @@ function Test-TargetResource [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -140,6 +186,7 @@ function Test-TargetResource [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -168,10 +215,12 @@ function Test-TargetResource <# .SYNOPSIS - The Get-TargetResource cmdlet on a full server. + The Get-TargetResource cmdlet on a full server. #> function Get-TargetResourceOnFullSKU { + [OutputType([System.Collections.Hashtable])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -182,55 +231,58 @@ function Get-TargetResourceOnFullSKU 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 $principalContext = New-Object 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 ($user -ne $null) { - # 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-ExceptionDueToDirectoryServicesError -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. + The Set-TargetResource cmdlet on a full server. + .NOTES + $Password is required if $Ensure is set to 'Present' #> function Set-TargetResourceOnFullSKU { @@ -242,9 +294,9 @@ function Set-TargetResourceOnFullSKU [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -254,6 +306,7 @@ function Set-TargetResourceOnFullSKU [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -271,169 +324,172 @@ 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. + # Try to find a user by name. $principalContext = New-Object 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 ($user -eq $null) { - # 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. + # The user with the provided name does not exist so add a new user $user = New-Object System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $principalContext - $user.Name = $UserName; - $saveChanges = $true; + $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-ExceptionDueToDirectoryServicesError -ErrorId 'MultipleMatches' -ErrorMessage ($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. + The Test-TargetResource cmdlet on a full server. + .NOTES + There's no easy way to check whether the PasswordChangeRequired is set + to true or false, so this value is not tested here. #> function Test-TargetResourceOnFullSKU { + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -441,9 +497,9 @@ function Test-TargetResourceOnFullSKU [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -453,6 +509,7 @@ function Test-TargetResourceOnFullSKU [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -470,107 +527,116 @@ function Test-TargetResourceOnFullSKU 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 $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 ($user -eq $null) { - # 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-ExceptionDueToDirectoryServicesError -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 + The Get-TargetResource cmdlet on a Nano Server. #> function Get-TargetResourceOnNanoServer { + [OutputType([System.Collections.Hashtable])] + [CmdletBinding()] param ( [parameter(Mandatory = $true)] @@ -581,35 +647,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-TerminatingError -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 +688,15 @@ function Get-TargetResourceOnNanoServer $returnValue.Add('PasswordNeverExpires', $true) } - return $returnValue; + return $returnValue } <# .SYNOPSIS - The Set-TargetResource cmdlet on a Nano server. + The Set-TargetResource cmdlet on a Nano server. #> function Set-TargetResourceOnNanoServer { - [CmdletBinding(SupportsShouldProcess = $true)] param ( [Parameter(Mandatory = $true)] @@ -638,9 +704,9 @@ function Set-TargetResourceOnNanoServer [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -650,6 +716,7 @@ function Set-TargetResourceOnNanoServer [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -667,43 +734,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-TerminatingError -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 +789,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 +806,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 +824,61 @@ 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. + The Test-TargetResource cmdlet on a Nano server. + .NOTES + There's no easy way to check whether the PasswordChangeRequired is set + to true or false, so this value is not tested here. #> function Test-TargetResourceOnNanoServer { + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -811,9 +886,9 @@ function Test-TargetResourceOnNanoServer [System.String] $UserName, - [ValidateSet("Present", "Absent")] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', [System.String] $FullName, @@ -823,6 +898,7 @@ function Test-TargetResourceOnNanoServer [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] $Password, [System.Boolean] @@ -840,9 +916,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 +927,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 +937,81 @@ function Test-TargetResourceOnNanoServer return $false } } - Throw-TerminatingError -ErrorRecord $_ + New-TerminatingError -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-ValidCredentialsOnNanoServer -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 User name does not contain invalid characters. + + .PARAMETER UserName + The username to validate #> -function ValidateUserName +function Assert-UserNameValid { + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -933,35 +1020,42 @@ 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-InvalidArgumentError -ErrorId 'UserNameHasOnlyWhiteSpacesAndDots' -ErrorMessage ($script:localizedData.InvalidUserName -f $UserName, [String]::Join(' ', $invalidChars)) } - if($UserName.IndexOfAny($invalidChars) -ne -1) + if ($UserName.IndexOfAny($invalidChars) -ne -1) { - ThrowInvalidArgumentError -ErrorId "UserNameHasInvalidCharachter" -ErrorMessage ($LocalizedData.InvalidUserName -f $UserName, [string]::Join(" ", $invalidChars)) + New-InvalidArgumentError -ErrorId 'UserNameHasInvalidCharachter' -ErrorMessage ($script:localizedData.InvalidUserName -f $UserName, [String]::Join(' ', $invalidChars)) } } <# .SYNOPSIS - Throws an argument error. + Creates a new Invalid Argument Error record and throws it. + + .PARAMETER ErrorId + The ID for the error record to be thrown + + .PARAMETER ErrorMessage + Message to be included in the error record to be thrown #> -function ThrowInvalidArgumentError +function New-InvalidArgumentError { [CmdletBinding()] param @@ -979,12 +1073,22 @@ function ThrowInvalidArgumentError ) $errorCategory=[System.Management.Automation.ErrorCategory]::InvalidArgument - $exception = New-Object System.ArgumentException $ErrorMessage; + $exception = New-Object System.ArgumentException $ErrorMessage $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $errorCategory, $null throw $errorRecord } -function ThrowExceptionDueToDirectoryServicesError +<# + .SYNOPSIS + Creates a new Connection error record and throws it. + + .PARAMETER ErrorId + The ID for the error record to be thrown + + .PARAMETER ErrorMessage + Message to be included in the error record to be thrown +#> +function New-ExceptionDueToDirectoryServicesError { [CmdletBinding()] param @@ -1006,53 +1110,58 @@ function ThrowExceptionDueToDirectoryServicesError 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 - throw $errorRecord -} - <# .SYNOPSIS - Writes either to Verbose or ShouldProcess channel. + Create a new terminating error record and throws it. + + .PARAMETER Message + Optional message to be included in the error record to be thrown. + + .PARAMETER ErrorRecord + Optional ErrorRecord object if you want the exception from this object to + be included in the new error record to be thrown. #> -function Write-Log +function New-TerminatingError { - [CmdletBinding(SupportsShouldProcess=$true)] + [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] [System.String] - $Message + $Message, + + [System.Management.Automation.ErrorRecord] + $ErrorRecord ) - if ($PSCmdlet.ShouldProcess($Message, $null, $null)) + + if ($null -ne $ErrorRecord) + { + $exception = New-Object 'System.InvalidOperationException' $Message, $ErrorRecord.Exception + } + else { - Write-Verbose $Message + $exception = New-Object 'System.InvalidOperationException' $Message } + + $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, 'MachineStateIncorrect', 'InvalidOperation', $null + + throw $errorRecord } <# .SYNOPSIS - Validates the local user's credentials on the local machine. + Tests the local user's credentials on the local machine. + + .PARAMETER UserName + The user name to validate the credentials of. + + .PARAMETER Password + The Password of the given user. #> -function ValidateCredentialsOnNanoServer +function Test-ValidCredentialsOnNanoServer { + [OutputType([System.Boolean])] + [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -1061,7 +1170,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 962252e7caa1bb1b0c2eb318ad1c3e73aff34fa9..92809e05005678b0cda60bd15d840e353532144a 100644 GIT binary patch literal 2418 zcmc&#O;6iE6r6LV{)bgAr4k}N^wRbeQjkzdK!w5qA(S|#jb!Z94p3C_*MXV0b~ZR9 zf}|?4{IR=l-^aY!@%`Niz9L3}2swrrVa#WS6l-YU5WhGo;J7B6443?h`0dL1OU}j^ za6IO!!v%WW+rv+@NjMjAlyJpc=KSh$E;3&!cNN%Rr1zZv%)3PoF+B{->pI6BY@y2> z{reE>wA#fU+FaY^n)2-60B87!5BOxhPVp9R7$ae56wwzDT{Q2>-f+`Un5!$@Tv0J@Yy#B~&N;|JUOq`&m3zskl8A oMSszd)$4TJ@ieOHX8HC#)$IR9qibwxnPO*TskFoM;I@1}0CgvzUjP6A delta 100 zcmew))Wy2NiE*+J(>_-{h8TuCh5&{lhE#@PAkJebVNhVGW=H|D(-;yNN*Qv1BDFv< h1+Yp-h9o3)P|?Zptg?*7lYN=xIcgcK8F(4E7yxOc6ZrrD 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/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..71e1c07e6 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,15 @@ function Test-UserOnNanoServer } throw $_.Exception } + finally + { + Remove-LocalUser -Name $UserName + } return $false - - Remove-LocalUser -Name $UserName } Export-ModuleMember -Function ` New-User, ` Remove-User, ` - Test-User + Test-* diff --git a/Tests/Unit/MSFT_xUserResource.Tests.ps1 b/Tests/Unit/MSFT_xUserResource.Tests.ps1 index 2ffb69e57..6dc3a387f 100644 --- a/Tests/Unit/MSFT_xUserResource.Tests.ps1 +++ b/Tests/Unit/MSFT_xUserResource.Tests.ps1 @@ -1,149 +1,446 @@ #To run these tests, the currently logged on user must have rights to create a user -[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName 'xPSDesiredStateConfiguration' ` +Import-Module "$PSScriptRoot\..\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" - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) +try { - New-User -Credential $testCredential -Description $testUserDescription + Import-Module "$PSScriptRoot\MSFT_xUserResource.TestHelper.psm1" -Force - try - { - $getTargetResourceResult = Get-TargetResource $testUserName + 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 { - $getTargerResourceResultProperties = @('UserName', 'Ensure', 'Description', 'Disabled', 'PasswordNeverExpires', 'PasswordChangeNotAllowed') + Describe 'xUserResource/Get-TargetResource' { - Test-GetTargetResourceResult -GetTargetResourceResult $getTargetResourceResult -GetTargetResourceResultProperties $getTargerResourceResultProperties + Context 'Tests on FullSKU' { + Mock -CommandName Test-IsNanoServer -MockWith { return $false } - $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 - } - } + It 'Should return the user as Present' { + $getTargetResourceResult = Get-TargetResource $existingUserName - It 'Get-TargetResource user absent' { - $testUserName = "AbsentUserUserName123456789" + $getTargetResourceResult['UserName'] | Should Be $existingUserName + $getTargetResourceResult['Ensure'] | Should Be 'Present' + $getTargetResourceResult['Description'] | Should Be $existingDescription + $getTargetResourceResult['PasswordChangeRequired'] | Should Be $null + } - $getTargetResourceResult = Get-TargetResource $testUserName + It 'Should return the user as Absent' { + $getTargetResourceResult = Get-TargetResource 'NotAUserName' - $getTargerResourceResultProperties = @('UserName', 'Ensure') + $getTargetResourceResult['UserName'] | Should Be 'NotAUserName' + $getTargetResourceResult['Ensure'] | Should Be 'Absent' + } + } - Test-GetTargetResourceResult -GetTargetResourceResult $getTargetResourceResult -GetTargetResourceResultProperties $getTargerResourceResultProperties + Context 'Tests on Nano Server' { + Mock -CommandName Test-IsNanoServer -MockWith { return $true } - $getTargetResourceResult["UserName"] | Should Be $TestUserName - $getTargetResourceResult["Ensure"] | Should Be "Absent" - } + It 'Should return the user as Present on Nano Server' -Skip:$script:skipMe { + $getTargetResourceResult = Get-TargetResource $existingUserName - It 'Test-TargetResource user present and correct description' { - $testUserName = "TestUserName12345" - $testUserPassword = "StrongOne7." - $testUserDescription = "Some Description" + $getTargetResourceResult['UserName'] | Should Be $existingUserName + $getTargetResourceResult['Ensure'] | Should Be 'Present' + $getTargetResourceResult['Description'] | Should Be $existingDescription + $getTargetResourceResult['PasswordChangeRequired'] | Should Be $null + } - $secureTestPassword = ConvertTo-SecureString $testUserPassword -AsPlainText -Force - $testCredential = New-Object PSCredential ($testUserName, $secureTestPassword) + It 'Should return the user as Absent' -Skip:$script:skipMe { + $getTargetResourceResult = Get-TargetResource 'NotAUserName' - New-User -Credential $testCredential -Description $testUserDescription - - try - { - $testTargetResourceResult = Test-TargetResource $testUserName -Description $testUserDescription - $testTargetResourceResult | Should Be $true - } - finally - { - Remove-User -UserName $testUserName + $getTargetResourceResult['UserName'] | Should Be 'NotAUserName' + $getTargetResourceResult['Ensure'] | Should Be 'Absent' + } + } } - } - - 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) - New-User -Credential $testCredential -Description $testUserDescription + 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 + } + } - try - { - $testTargetResourceResult = Test-TargetResource $testUserName -Description "Wrong description" - $testTargetResourceResult | Should Be $false + Context 'Tests on Nano Server' { + Mock -CommandName Test-IsNanoServer -MockWith { return $true } + Mock -CommandName Test-ValidCredentialsOnNanoServer { 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 + } + } } - finally - { - Remove-User -UserName $testUserName - } - } - 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-ValidCredentialsOnNanoServer { 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-ValidCredentialsOnNanoServer { 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 +} + From 60e3e7f64736cf943add0746748a94bac79c0775 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Tue, 27 Sep 2016 18:04:46 -0700 Subject: [PATCH 2/9] updating readme --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa0d851f3..134156c4b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Please check out common DSC Resources [contributing guidelines](https://github.c Resources that work on Nano Server: * xWindowsOptionalFeature +* xUser ### xArchive @@ -365,8 +366,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 From 7397ce2395f95b5b0b695c95a8092824a70d6e91 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Thu, 29 Sep 2016 18:26:46 -0700 Subject: [PATCH 3/9] first few changes from review --- .../MSFT_xUserResource.psm1 | 54 ++++++++++--------- README.md | 1 + 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 1a5758e25..17c41f550 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -1,4 +1,4 @@ -[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 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] # Should process is called in a helper functions but not directly in Set-TargetResource param () @@ -15,7 +15,7 @@ if (-not (Test-IsNanoServer)) <# .SYNOPSIS - Retrieves the state of a Windows optional feature resource. + Retrieves the user with the given Username .PARAMETER UserName The name of the user to retrieve. @@ -44,10 +44,10 @@ function Get-TargetResource <# .SYNOPSIS - Creates, Modifies, or Deletes a User. + Creates, modifies, or deletes a User. .PARAMETER UserName - The name of the user to Create/Modify/Delete. + The name of the user to create, modify, or delete. .PARAMETER Ensure Specifies whether the user should exist or not. @@ -61,14 +61,14 @@ function Get-TargetResource Optional description for the user. .PARAMETER Password - Password for the user account. + The desired password for the user. .PARAMETER Disabled - Specifies whether the accound should be disabled or not. + Specifies whether the user should be disabled or not. By default this is set to $false .PARAMETER PasswordNeverExpires - Specifies whether the Password expires or not. + Specifies whether the Password should ever expire or not. By default this is set to $false .PARAMETER PasswordChangeRequired @@ -76,7 +76,7 @@ function Get-TargetResource By default this is set to $false .PARAMETER PasswordChangeNotAllowed - Specifies whether the user is allowed to change their password. + Specifies whether the user is allowed to change their password or not. By default this is set to $false #> function Set-TargetResource @@ -129,8 +129,7 @@ function Set-TargetResource <# .SYNOPSIS - The Test-TargetResource cmdlet is used to validate if the resource - is in the specified state. + Tests if a user is in the desired state. .PARAMETER UserName The name of the user to test the state of. @@ -140,28 +139,27 @@ function Set-TargetResource By default this is set to Present .PARAMETER FullName - The FullName of the user being tested. - If you don't care about what the FullName is, don't include this. + The full name/display name that the user should have.. + If not provided, this value will not be tested. .PARAMETER Description - The Description of the user being tested. - If you don't care about what the description is, don't include this. + The Description that the user should have. + If not provided, this value will not be tested. .PARAMETER Password - Password for the user account to be tested + The password the user should have. .PARAMETER Disabled - Specifies whether the accound should be disabled or not. + Specifies whether the user account should be disabled or not. .PARAMETER PasswordNeverExpires - Specifies whether the Password should expire or not. + Specifies whether the Password should ever expire or not. .PARAMETER PasswordChangeRequired - There's no easy way to check whether the PasswordChangeRequired is set - to true or false, so this value is not tested here. + 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. + Specifies whether the user should be allowed to change their password or not. #> function Test-TargetResource { @@ -169,7 +167,7 @@ function Test-TargetResource [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $UserName, @@ -234,13 +232,15 @@ function Get-TargetResourceOnFullSKU 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) + $principalContext = New-Object + -TypeName System.DirectoryServices.AccountManagement.PrincipalContext + -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) try { Write-Verbose -Message 'Starting Get-TargetResource on FullSKU' $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($principalContext, $UserName) - if ($user -ne $null) + if ($null -ne $user) { # The user is found. Return all user properties and Ensure='Present'. $returnValue = @{ @@ -330,7 +330,9 @@ function Set-TargetResourceOnFullSKU # Try to find a user by name. - $principalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) + $principalContext = New-Object ` + -TypeName System.DirectoryServices.AccountManagement.PrincipalContext ` + -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) try { @@ -360,7 +362,9 @@ function Set-TargetResourceOnFullSKU if (-not $userExists) { # The user with the provided name does not exist so add a new user - $user = New-Object System.DirectoryServices.AccountManagement.UserPrincipal -ArgumentList $principalContext + $user = New-Object ` + -TypeName System.DirectoryServices.AccountManagement.UserPrincipal ` + -ArgumentList $principalContext $user.Name = $UserName $saveChanges = $true } diff --git a/README.md b/README.md index 134156c4b..4106e01e2 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. From 42d51d9f2543124d3bc3987507fea3293a378127 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Mon, 3 Oct 2016 13:00:38 -0700 Subject: [PATCH 4/9] more fixes from review and typo fix that was causing build to break --- .../MSFT_xUserResource.psm1 | 175 ++++++++++++++++-- README.md | 26 ++- 2 files changed, 172 insertions(+), 29 deletions(-) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 17c41f550..80b4702b8 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -1,9 +1,9 @@ [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] # User name and password needed for this resource [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSDSCUseVerboseMessageInDSCResource', '')] # Write-Verbose Used in helper functions -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] # Should process is called in a helper functions but not directly in Set-TargetResource param () -Import-Module "$PSScriptRoot\..\CommonResourceHelper.psm1" +Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) ` + -ChildPath 'CommonResourceHelper.psm1') # Localized messages for Write-Verbose statements in this resource $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xUserResource' @@ -78,9 +78,14 @@ function Get-TargetResource .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 { + # Should process is called in a helper functions but not directly in Set-TargetResource + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [CmdletBinding(SupportsShouldProcess = $true)] param ( @@ -213,7 +218,10 @@ 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 { @@ -232,8 +240,8 @@ function Get-TargetResourceOnFullSKU Assert-UserNameValid -UserName $UserName # Try to find a user by a name - $principalContext = New-Object - -TypeName System.DirectoryServices.AccountManagement.PrincipalContext + $principalContext = New-Object ` + -TypeName System.DirectoryServices.AccountManagement.PrincipalContext ` -ArgumentList ([System.DirectoryServices.AccountManagement.ContextType]::Machine) try @@ -280,9 +288,43 @@ function Get-TargetResourceOnFullSKU <# .SYNOPSIS - The Set-TargetResource cmdlet on a full server. - .NOTES - $Password is required if $Ensure is set to 'Present' + 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 { @@ -485,10 +527,37 @@ function Set-TargetResourceOnFullSKU <# .SYNOPSIS - The Test-TargetResource cmdlet on a full server. - .NOTES - There's no easy way to check whether the PasswordChangeRequired is set - to true or false, so this value is not tested here. + 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 { @@ -635,7 +704,10 @@ function Test-TargetResourceOnFullSKU <# .SYNOPSIS - The Get-TargetResource cmdlet on a Nano Server. + Retrieves the user with the given Username when on a nano server. + + .PARAMETER UserName + The name of the user to retrieve. #> function Get-TargetResourceOnNanoServer { @@ -697,7 +769,43 @@ function Get-TargetResourceOnNanoServer <# .SYNOPSIS - The Set-TargetResource cmdlet on a Nano server. + Creates, modifies, or deletes a User when on a 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 { @@ -874,10 +982,37 @@ function Set-TargetResourceOnNanoServer <# .SYNOPSIS - The Test-TargetResource cmdlet on a Nano server. - .NOTES - There's no easy way to check whether the PasswordChangeRequired is set - to true or false, so this value is not tested here. + Tests if a user is in the desired state when on a 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 { @@ -971,7 +1106,7 @@ function Test-TargetResourceOnNanoServer if ($PSBoundParameters.ContainsKey('Password')) { - if(-not (Test-ValidCredentialsOnNanoServer -UserName $UserName -Password $Password.Password)) + if(-not (Test-CredentialsValidOnNanoServer -UserName $UserName -Password $Password.Password)) { # The Password property does not match Write-Verbose -Message ($script:localizedData.PasswordPropertyMismatch -f 'Password') @@ -1162,7 +1297,7 @@ function New-TerminatingError .PARAMETER Password The Password of the given user. #> -function Test-ValidCredentialsOnNanoServer +function Test-CredentialsValidOnNanoServer { [OutputType([System.Boolean])] [CmdletBinding()] diff --git a/README.md b/README.md index 4106e01e2..507f2586d 100644 --- a/README.md +++ b/README.md @@ -214,23 +214,31 @@ 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**: Indicates the account name for which you want to ensure a specific state. +* **[String] Description**: Indicates the description you want to use for the user account. +* **[Boolean] 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. - Suported values: $true, $false - Default value: $false -* **Ensure**: Ensures that the feature is present or absent. +* **[String] Ensure**: 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**: Represents a string with the full name you want to use for the user account. +* **[PSCredential] Password**: Indicates the password you want to use for this account. +* **[Boolean] 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. - 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**: 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**: 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 From 91564ad94c53dfadad0d560b404a7fe8bc5a0305 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Mon, 3 Oct 2016 13:44:00 -0700 Subject: [PATCH 5/9] finished fixes from review --- .../MSFT_xUserResource.psm1 | 101 ++++-------------- Tests/Unit/MSFT_xUserResource.Tests.ps1 | 6 +- 2 files changed, 22 insertions(+), 85 deletions(-) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 80b4702b8..52e3df76e 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -273,7 +273,7 @@ function Get-TargetResourceOnFullSKU } catch { - New-ExceptionDueToDirectoryServicesError -ErrorId 'MultipleMatches' -ErrorMessage ($script:localizedData.MultipleMatches + $_) + New-ConnectionException -ErrorId 'MultipleMatches' -ErrorMessage ($script:localizedData.MultipleMatches + $_) } finally { @@ -510,7 +510,7 @@ function Set-TargetResourceOnFullSKU } catch { - New-ExceptionDueToDirectoryServicesError -ErrorId 'MultipleMatches' -ErrorMessage ($script:localizedData.MultipleMatches + $_) + New-InvlaidOperationException -Message ($script:localizedData.MultipleMatches + $_) } finally { @@ -682,7 +682,7 @@ function Test-TargetResourceOnFullSKU } catch { - New-ExceptionDueToDirectoryServicesError -ErrorId 'ConnectionError' -ErrorMessage ($script:localizedData.ConnectionError + $_) + New-ConnectionException -ErrorId 'ConnectionError' -ErrorMessage ($script:localizedData.ConnectionError + $_) } finally @@ -741,7 +741,7 @@ function Get-TargetResourceOnNanoServer Ensure = 'Absent' } } - New-TerminatingError -ErrorRecord $_ + New-InvalidOperationException -ErrorRecord $_ } # The user is found. Return all user properties and Ensure='Present'. @@ -867,7 +867,7 @@ function Set-TargetResourceOnNanoServer } else { - New-TerminatingError -ErrorRecord $_ + New-InvalidOperationException -ErrorRecord $_ } } @@ -1076,7 +1076,7 @@ function Test-TargetResourceOnNanoServer return $false } } - New-TerminatingError -ErrorRecord $_ + New-InvlaidOperationException -ErrorRecord $_ } # A user with the provided name exists @@ -1175,48 +1175,19 @@ function Assert-UserNameValid if ($wrongName) { - New-InvalidArgumentError -ErrorId 'UserNameHasOnlyWhiteSpacesAndDots' -ErrorMessage ($script: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) { - New-InvalidArgumentError -ErrorId 'UserNameHasInvalidCharachter' -ErrorMessage ($script:localizedData.InvalidUserName -f $UserName, [String]::Join(' ', $invalidChars)) + New-InvalidArgumentException ` + -Message ($script:localizedData.InvalidUserName -f $UserName, [String]::Join(' ', $invalidChars)) ` + -ArgumentName 'UserName' } } -<# - .SYNOPSIS - Creates a new Invalid Argument Error record and throws it. - - .PARAMETER ErrorId - The ID for the error record to be thrown - - .PARAMETER ErrorMessage - Message to be included in the error record to be thrown -#> -function New-InvalidArgumentError -{ - [CmdletBinding()] - param - ( - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorMessage - ) - - $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 -} - <# .SYNOPSIS Creates a new Connection error record and throws it. @@ -1227,7 +1198,7 @@ function New-InvalidArgumentError .PARAMETER ErrorMessage Message to be included in the error record to be thrown #> -function New-ExceptionDueToDirectoryServicesError +function New-ConnectionException { [CmdletBinding()] param @@ -1244,46 +1215,12 @@ function New-ExceptionDueToDirectoryServicesError ) $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 -} - -<# - .SYNOPSIS - Create a new terminating error record and throws it. - - .PARAMETER Message - Optional message to be included in the error record to be thrown. - - .PARAMETER ErrorRecord - Optional ErrorRecord object if you want the exception from this object to - be included in the new error record to be thrown. -#> -function New-TerminatingError -{ - [CmdletBinding()] - param - ( - [System.String] - $Message, - - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - - if ($null -ne $ErrorRecord) - { - $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 } diff --git a/Tests/Unit/MSFT_xUserResource.Tests.ps1 b/Tests/Unit/MSFT_xUserResource.Tests.ps1 index 6dc3a387f..bdd44a4a5 100644 --- a/Tests/Unit/MSFT_xUserResource.Tests.ps1 +++ b/Tests/Unit/MSFT_xUserResource.Tests.ps1 @@ -172,7 +172,7 @@ try { Context 'Tests on Nano Server' { Mock -CommandName Test-IsNanoServer -MockWith { return $true } - Mock -CommandName Test-ValidCredentialsOnNanoServer { return $true } + Mock -CommandName Test-CredentialsValidOnNanoServer { return $true } try { @@ -334,7 +334,7 @@ try { $absentUserName = 'AbsentUserUserName123456789' It 'Should return true when user Present and correct values' -Skip:$script:skipMe { - Mock -CommandName Test-ValidCredentialsOnNanoServer { return $true } + Mock -CommandName Test-CredentialsValidOnNanoServer { return $true } $testTargetResourceResult = Test-TargetResource -UserName $existingUserName ` -Description $existingDescription ` @@ -364,7 +364,7 @@ try { } It 'Should return false when Password is wrong' -Skip:$script:skipMe { - Mock -CommandName Test-ValidCredentialsOnNanoServer { return $false } + Mock -CommandName Test-CredentialsValidOnNanoServer { return $false } $badPassword = 'WrongPassword' $secureBadPassword = ConvertTo-SecureString $badPassword -AsPlainText -Force From 1ffe216f9dc6a1a7ad904b6db9fdde16c6fd71e3 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Tue, 4 Oct 2016 09:23:38 -0700 Subject: [PATCH 6/9] changes from review and fixed typo that was causing build to break --- DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 | 4 ++-- Tests/Unit/MSFT_xUserResource.TestHelper.psm1 | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 52e3df76e..54bbfe0d3 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -510,7 +510,7 @@ function Set-TargetResourceOnFullSKU } catch { - New-InvlaidOperationException -Message ($script:localizedData.MultipleMatches + $_) + New-InvalidOperationException -Message ($script:localizedData.MultipleMatches + $_) } finally { @@ -1076,7 +1076,7 @@ function Test-TargetResourceOnNanoServer return $false } } - New-InvlaidOperationException -ErrorRecord $_ + New-InvalidOperationException -ErrorRecord $_ } # A user with the provided name exists diff --git a/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 b/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 index 71e1c07e6..1ed7d5a3d 100644 --- a/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 +++ b/Tests/Unit/MSFT_xUserResource.TestHelper.psm1 @@ -464,4 +464,5 @@ function Test-UserOnNanoServer Export-ModuleMember -Function ` New-User, ` Remove-User, ` - Test-* + Test-IsLocalMachine, ` + Test-User From 27d97fbf55df794b7badba8217b553b5c4cf9496 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Thu, 6 Oct 2016 16:40:52 -0700 Subject: [PATCH 7/9] review changes --- README.md | 22 +++++++++++++--------- Tests/Unit/MSFT_xUserResource.Tests.ps1 | 12 +++++++----- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 507f2586d..3898f3db3 100644 --- a/README.md +++ b/README.md @@ -222,25 +222,29 @@ Provides a mechanism to manage local users on a target node. #### Parameters -* **[String] UserName**: Indicates the account name for which you want to ensure a specific state. -* **[String] Description**: Indicates the description you want to use for the user account. -* **[Boolean] 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. +* **[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 -* **[String] 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 -* **[String] FullName**: Represents a string with the full name you want to use for the user account. -* **[PSCredential] Password**: Indicates the password you want to use for this account. -* **[Boolean] 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 -* **[Boolean] 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 -* **[Boolean] 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. diff --git a/Tests/Unit/MSFT_xUserResource.Tests.ps1 b/Tests/Unit/MSFT_xUserResource.Tests.ps1 index bdd44a4a5..3db55547e 100644 --- a/Tests/Unit/MSFT_xUserResource.Tests.ps1 +++ b/Tests/Unit/MSFT_xUserResource.Tests.ps1 @@ -1,19 +1,21 @@ -#To run these tests, the currently logged on user must have rights to create a user +# To run these tests, the currently logged on user must have rights to create a user [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param () -Import-Module "$PSScriptRoot\..\CommonTestHelper.psm1" -Force +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 - - try { - Import-Module "$PSScriptRoot\MSFT_xUserResource.TestHelper.psm1" -Force + Import-Module -Name (Join-Path -Path (Split-Path $PSScriptRoot -Parent) ` + -ChildPath 'MSFT_xUserResource.TestHelper.psm1') ` + -Force InModuleScope 'MSFT_xUserResource' { # Used to skip the Nano server tests for the time being since they are not working on AppVeyor From 32ef66d3d51698289ca3d6a42028ea9ff3ae3bf0 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Thu, 6 Oct 2016 17:29:44 -0700 Subject: [PATCH 8/9] more review changes --- .../MSFT_xUserResource.psm1 | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 54bbfe0d3..77713a0f4 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -15,7 +15,7 @@ if (-not (Test-IsNanoServer)) <# .SYNOPSIS - Retrieves the user with the given Username + Retrieves the user with the given username .PARAMETER UserName The name of the user to retrieve. @@ -44,14 +44,14 @@ function Get-TargetResource <# .SYNOPSIS - Creates, modifies, or deletes a User. + 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 + By default this is set to Present. .PARAMETER FullName The (optional) full name or display name of the user. @@ -68,7 +68,7 @@ function Get-TargetResource By default this is set to $false .PARAMETER PasswordNeverExpires - Specifies whether the Password should ever expire or not. + Specifies whether the password should ever expire or not. By default this is set to $false .PARAMETER PasswordChangeRequired @@ -80,7 +80,7 @@ function Get-TargetResource By default this is set to $false .NOTES - If Ensure is set to 'Present' then the Password parameter is required. + If Ensure is set to 'Present' then the password parameter is required. #> function Set-TargetResource { @@ -144,11 +144,11 @@ function Set-TargetResource By default this is set to Present .PARAMETER FullName - The full name/display name that the user should have.. + 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. + The description that the user should have. If not provided, this value will not be tested. .PARAMETER Password @@ -158,7 +158,7 @@ function Set-TargetResource Specifies whether the user account should be disabled or not. .PARAMETER PasswordNeverExpires - Specifies whether the Password should ever expire or not. + 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. @@ -218,7 +218,7 @@ function Test-TargetResource <# .SYNOPSIS - Retrieves the user with the given Username when 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. @@ -288,7 +288,7 @@ function Get-TargetResourceOnFullSKU <# .SYNOPSIS - Creates, modifies, or deletes a User when 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. @@ -312,7 +312,7 @@ function Get-TargetResourceOnFullSKU By default this is set to $false .PARAMETER PasswordNeverExpires - Specifies whether the Password should ever expire or not. + Specifies whether the password should ever expire or not. By default this is set to $false .PARAMETER PasswordChangeRequired @@ -385,7 +385,7 @@ function Set-TargetResourceOnFullSKU $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($script:localizedData.UserWithName -f $UserName, $script:localizedData.AddOperation) @@ -537,11 +537,11 @@ function Set-TargetResourceOnFullSKU By default this is set to Present .PARAMETER FullName - The full name/display name that the user should have.. + 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. + The description that the user should have. If not provided, this value will not be tested. .PARAMETER Password @@ -551,7 +551,7 @@ function Set-TargetResourceOnFullSKU Specifies whether the user account should be disabled or not. .PARAMETER PasswordNeverExpires - Specifies whether the Password should ever expire or not. + 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. @@ -603,12 +603,13 @@ function Test-TargetResourceOnFullSKU 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) + $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) + if ($null -eq $user) { # A user with the provided name does not exist Write-Verbose -Message ($script:localizedData.UserDoesNotExist -f $UserName) @@ -704,7 +705,7 @@ function Test-TargetResourceOnFullSKU <# .SYNOPSIS - Retrieves the user with the given Username when on a nano server. + Retrieves the user with the given username when on a Nano Server. .PARAMETER UserName The name of the user to retrieve. @@ -744,7 +745,7 @@ function Get-TargetResourceOnNanoServer 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' @@ -769,7 +770,7 @@ function Get-TargetResourceOnNanoServer <# .SYNOPSIS - Creates, modifies, or deletes a User when on a nano server. + Creates, modifies, or deletes a user when on a Nano Server. .PARAMETER UserName The name of the user to create, modify, or delete. @@ -793,7 +794,7 @@ function Get-TargetResourceOnNanoServer By default this is set to $false .PARAMETER PasswordNeverExpires - Specifies whether the Password should ever expire or not. + Specifies whether the password should ever expire or not. By default this is set to $false .PARAMETER PasswordChangeRequired @@ -982,7 +983,7 @@ function Set-TargetResourceOnNanoServer <# .SYNOPSIS - Tests if a user is in the desired state when on a nano server. + Tests if a user is in the desired state when on a Nano Server. .PARAMETER UserName The name of the user to test the state of. @@ -992,11 +993,11 @@ function Set-TargetResourceOnNanoServer By default this is set to Present .PARAMETER FullName - The full name/display name that the user should have.. + 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. + The description that the user should have. If not provided, this value will not be tested. .PARAMETER Password @@ -1006,7 +1007,7 @@ function Set-TargetResourceOnNanoServer Specifies whether the user account should be disabled or not. .PARAMETER PasswordNeverExpires - Specifies whether the Password should ever expire or not. + 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. @@ -1143,10 +1144,10 @@ function Test-TargetResourceOnNanoServer <# .SYNOPSIS - Checks that the User name does not contain invalid characters. + Checks that the username does not contain invalid characters. .PARAMETER UserName - The username to validate + The username to validate. #> function Assert-UserNameValid { @@ -1193,10 +1194,10 @@ function Assert-UserNameValid Creates a new Connection error record and throws it. .PARAMETER ErrorId - The ID for the error record to be thrown + The ID for the error record to be thrown. .PARAMETER ErrorMessage - Message to be included in the error record to be thrown + Message to be included in the error record to be thrown. #> function New-ConnectionException { @@ -1229,10 +1230,10 @@ function New-ConnectionException Tests the local user's credentials on the local machine. .PARAMETER UserName - The user name to validate the credentials of. + The username to validate the credentials of. .PARAMETER Password - The Password of the given user. + The password of the given user. #> function Test-CredentialsValidOnNanoServer { From 6dac6b4d801727fc5ab02391bd96595c575bbfd5 Mon Sep 17 00:00:00 2001 From: Mariah Breakey Date: Fri, 7 Oct 2016 10:46:35 -0700 Subject: [PATCH 9/9] changing to Nano Server instead of 'a Nano Server' --- DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 index 77713a0f4..5a5602608 100644 --- a/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 +++ b/DSCResources/MSFT_xUserResource/MSFT_xUserResource.psm1 @@ -705,7 +705,7 @@ function Test-TargetResourceOnFullSKU <# .SYNOPSIS - Retrieves the user with the given username when on a Nano Server. + Retrieves the user with the given username when on Nano Server. .PARAMETER UserName The name of the user to retrieve. @@ -770,7 +770,7 @@ function Get-TargetResourceOnNanoServer <# .SYNOPSIS - Creates, modifies, or deletes a user when 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. @@ -983,7 +983,7 @@ function Set-TargetResourceOnNanoServer <# .SYNOPSIS - Tests if a user is in the desired state when 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.