diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..67a3f8b49 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +DSCResource.Tests/ diff --git a/DSCResource.Tests b/DSCResource.Tests deleted file mode 160000 index b367d4bea..000000000 --- a/DSCResource.Tests +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b367d4beaaa93c8c2f5871a71fcf5e1090c8a032 diff --git a/DSCResources/MSFT_xADCommon/MSFT_xADCommon.ps1 b/DSCResources/MSFT_xADCommon/MSFT_xADCommon.ps1 index 6c44915cb..ba73217fe 100644 --- a/DSCResources/MSFT_xADCommon/MSFT_xADCommon.ps1 +++ b/DSCResources/MSFT_xADCommon/MSFT_xADCommon.ps1 @@ -1,4 +1,4 @@ -data localizedString +data localizedString { # culture="en-US" ConvertFrom-StringData @' @@ -282,8 +282,12 @@ function Test-Members $MembersToExclude ) - if ($Members.Count -gt 0) + if ($PSBoundParameters.ContainsKey('Members')) { + if ($null -eq $Members) + { + $Members = @(); + } Write-Verbose ($localizedString.CheckingMembers -f 'Explicit'); $Members = [System.String[]] @(Remove-DuplicateMembers -Members $Members); if ($ExistingMembers.Count -ne $Members.Count) @@ -296,14 +300,18 @@ function Test-Members { if ($member -notin $ExistingMembers) { - Write-Verbose -Message ($Localizeda.MemberNotInDesiredState -f $member);; + Write-Verbose -Message ($localizedString.MemberNotInDesiredState -f $member); return $false; } } } #end if $Members - if ($MembersToInclude.Count -gt 0) - { + if ($PSBoundParameters.ContainsKey('MembersToInclude')) + { + if ($null -eq $MembersToInclude) + { + $MembersToInclude = @(); + } Write-Verbose -Message ($localizedString.CheckingMembers -f 'Included'); $MembersToInclude = [System.String[]] @(Remove-DuplicateMembers -Members $MembersToInclude); foreach ($member in $MembersToInclude) @@ -316,8 +324,13 @@ function Test-Members } } #end if $MembersToInclude - if ($MembersToExclude.Count -gt 0) + #if ($MembersToExclude.Count -gt 0) + if ($PSBoundParameters.ContainsKey('MembersToExclude')) { + if ($null -eq $MembersToExclude) + { + $MembersToExclude = @(); + } Write-Verbose -Message ($localizedString.CheckingMembers -f 'Excluded'); $MembersToExclude = [System.String[]] @(Remove-DuplicateMembers -Members $MembersToExclude); foreach ($member in $MembersToExclude) @@ -429,7 +442,7 @@ function Get-ADCommonParameters ( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] - [Alias('UserName','GroupName')] + [Alias('UserName','GroupName','ComputerName')] [System.String] $Identity, diff --git a/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 b/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 new file mode 100644 index 000000000..c53c12499 --- /dev/null +++ b/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 @@ -0,0 +1,470 @@ +$moduleRoot = Split-Path -Path $MyInvocation.MyCommand.Path -Parent +#region LocalizedData +$culture = 'en-us' +if (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath $PSUICulture)) +{ + $culture = $PSUICulture +} +$importLocalizedDataParams = @{ + BindingVariable = 'LocalizedData' + Filename = 'MSFT_xADComputer.psd1' + BaseDirectory = $moduleRoot + UICulture = $culture +} +Import-LocalizedData @importLocalizedDataParams +#endregion + +## Create a property map that maps the DSC resource parameters to the +## Active Directory computer attributes. +$adPropertyMap = @( + @{ Parameter = 'ComputerName'; ADProperty = 'cn'; } + @{ Parameter = 'Location'; } + @{ Parameter = 'DnsHostName'; } + @{ Parameter = 'ServicePrincipalNames'; } + @{ Parameter = 'UserPrincipalName'; } + @{ Parameter = 'DisplayName'; } + @{ Parameter = 'Path'; ADProperty = 'distinguishedName'; } + @{ Parameter = 'Description'; } + @{ Parameter = 'Enabled'; } + @{ Parameter = 'Manager'; ADProperty = 'managedBy'; } +) + + +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + # Common Name + [Parameter(Mandatory)] + [System.String] $ComputerName, + + [ValidateSet('Present', 'Absent')] + [System.String] $Ensure = 'Present', + + [ValidateNotNull()] + [System.String] $UserPrincipalName, + + [ValidateNotNull()] + [System.String] $DisplayName, + + [ValidateNotNull()] + [System.String] $Path, + + [ValidateNotNull()] + [System.String] $Location, + + [ValidateNotNull()] + [System.String] $DnsHostName, + + [ValidateNotNull()] + [System.String[]] $ServicePrincipalNames, + + [ValidateNotNull()] + [System.String] $Description, + + ## Computer's manager specified as a Distinguished Name (DN) + [ValidateNotNull()] + [System.String] $Manager, + + [ValidateNotNull()] + [System.Boolean] $Enabled = $true, + + [ValidateNotNull()] + [System.String] $DomainController, + + ## Ideally this should just be called 'Credential' but is here for consistency with xADUser + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.CredentialAttribute()] + $DomainAdministratorCredential + ) + + Assert-Module -ModuleName 'ActiveDirectory'; + Import-Module -Name 'ActiveDirectory' -Verbose:$false; + + try + { + $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; + + $adProperties = @(); + ## Create an array of the AD property names to retrieve from the property map + foreach ($property in $adPropertyMap) + { + + if ($property.ADProperty) + { + $adProperties += $property.ADProperty; + } + else + { + $adProperties += $property.Parameter; + } + } + + Write-Verbose -Message ($LocalizedData.RetrievingADComputer -f $ComputerName); + $adComputer = Get-ADComputer @adCommonParameters -Properties $adProperties; + Write-Verbose -Message ($LocalizedData.ADComputerIsPresent -f $ComputerName); + $Ensure = 'Present'; + } + catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] + { + Write-Verbose -Message ($LocalizedData.ADComputerNotPresent -f $ComputerName); + $Ensure = 'Absent'; + } + catch + { + Write-Error -Message ($LocalizedData.RetrievingADComputerError -f $ComputerName); + throw $_; + } + + $targetResource = @{ + ComputerName = $ComputerName; + DistinguishedName = $adComputer.DistinguishedName; ## Read-only property + SID = $adComputer.SID; ## Read-only property + Ensure = $Ensure; + DomainController = $DomainController; + } + + ## Retrieve each property from the ADPropertyMap and add to the hashtable + foreach ($property in $adPropertyMap) + { + $propertyName = $property.Parameter; + if ($propertyName -eq 'Path') { + ## The path returned is not the parent container + if (-not [System.String]::IsNullOrEmpty($adComputer.DistinguishedName)) + { + $targetResource['Path'] = Get-ADObjectParentDN -DN $adComputer.DistinguishedName; + } + } + elseif ($property.ADProperty) + { + ## The AD property name is different to the function parameter to use this + $targetResource[$propertyName] = $adComputer.($property.ADProperty); + } + else + { + ## The AD property name matches the function parameter + if ($adComputer.$propertyName -is [Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]) + { + $targetResource[$propertyName] = $adComputer.$propertyName -as [System.String[]]; + } + else + { + $targetResource[$propertyName] = $adComputer.$propertyName; + } + } + } + return $targetResource; + +} #end function Get-TargetResource + + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + # Common Name + [Parameter(Mandatory)] + [System.String] $ComputerName, + + [ValidateSet('Present', 'Absent')] + [System.String] $Ensure = 'Present', + + [ValidateNotNull()] + [System.String] $UserPrincipalName, + + [ValidateNotNull()] + [System.String] $DisplayName, + + [ValidateNotNull()] + [System.String] $Path, + + [ValidateNotNull()] + [System.String] $Location, + + [ValidateNotNull()] + [System.String] $DnsHostName, + + [ValidateNotNull()] + [System.String[]] $ServicePrincipalNames, + + [ValidateNotNull()] + [System.String] $Description, + + ## Computer's manager specified as a Distinguished Name (DN) + [ValidateNotNull()] + [System.String] $Manager, + + [ValidateNotNull()] + [System.Boolean] $Enabled = $true, + + [ValidateNotNull()] + [System.String] $DomainController, + + ## Ideally this should just be called 'Credential' but is here for backwards compatibility + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.CredentialAttribute()] + $DomainAdministratorCredential + ) + + $targetResource = Get-TargetResource @PSBoundParameters; + $isCompliant = $true; + + if ($Ensure -eq 'Absent') + { + if ($targetResource.Ensure -eq 'Present') + { + Write-Verbose -Message ($LocalizedData.ADComputerNotDesiredPropertyState -f ` + 'Ensure', $PSBoundParameters.Ensure, $targetResource.Ensure); + $isCompliant = $false; + } + } + else + { + ## Add ensure and enabled as they may not be explicitly passed and we want to enumerate them + $PSBoundParameters['Ensure'] = $Ensure; + $PSBoundParameters['Enabled'] = $Enabled; + + foreach ($parameter in $PSBoundParameters.Keys) + { + if ($targetResource.ContainsKey($parameter)) + { + ## This check is required to be able to explicitly remove values with an empty string, if required + if (([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) -and + ([System.String]::IsNullOrEmpty($targetResource.$parameter))) + { + # Both values are null/empty and therefore we are compliant + } + elseif ($parameter -eq 'ServicePrincipalNames') + { + $testMembersParams = @{ + ExistingMembers = $targetResource.ServicePrincipalNames -as [System.String[]]; + Members = $ServicePrincipalNames; + } + if (-not (Test-Members @testMembersParams)) + { + $existingSPNs = $testMembersParams['ExistingMembers'] -join ','; + $desiredSPNs = $ServicePrincipalNames -join ','; + Write-Verbose -Message ($LocalizedData.ADComputerNotDesiredPropertyState -f ` + 'ServicePrincipalNames', $desiredSPNs, $existingSPNs); + $isCompliant = $false; + } + } + elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter) + { + Write-Verbose -Message ($LocalizedData.ADComputerNotDesiredPropertyState -f ` + $parameter, $PSBoundParameters.$parameter, $targetResource.$parameter); + $isCompliant = $false; + } + } + } #end foreach PSBoundParameter + } + + if ($isCompliant) + { + Write-Verbose -Message ($LocalizedData.ADComputerInDesiredState -f $ComputerName) + return $true + } + else + { + Write-Verbose -Message ($LocalizedData.ADComputerNotInDesiredState -f $ComputerName) + return $false + } + +} #end function Test-TargetResource + + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + # Common Name + [Parameter(Mandatory)] + [System.String] $ComputerName, + + [ValidateSet('Present', 'Absent')] + [System.String] $Ensure = 'Present', + + [ValidateNotNull()] + [System.String] $UserPrincipalName, + + [ValidateNotNull()] + [System.String] $DisplayName, + + [ValidateNotNull()] + [System.String] $Path, + + [ValidateNotNull()] + [System.String] $Location, + + [ValidateNotNull()] + [System.String] $DnsHostName, + + [ValidateNotNull()] + [System.String[]] $ServicePrincipalNames, + + [ValidateNotNull()] + [System.String] $Description, + + ## Computer's manager specified as a Distinguished Name (DN) + [ValidateNotNull()] + [System.String] $Manager, + + [ValidateNotNull()] + [System.Boolean] $Enabled = $true, + + [ValidateNotNull()] + [System.String] $DomainController, + + ## Ideally this should just be called 'Credential' but is here for backwards compatibility + [ValidateNotNull()] + [System.Management.Automation.PSCredential] + [System.Management.Automation.CredentialAttribute()] + $DomainAdministratorCredential + ) + + $targetResource = Get-TargetResource @PSBoundParameters; + + ## Add ensure and enabled as they may not be explicitly passed and we want to enumerate them + $PSBoundParameters['Ensure'] = $Ensure; + $PSBoundParameters['Enabled'] = $Enabled; + + if ($Ensure -eq 'Present') + { + if ($targetResource.Ensure -eq 'Absent') { + ## Computer does not exist and needs creating + $newADComputerParams = Get-ADCommonParameters @PSBoundParameters -UseNameParameter; + if ($PSBoundParameters.ContainsKey('Path')) + { + Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f 'Path', $Path); + $newADComputerParams['Path'] = $Path; + } + Write-Verbose -Message ($LocalizedData.AddingADComputer -f $ComputerName); + New-ADComputer @newADComputerParams; + ## Now retrieve the newly created computer + $targetResource = Get-TargetResource @PSBoundParameters; + } + + $setADComputerParams = Get-ADCommonParameters @PSBoundParameters; + $replaceComputerProperties = @{}; + $removeComputerProperties = @{}; + foreach ($parameter in $PSBoundParameters.Keys) + { + ## Only check/action properties specified/declared parameters that match one of the function's + ## parameters. This will ignore common parameters such as -Verbose etc. + if ($targetResource.ContainsKey($parameter)) + { + if ($parameter -eq 'Path' -and ($PSBoundParameters.Path -ne $targetResource.Path)) + { + ## Cannot move computers by updating the DistinguishedName property + $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; + ## Using the SamAccountName for identity with Move-ADObject does not work, use the DN instead + $adCommonParameters['Identity'] = $targetResource.DistinguishedName; + Write-Verbose -Message ($LocalizedData.MovingADComputer -f ` + $targetResource.Path, $PSBoundParameters.Path); + Move-ADObject @adCommonParameters -TargetPath $PSBoundParameters.Path; + } + elseif ($parameter -eq 'ServicePrincipalNames') + { + Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f ` + 'ServicePrincipalNames', ($ServicePrincipalNames -join ',')); + $replaceComputerProperties['ServicePrincipalName'] = $ServicePrincipalNames; + } + elseif ($parameter -eq 'Enabled' -and ($PSBoundParameters.$parameter -ne $targetResource.$parameter)) + { + ## We cannot enable/disable an account with -Add or -Replace parameters, but inform that + ## we will change this as it is out of compliance (it always gets set anyway) + Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f ` + $parameter, $PSBoundParameters.$parameter); + } + elseif ($PSBoundParameters.$parameter -ne $targetResource.$parameter) + { + ## Find the associated AD property + $adProperty = $adPropertyMap | Where-Object { $_.Parameter -eq $parameter }; + + if ([System.String]::IsNullOrEmpty($adProperty)) + { + ## We can't do anything with an empty AD property! + } + elseif ([System.String]::IsNullOrEmpty($PSBoundParameters.$parameter)) + { + ## We are removing properties + ## Only remove if the existing value in not null or empty + if (-not ([System.String]::IsNullOrEmpty($targetResource.$parameter))) + { + Write-Verbose -Message ($LocalizedData.RemovingADComputerProperty -f ` + $parameter, $PSBoundParameters.$parameter); + if ($adProperty.UseCmdletParameter -eq $true) + { + ## We need to pass the parameter explicitly to Set-ADComputer, not via -Remove + $setADComputerParams[$adProperty.Parameter] = $PSBoundParameters.$parameter; + } + elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty)) + { + $removeComputerProperties[$adProperty.Parameter] = $targetResource.$parameter; + } + else + { + $removeComputerProperties[$adProperty.ADProperty] = $targetResource.$parameter; + } + } + } #end if remove existing value + else + { + ## We are replacing the existing value + Write-Verbose -Message ($LocalizedData.UpdatingADComputerProperty -f ` + $parameter, $PSBoundParameters.$parameter); + if ($adProperty.UseCmdletParameter -eq $true) + { + ## We need to pass the parameter explicitly to Set-ADComputer, not via -Replace + $setADComputerParams[$adProperty.Parameter] = $PSBoundParameters.$parameter; + } + elseif ([System.String]::IsNullOrEmpty($adProperty.ADProperty)) + { + $replaceComputerProperties[$adProperty.Parameter] = $PSBoundParameters.$parameter; + } + else + { + $replaceComputerProperties[$adProperty.ADProperty] = $PSBoundParameters.$parameter; + } + } #end if replace existing value + } + + } #end if TargetResource parameter + } #end foreach PSBoundParameter + + ## Only pass -Remove and/or -Replace if we have something to set/change + if ($replaceComputerProperties.Count -gt 0) + { + $setADComputerParams['Replace'] = $replaceComputerProperties; + } + if ($removeComputerProperties.Count -gt 0) + { + $setADComputerParams['Remove'] = $removeComputerProperties; + } + + Write-Verbose -Message ($LocalizedData.UpdatingADComputer -f $ComputerName); + [ref] $null = Set-ADComputer @setADComputerParams -Enabled $Enabled; + } + elseif (($Ensure -eq 'Absent') -and ($targetResource.Ensure -eq 'Present')) + { + ## User exists and needs removing + Write-Verbose ($LocalizedData.RemovingADComputer -f $ComputerName); + $adCommonParameters = Get-ADCommonParameters @PSBoundParameters; + [ref] $null = Remove-ADComputer @adCommonParameters -Confirm:$false; + } + +} #end function Set-TargetResource + + +## Import the common AD functions +$adCommonFunctions = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.ps1'; +. $adCommonFunctions; + +Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xADComputer/MSFT_xADComputer.schema.mof b/DSCResources/MSFT_xADComputer/MSFT_xADComputer.schema.mof new file mode 100644 index 000000000..86ac1e09c --- /dev/null +++ b/DSCResources/MSFT_xADComputer/MSFT_xADComputer.schema.mof @@ -0,0 +1,19 @@ +[ClassVersion("1.0.0.0"), FriendlyName("xADComputer")] +class MSFT_xADComputer : OMI_BaseResource +{ + [Key, Description("Specifies the name of the computer")] String ComputerName; + [Write, Description("Specifies the location of the computer, such as an office number")] String Location; + [Write, Description("Specifies the fully qualified domain name (FQDN) of the computer")] String DnsHostName; + [Write, Description("Specifies the service principal names for the computer account")] String ServicePrincipalNames[]; + [Write, Description("Specifies the UPN assigned to the computer account")] String UserPrincipalName; + [Write, Description("Specifies the display name of the computer")] String DisplayName; + [Write, Description("Specifies the X.500 path of the Organizational Unit (OU) or container where the computer is located")] String Path; + [Write, Description("Specifies a description of the computer object")] String Description; + [Write, Description("Specifies if the computer account is enabled")] Boolean Enabled; + [Write, Description("Specifies the user or group Distinguished Name that manages the computer object")] String Manager; + [Write, Description("Specifies the Active Directory Domain Services instance to connect to perform the task")] String DomainController; + [Write, Description("Specifies the user account credentials to use to perform the task"), EmbeddedInstance("MSFT_Credential")] String DomainAdministratorCredential; + [Write, ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; + [Read, Description("Returns the X.500 path of the computer object")] String DistinguishedName; + [Read, Description("Returns the security identifier of the computer object")] String SID; +}; diff --git a/DSCResources/MSFT_xADComputer/en-US/MSFT_xADComputer.psd1 b/DSCResources/MSFT_xADComputer/en-US/MSFT_xADComputer.psd1 new file mode 100644 index 000000000..7387a59c7 --- /dev/null +++ b/DSCResources/MSFT_xADComputer/en-US/MSFT_xADComputer.psd1 @@ -0,0 +1,21 @@ +# culture="en-US" +ConvertFrom-StringData @' + RoleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed. + RetrievingADComputerError = Error looking up Active Directory computer '{0}'. + + RetrievingADComputer = Retrieving Active Directory computer '{0}' ... + CreatingADDomainConnection = Creating connection to Active Directory domain ... + ADComputerIsPresent = Active Directory computer '{0}' is present. + ADComputerNotPresent = Active Directory computer '{0}' was NOT present. + ADComputerNotDesiredPropertyState = Computer '{0}' property is NOT in the desired state. Expected '{1}', actual '{2}'. + ADComputerInDesiredState = Active Directory computer '{0}' is in the desired state. + ADComputerNotInDesiredState = Active Directory computer '{0}' is NOT in the desired state. + + AddingADComputer = Adding Active Directory computer '{0}'. + RemovingADComputer = Removing Active Directory computer '{0}'. + UpdatingADComputer = Updating Active Directory computer '{0}'. + UpdatingADComputerProperty = Updating computer property '{0}' with/to '{1}'. + RemovingADComputerProperty = Removing computer property '{0}' with '{1}'. + MovingADComputer = Moving computer from '{0}' to '{1}'. + RenamingADComputer = Renaming computer from '{0}' to '{1}'. +'@ diff --git a/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 b/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 index 99bf9aab9..824ec229d 100644 --- a/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 +++ b/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 @@ -35,7 +35,7 @@ function Get-TargetResource $domain = Get-ADDomain -Identity $DomainName -Credential $DomainAdministratorCredential if ($domain -ne $null) { - Write-Verbose -Message "Domain '$($fullDomainName)' is present. Looking for DCs ..." + Write-Verbose -Message "Domain '$($DomainName)' is present. Looking for DCs ..." try { $dc = Get-ADDomainController -Identity $env:COMPUTERNAME -Credential $DomainAdministratorCredential @@ -155,7 +155,7 @@ function Test-TargetResource catch { if ($error[0]) {Write-Verbose $error[0].Exception} - Write-Verbose -Message "Domain '$($Name)' is NOT present on the current node." + Write-Verbose -Message "Domain '$($DomainName)' is NOT present on the current node." $false } } diff --git a/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 b/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 index a68cb1d2f..35a34e8bb 100644 --- a/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 +++ b/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 @@ -257,13 +257,8 @@ function Test-TargetResource Write-Verbose ($LocalizedData.NotDesiredPropertyState -f 'Notes', $Notes, $targetResource.Notes); $targetResourceInCompliance = $false; } - $testMembersParams = @{ - ExistingMembers = $targetResource.Members; - Members = $Members; - MembersToInclude= $MembersToInclude; - MembersToExclude = $MembersToExclude; - } - if (-not (Test-Members @testMembersParams)) + ## Test group members match passed membership parameters + if (-not (Test-Members @assertMemberParameters -ExistingMembers $targetResource.Members)) { Write-Verbose -Message $LocalizedData.GroupMembershipNotDesiredState; $targetResourceInCompliance = $false; @@ -349,7 +344,7 @@ function Set-TargetResource $adGroupParams = Get-ADCommonParameters @PSBoundParameters; try { - $adGroup = Get-ADGroup @adGroupParams -Property Name,GroupScope,GroupCategory,DistinguishedName,Description,DisplayName,Info; + $adGroup = Get-ADGroup @adGroupParams -Property Name,GroupScope,GroupCategory,DistinguishedName,Description,DisplayName,ManagedBy,Info; if ($Ensure -eq 'Present') { diff --git a/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 b/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 index ce9bb694a..73e65f63f 100644 --- a/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 +++ b/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 @@ -70,6 +70,7 @@ $adPropertyMap = @( function Get-TargetResource { + [CmdletBinding()] [OutputType([System.Collections.Hashtable])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] param @@ -288,6 +289,7 @@ function Get-TargetResource function Test-TargetResource { + [CmdletBinding()] [OutputType([System.Boolean])] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] param @@ -493,6 +495,7 @@ function Test-TargetResource function Set-TargetResource { + [CmdletBinding()] [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPassWordParams', '')] param ( diff --git a/README.md b/README.md index 399b90a2a..da2eab7f4 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,12 @@ Please check out common DSC Resource [contributing guidelines](https://github.co ## Description -The **xActiveDirectory** module contains the **xADDomain, xADDomainController, xADUser, xWaitForDomain, xADDomainTrust, xADRecycleBin, xADGroup, xADOrganizationalUnit and xADDomainDefaultPasswordPolicy** DSC Resources. +The **xActiveDirectory** module contains the **xADComputer, xADDomain, xADDomainController, xADUser, xWaitForDomain, xADDomainTrust, xADRecycleBin, xADGroup, xADOrganizationalUnit and xADDomainDefaultPasswordPolicy** DSC Resources. These DSC Resources allow you to configure new domains, child domains, and high availability domain controllers, establish cross-domain trusts and manage users, groups and OUs. ## Resources +* **xADComputer** creates and manages Active Directory computer accounts. * **xADDomain** creates new Active Directory forest configurations and new Active Directory domain configurations. * **xADDomainController** installs and configures domain controllers in Active Directory. * **xADDomainDefaultPasswordPolicy** manages an Active Directory domain's default password policy. @@ -205,9 +206,32 @@ The xADDomainDefaultPasswordPolicy DSC resource will manage an Active Directory * **DomainController**: An existing Active Directory domain controller used to perform the operation (optional). * **Credential**: User account credentials used to perform the operation (optional). +### **xADComputer** +The xADComputer DSC resource will manage computer accounts within Active Directory. +* **ComputerName**: Specifies the name of the computer to manage. +* **Location**: Specifies the location of the computer, such as an office number (optional). +* **DnsHostName**: Specifies the fully qualified domain name (FQDN) of the computer (optional). +* **ServicePrincipalNames**: Specifies the service principal names for the computer account (optional). +* **UserPrincipalName** :Specifies the UPN assigned to the computer account (optional). +* **DisplayName**: "Specifies the display name of the computer (optional). +* **Path**: Specifies the X.500 path of the container where the computer is located (optional). +* **Description**: Specifies a description of the computer object (optional). +* **Enabled**: Specifies if the computer account is enabled (optional). +* **Manager**: Specifies the user or group Distinguished Name that manages the computer object (optional). + * Valid values are the user's or group's DistinguishedName, ObjectGUID, SID or SamAccountName. +* **DomainController**: Specifies the Active Directory Domain Services instance to connect to perform the task (optional). +* **DomainAdministratorCredential**: Specifies the user account credentials to use to perform the task (optional). +* **Ensure**: Specifies whether the computer account is present or absent. + * Valid values are 'Present' and 'Absent'. + * It not specified, it defaults to 'Present'. +* **DistinguishedName**: Returns the X.500 path of the computer object (read-only). +* **SID**: Returns the security identifier of the computer object (read-only). + ## Versions ### Unreleased +* xADDomainController: Customer identified two cases of incorrect variables being called in Verbose output messages. Corrected. +* xADComputer: New resource added. ### 2.11.0.0 * xWaitForADDomain: Made explicit credentials optional and other various updates @@ -997,3 +1021,53 @@ Example_xADDomainDefaultPasswordPolicy -DomainName 'contoso.com' -ComplexityEnab Start-DscConfiguration -Path .\Example_xADDomainDefaultPasswordPolicy -Wait -Verbose ``` + +### Create an Active Directory Computer Account + +In this example, we create a 'NANO-001' computer account in the 'Server' OU of the 'example.com' Active Directory domain. + +```powershell +configuration Example_xADComputerAccount +{ + Param + ( + [parameter(Mandatory = $true)] + [System.String] + $DomainController, + + [parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $DomainCredential, + + [parameter(Mandatory = $true)] + [System.String] + $ComputerName, + + [parameter(Mandatory = $true)] + [System.String] + $Path + ) + + Import-DscResource -Module xActiveDirectory + + Node $AllNodes.NodeName + { + xADComputer "$ComputerName" + { + DomainController = $DomainController + DomainAdministratorCredential = $DomainCredential + ComputerName = $ComputerName + Path = $Path + } + } +} + +Example_xADComputerAccount -DomainController 'DC01' ` + -DomainCredential (Get-Credential -Message "Domain Credentials") ` + -ComputerName 'NANO-001' ` + -Path 'ou=Servers,dc=example,dc=com' ` + -ConfigurationData $ConfigurationData + +Start-DscConfiguration -Path .\Example_xADComputerAccount -Wait -Verbose + +``` diff --git a/Tests/Unit/MSFT_xADCommon.Tests.ps1 b/Tests/Unit/MSFT_xADCommon.Tests.ps1 index e05c3d74a..278282cb7 100644 --- a/Tests/Unit/MSFT_xADCommon.Tests.ps1 +++ b/Tests/Unit/MSFT_xADCommon.Tests.ps1 @@ -396,6 +396,19 @@ try $result['Name'] | Should Be $testIdentity; } + foreach ($identityParam in @('UserName','GroupName','ComputerName')) { + It "Returns 'Identity' key when '$identityParam' alias is specified" { + $testIdentity = 'contoso.com'; + $getADCommonParameters = @{ + $identityParam = $testIdentity; + } + + $result = Get-ADCommonParameters @getADCommonParameters; + + $result['Identity'] | Should Be $testIdentity; + } + } + It "Returns 'Identity' key by default when 'Identity' and 'CommonName' are specified" { $testIdentity = 'contoso.com'; $testCommonName = 'Test Common Name'; @@ -441,8 +454,7 @@ try It "Returns 'Credential' key when specified" { $testIdentity = 'contoso.com'; - $testPassword = (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force); - $testCredential = New-Object System.Management.Automation.PSCredential 'Safemode', $testPassword; + $testCredential = [System.Management.Automation.PSCredential]::Empty; $result = Get-ADCommonParameters -Identity $testIdentity -Credential $testCredential; @@ -468,8 +480,7 @@ try It "Converts 'DomainAdministratorCredential' parameter to 'Credential' key" { $testIdentity = 'contoso.com'; - $testPassword = (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force); - $testCredential = New-Object System.Management.Automation.PSCredential 'Safemode', $testPassword; + $testCredential = [System.Management.Automation.PSCredential]::Empty; $result = Get-ADCommonParameters -Identity $testIdentity -DomainAdministratorCredential $testCredential; diff --git a/Tests/Unit/MSFT_xADComputer.Tests.ps1 b/Tests/Unit/MSFT_xADComputer.Tests.ps1 new file mode 100644 index 000000000..4bd9095f5 --- /dev/null +++ b/Tests/Unit/MSFT_xADComputer.Tests.ps1 @@ -0,0 +1,511 @@ +$Global:DSCModuleName = 'xActiveDirectory' # Example xNetworking +$Global:DSCResourceName = 'MSFT_xADComputer' # Example MSFT_xFirewall + +#region HEADER +# Unit Test Template Version: 1.1.0 +[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) +if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) +} + +Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $Global:DSCModuleName ` + -DSCResourceName $Global:DSCResourceName ` + -TestType Unit +#endregion HEADER + + +# Begin Testing +try +{ + + #region Pester Tests + + # The InModuleScope command allows you to perform white-box unit testing on the internal + # (non-exported) code of a Script Module. + InModuleScope $Global:DSCResourceName { + + $testPresentParams = @{ + ComputerName = 'TESTCOMPUTER'; + Ensure = 'Present'; + } + + $testAbsentParams = $testPresentParams.Clone(); + $testAbsentParams['Ensure'] = 'Absent'; + + $fakeADComputer = @{ + DistinguishedName = "CN=$($testPresentParams.ComputerName),CN=Computers,DC=contoso,DC=com"; + Enabled = $true; + Name = $testPresentParams.ComputerName; + SamAccountName = '{0}$' -f $testPresentParams.ComputerName; + SID = 'S-1-5-21-1409167834-891301383-2860967316-1143'; + ObjectClass = 'computer'; + ObjectGUID = [System.Guid]::NewGuid(); + UserPrincipalName = 'TESTCOMPUTER@contoso.com'; + ServicePrincipalNames = @('spn/a','spn/b'); + Location = 'Test location'; + DnsHostName = '{0}.contoso.com' -f $testPresentParams.ComputerName; + DisplayName = $testPresentParams.ComputerName; + Description = 'Test description'; + ManagedBy = 'CN=Manager,CN=Users,DC=contoso,DC=com'; + } + + $testDomainController = 'TESTDC'; + $testCredential = [System.Management.Automation.PSCredential]::Empty; + + #region Function Get-TargetResource + Describe "$($Global:DSCResourceName)\Get-TargetResource" { + + It "Returns a 'System.Collections.Hashtable' object type" { + Mock Get-ADComputer { return [PSCustomObject] $fakeADComputer; } + + $adUser = Get-TargetResource @testPresentParams; + + $adUser -is [System.Collections.Hashtable] | Should Be $true; + } + + It "Returns 'Ensure' is 'Present' when user account exists" { + Mock Get-ADComputer { return [PSCustomObject] $fakeADComputer; } + + $adUser = Get-TargetResource @testPresentParams; + + $adUser.Ensure | Should Be 'Present'; + } + + It "Returns 'Ensure' is 'Absent' when user account does not exist" { + Mock Get-ADComputer { throw New-Object Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException } + + $adUser = Get-TargetResource @testPresentParams; + + $adUser.Ensure | Should Be 'Absent'; + } + + It "Calls 'Get-ADComputer' with 'Server' parameter when 'DomainController' specified" { + Mock Get-ADComputer -ParameterFilter { $Server -eq $testDomainController } -MockWith { return [PSCustomObject] $fakeADComputer; } + + Get-TargetResource @testPresentParams -DomainController $testDomainController; + + Assert-MockCalled Get-ADComputer -ParameterFilter { $Server -eq $testDomainController } -Scope It; + } + + It "Calls 'Get-ADComputer' with 'Credential' parameter when 'DomainAdministratorCredential' specified" { + Mock Get-ADComputer -ParameterFilter { $Credential -eq $testCredential } -MockWith { return [PSCustomObject] $fakeADComputer; } + + Get-TargetResource @testPresentParams -DomainAdministratorCredential $testCredential; + + Assert-MockCalled Get-ADComputer -ParameterFilter { $Credential -eq $testCredential } -Scope It; + } + + } + #endregion + + #region Function Test-TargetResource + Describe "$($Global:DSCResourceName)\Test-TargetResource" { + + $testStringProperties = @( + 'Location', + 'DnsHostName', + 'UserPrincipalName', + 'DisplayName', + 'Path', + 'Description', + 'Manager' + ); + $testArrayProperties = @( + 'ServicePrincipalNames' + ); + $testBooleanProperties = @( + 'Enabled' + ); + + It "Passes when computer account does not exist and 'Ensure' is 'Absent'" { + Mock Get-TargetResource { return $testAbsentParams } + + Test-TargetResource @testAbsentParams | Should Be $true; + } + + It "Passes when computer account exists and 'Ensure' is 'Present'" { + Mock Get-TargetResource { return $testPresentParams } + + Test-TargetResource @testPresentParams | Should Be $true; + } + + It "Fails when computer account does not exist and 'Ensure' is 'Present'" { + Mock Get-TargetResource { return $testAbsentParams } + + Test-TargetResource @testPresentParams | Should Be $false; + } + + It "Fails when computer account exists, and 'Ensure' is 'Absent'" { + Mock Get-TargetResource { return $testPresentParams } + + Test-TargetResource @testAbsentParams | Should Be $false; + } + + foreach ($testParameter in $testStringProperties) { + + It "Passes when computer account '$testParameter' matches AD account property" { + $testParameterValue = 'Test Parameter String Value'; + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = $testParameterValue; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + It "Fails when computer account '$testParameter' does not match incorrect AD account property value" { + $testParameterValue = 'Test Parameter String Value'; + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $invalidADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $invalidADComputer[$testParameter] = $testParameterValue.Substring(0, ([System.Int32] $testParameterValue.Length/2)); + return $invalidADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + It "Fails when computer account '$testParameter' does not match empty AD account property value" { + $testParameterValue = 'Test Parameter String Value'; + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $invalidADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $invalidADComputer[$testParameter] = ''; + return $invalidADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + It "Fails when computer account '$testParameter' does not match null AD account property value" { + $testParameterValue = 'Test Parameter String Value'; + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $invalidADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $invalidADComputer[$testParameter] = $null; + return $invalidADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + It "Passes when empty computer account '$testParameter' matches empty AD account property" { + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = ''; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + It "Passes when empty computer account '$testParameter' matches null AD account property" { + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = $null; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + } #end foreach test string property + + foreach ($testParameter in $testArrayProperties) { + + It "Passes when computer account '$testParameter' matches empty AD account property" { + $testParameterValue = @(); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = $testParameterValue; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + It "Passes when computer account '$testParameter' matches single AD account property" { + $testParameterValue = @('Entry1'); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = $testParameterValue; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + It "Passes when computer account '$testParameter' matches multiple AD account property" { + $testParameterValue = @('Entry1','Entry2'); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = $testParameterValue; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + It "Fails when computer account '$testParameter' does not match AD account property count" { + $testParameterValue = @('Entry1','Entry2'); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = @('Entry1'); + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + It "Fails when computer account '$testParameter' does not match AD account property name" { + $testParameterValue = @('Entry1'); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = @('Entry2'); + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + It "Fails when computer account '$testParameter' does not match empty AD account property" { + $testParameterValue = @('Entry1'); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = @(); + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + It "Fails when empty computer account '$testParameter' does not match AD account property" { + $testParameterValue = @(); + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = @('ExtraEntry1'); + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + } #end foreach test string property + + foreach ($testParameter in $testBooleanProperties) { + + It "Passes when computer account '$testParameter' matches AD account property" { + $testParameterValue = $true; + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $validADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $validADComputer[$testParameter] = $testParameterValue; + return $validADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $true; + } + + It "Fails when computer account '$testParameter' does not match AD account property value" { + $testParameterValue = $true; + $testValidPresentParams = $testPresentParams.Clone(); + $testValidPresentParams[$testParameter] = $testParameterValue; + $invalidADComputer = $testPresentParams.Clone(); + Mock Get-TargetResource { + $invalidADComputer[$testParameter] = -not $testParameterValue; + return $invalidADComputer; + } + + Test-TargetResource @testValidPresentParams | Should Be $false; + } + + } #end foreach test boolean property + + } + #endregion + + #region Function Set-TargetResource + Describe "$($Global:DSCResourceName)\Set-TargetResource" { + + $testStringProperties = @( + 'Location', + 'DnsHostName', + 'UserPrincipalName', + 'DisplayName', + 'Description' + # Manager is translated to ManagedBy + ); + + $testArrayProperties = @( + 'ServicePrincipalNames' + ); + $testBooleanProperties = @( + 'Enabled' + ); + + It "Calls 'New-ADComputer' when 'Ensure' is 'Present' and the account does not exist" { + $newComputerName = 'NEWCOMPUTER' + $newAbsentParams = $testAbsentParams.Clone(); + $newAbsentParams['ComputerName'] = $newComputerName; + $newPresentParams = $testPresentParams.Clone(); + $newPresentParams['ComputerName'] = $newComputerName; + Mock New-ADComputer -ParameterFilter { $Name -eq $newComputerName } -MockWith { } + Mock Set-ADComputer { } + Mock Get-TargetResource -ParameterFilter { $ComputerName -eq $newComputerName } -MockWith { return $newAbsentParams; } + + Set-TargetResource @newPresentParams; + + Assert-MockCalled New-ADComputer -ParameterFilter { $Name -eq $newComputerName } -Scope It; + } + + It "Calls 'New-ADComputer' with 'Path' when specified" { + $newComputerName = 'NEWCOMPUTER' + $newAbsentParams = $testAbsentParams.Clone(); + $newAbsentParams['ComputerName'] = $newComputerName; + $newPresentParams = $testPresentParams.Clone(); + $newPresentParams['ComputerName'] = $newComputerName; + $targetPath = 'OU=Test,DC=contoso,DC=com'; + Mock New-ADComputer -ParameterFilter { $Path -eq $targetPath } -MockWith { } + Mock Set-ADComputer { } + Mock Get-TargetResource -ParameterFilter { $ComputerName -eq $newComputerName } -MockWith { return $newAbsentParams; } + + Set-TargetResource @newPresentParams -Path $targetPath; + + Assert-MockCalled New-ADComputer -ParameterFilter { $Path -eq $targetPath } -Scope It; + } + + It "Calls 'Move-ADObject' when 'Ensure' is 'Present', the computer account exists but Path is incorrect" { + $testTargetPath = 'OU=NewPath,DC=contoso,DC=com'; + Mock Set-ADComputer { } + Mock Get-ADComputer { + $duffADComputer = $fakeADComputer.Clone(); + $duffADComputer['DistinguishedName'] = 'CN={0},OU=WrongPath,DC=contoso,DC=com' -f $testPresentParams.ComputerName; + return $duffADComputer; + } + Mock Move-ADObject -ParameterFilter { $TargetPath -eq $testTargetPath } -MockWith { } + + Set-TargetResource @testPresentParams -Path $testTargetPath; + + Assert-MockCalled Move-ADObject -ParameterFilter { $TargetPath -eq $testTargetPath } -Scope It; + } + + foreach ($testParameter in $testStringProperties) { + + It "Calls 'Set-ADComputer' with 'Remove' when '$testParameter' is `$null" { + Mock Get-ADComputer { return $fakeADComputer; } + Mock Set-ADComputer -ParameterFilter { $Remove.ContainsKey($testParameter) } { } + + $setTargetResourceParams = $testPresentParams.Clone(); + $setTargetResourceParams[$testParameter] = ''; + Set-TargetResource @setTargetResourceParams; + + Assert-MockCalled Set-ADComputer -ParameterFilter { $Remove.ContainsKey($testParameter) } -Scope It -Exactly 1; + } + + It "Calls 'Set-ADComputer' with 'Replace' when existing '$testParameter' is not `$null" { + Mock Get-ADComputer { return $fakeADComputer; } + Mock Set-ADComputer -ParameterFilter { $Replace.ContainsKey($testParameter) } { } + + $setTargetResourceParams = $testPresentParams.Clone(); + $setTargetResourceParams[$testParameter] = 'NewStringValue'; + Set-TargetResource @setTargetResourceParams; + + Assert-MockCalled Set-ADComputer -ParameterFilter { $Replace.ContainsKey($testParameter) } -Scope It -Exactly 1; + } + + } #end foreach string parameter + + It "Calls 'Set-ADComputer' with 'Remove' when 'Manager' is `$null" { + ## Manager translates to AD attribute 'managedBy' + Mock Get-ADComputer { return $fakeADComputer; } + Mock Set-ADComputer -ParameterFilter { $Remove.ContainsKey('ManagedBy') } { } + + $setTargetResourceParams = $testPresentParams.Clone(); + $setTargetResourceParams['Manager'] = ''; + Set-TargetResource @setTargetResourceParams; + + Assert-MockCalled Set-ADComputer -ParameterFilter { $Remove.ContainsKey('ManagedBy') } -Scope It -Exactly 1; + } + + It "Calls 'Set-ADComputer' with 'Replace' when existing 'Manager' is not `$null" { + ## Manager translates to AD attribute 'managedBy' + Mock Get-ADComputer { return $fakeADComputer; } + Mock Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ManagedBy') } { } + + $setTargetResourceParams = $testPresentParams.Clone(); + $setTargetResourceParams['Manager'] = 'NewValue'; + Set-TargetResource @setTargetResourceParams; + + Assert-MockCalled Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ManagedBy') } -Scope It -Exactly 1; + } + + It "Calls 'Set-ADComputer' with 'Enabled' = 'True' by default" { + Mock Get-ADComputer { return $fakeADComputer; } + Mock Set-ADComputer -ParameterFilter { $Enabled -eq $true } { } + + $setTargetResourceParams = $testPresentParams.Clone(); + $setTargetResourceParams[$testParameter] = -not $fakeADComputer.$testParameter; + Set-TargetResource @setTargetResourceParams; + + Assert-MockCalled Set-ADComputer -ParameterFilter { $Enabled -eq $true } -Scope It -Exactly 1; + } + + It "Calls 'Set-ADComputer' with 'ServicePrincipalNames' when specified" { + $testSPNs = @('spn/a','spn/b'); + Mock Get-ADComputer { return $fakeADComputer; } + Mock Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ServicePrincipalName') } { } + + Set-TargetResource @testPresentParams -ServicePrincipalNames $testSPNs; + + Assert-MockCalled Set-ADComputer -ParameterFilter { $Replace.ContainsKey('ServicePrincipalName') } -Scope It -Exactly 1; + } + + It "Calls 'Remove-ADComputer' when 'Ensure' is 'Absent' and computer account exists" { + Mock Get-ADComputer { return $fakeADComputer; } + Mock Remove-ADComputer -ParameterFilter { $Identity.ToString() -eq $testAbsentParams.ComputerName } -MockWith { } + + Set-TargetResource @testAbsentParams; + + Assert-MockCalled Remove-ADComputer -ParameterFilter { $Identity.ToString() -eq $testAbsentParams.ComputerName } -Scope It; + } + + } + #endregion + } + #endregion +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +}