diff --git a/CHANGELOG.md b/CHANGELOG.md index fda1d4f83..95c5f8c52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ - Changes to xActiveDirectory - Added a Requirements section to every DSC resource README with the bullet point stating "Target machine must be running Windows Server 2008 R2 or later" ([issue #399](https://github.com/PowerShell/xActiveDirectory/issues/399)). - Added 'about_\.help.txt' file to all resources ([issue #404](https://github.com/PowerShell/xActiveDirectory/issues/404)). + - Added new helper functions in xActiveDirectory.Common. + - New-CimCredentialInstance + - Add-TypeAssembly + - New-ADDirectoryContext - Changes to xADManagedServiceAccount - Added a requirement to README stating "Group Managed Service Accounts need at least one Windows Server 2012 Domain Controller" ([issue #399](https://github.com/PowerShell/xActiveDirectory/issues/399)). - Changes to xADComputer @@ -15,6 +19,11 @@ - Fixes exception when creating a user with an empty string property ([issue #407](https://github.com/PowerShell/xActiveDirectory/issues/407)). - Fixes exception when updating `CommonName` and `Path` concurrently ([issue #402](https://github.com/PowerShell/xActiveDirectory/issues/402)). - Fixes ChangePasswordAtLogon Property to be only set to `true` at User Creation ([issue #414](https://github.com/PowerShell/xActiveDirectory/issues/414)). +- Changes to xADDomainTrust + - Refactored the resource to enable unit tests, and at the same time changed + it to use the same code pattern as the resource xADObjectEnabledState. + - Added unit tests ([issue #324](https://github.com/PowerShell/xActiveDirectory/issues/324)). + - Added comment-based help ([issue #337](https://github.com/PowerShell/xActiveDirectory/issues/337)). ## 3.0.0.0 diff --git a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 index 16c0122fb..1180904fd 100644 --- a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 +++ b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 @@ -6,6 +6,29 @@ Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath ' $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADDomainTrust' +<# + .SYNOPSIS + Returns the current state of the Active Directory trust. + + .PARAMETER SourceDomainName + Specifies the name of the Active Directory domain that is requesting the + trust. + + .PARAMETER TargetDomainName + Specifies the name of the Active Directory domain that is being trusted. + + .PARAMETER TargetDomainAdministratorCredential + Specifies the credentials to authenticate to the target domain.. + + .PARAMETER TrustType + Specifies the type of trust. Valid values are 'External' or 'Forest'. + 'External' means the context Domain, while 'Forest' means the context + 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. Valid values are 'Bidirectional', + 'Inbound', and 'Outbound'. +#> function Get-TargetResource { [CmdletBinding()] @@ -32,84 +55,106 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [ValidateSet('Bidirectional', 'Inbound', 'Outbound')] [System.String] - $TrustDirection, - - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present' + $TrustDirection ) - # Load the .NET assembly - try + # Return a credential object without the password. + $cimCredentialInstance = New-CimCredentialInstance -Credential $TargetDomainAdministratorCredential + + $returnValue = @{ + SourceDomainName = $SourceDomainName + TargetDomainName = $TargetDomainName + TargetDomainAdministratorCredential = $cimCredentialInstance + } + + $directoryContextType = ConvertTo-DirectoryContextType -TrustType $TrustType + + # Create the target object. + $newADDirectoryContextParameters = @{ + DirectoryContextType = $directoryContextType + Name = $TargetDomainName + Credential = $TargetDomainAdministratorCredential + } + + $targetDirectoryContext = Get-ADDirectoryContext @newADDirectoryContextParameters + + # Create the source object. + $newADDirectoryContextParameters = @{ + DirectoryContextType = $directoryContextType + Name = $SourceDomainName + } + + $sourceDirectoryContext = Get-ADDirectoryContext @newADDirectoryContextParameters + + if ($directoryContextType -eq 'Domain') { - Add-type -AssemblyName System.DirectoryServices + $trustSource = Get-ActiveDirectoryDomain -DirectoryContext $sourceDirectoryContext + $trustTarget = Get-ActiveDirectoryDomain -DirectoryContext $targetDirectoryContext } - # If not found, means ADDS role is not installed - catch + else { - $missingRoleMessage = $($script:localizedData.MissingRoleMessage) -f 'AD-Domain-Services' - New-ObjectNotFoundException -Message $missingRoleMessage -ErrorRecord $_ + $trustSource = Get-ActiveDirectoryForest -DirectoryContext $sourceDirectoryContext + $trustTarget = Get-ActiveDirectoryForest -DirectoryContext $targetDirectoryContext } try { - switch ($TrustType) - { - 'External' - { - $DomainOrForest = 'Domain' - } - - 'Forest' - { - $DomainOrForest = 'Forest' - } - } + # Find trust between source & destination. + Write-Verbose -Message ( + $script:localizedData.CheckingTrustMessage -f $SourceDomainName, $TargetDomainName, $directoryContextTyp + ) - # Create the target object - $trgDirectoryContext = New-Object -TypeName 'System.DirectoryServices.ActiveDirectory.DirectoryContext' -ArgumentList @($DomainOrForest, $TargetDomainName, $TargetDomainAdministratorCredential.UserName, $TargetDomainAdministratorCredential.GetNetworkCredential().Password) - $trgDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($trgDirectoryContext) + $trust = $trustSource.GetTrustRelationship($trustTarget) - # Create the source object - $srcDirectoryContext = New-Object -TypeName 'System.DirectoryServices.ActiveDirectory.DirectoryContext' -ArgumentList @($DomainOrForest, $SourceDomainName) - $srcDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($srcDirectoryContext) + $returnValue['TrustDirection'] = $trust.TrustDirection + $returnValue['TrustType'] = ConvertFrom-DirectoryContextType -DirectoryContextType $trust.TrustType - # Find trust between source & destination. - Write-Verbose -Message ($script:localizedData.CheckingTrustMessage -f $SourceDomainName, $TargetDomainName) - $trust = $srcDomain.GetTrustRelationship($trgDomain) + Write-Verbose -Message ($script:localizedData.TrustPresentMessage -f $SourceDomainName, $TargetDomainName, $directoryContextType) - Write-Verbose -Message ($script:localizedData.TrustPresentMessage -f $SourceDomainName, $TargetDomainName) - $Ensure = 'Present' + $returnValue['Ensure'] = 'Present' } catch { - Write-Verbose -Message ($script:localizedData.TrustAbsentMessage -f $SourceDomainName, $TargetDomainName) - $Ensure = 'Absent' - } + Write-Verbose -Message ($script:localizedData.TrustAbsentMessage -f $SourceDomainName, $TargetDomainName, $directoryContextType) - # return a credential object without password - $CIMCredential = New-CimInstance -ClassName MSFT_Credential -ClientOnly ` - -Namespace 'root/microsoft/windows/desiredstateconfiguration' ` - -Property @{ - UserName = [System.String] $TargetDomainAdministratorCredential.UserName - Password = [System.String] $null + $returnValue['Ensure'] = 'Absent' + $returnValue['TrustDirection'] = $null + $returnValue['TrustType'] = $null } - return @{ - SourceDomainName = $SourceDomainName - TargetDomainName = $TargetDomainName - Ensure = $Ensure - TrustType = $trust.TrustType - TrustDirection = $trust.TrustDirection - TargetDomainAdministratorCredential = $CIMCredential - } + return $returnValue } +<# + .SYNOPSIS + Creates, removes, or updates the Active Directory trust so it is in the + desired state. + + .PARAMETER SourceDomainName + Specifies the name of the Active Directory domain that is requesting the + trust. + + .PARAMETER TargetDomainName + Specifies the name of the Active Directory domain that is being trusted. + + .PARAMETER TargetDomainAdministratorCredential + Specifies the credentials to authenticate to the target domain.. + + .PARAMETER TrustType + Specifies the type of trust. Valid values are 'External' or 'Forest'. + 'External' means the context Domain, while 'Forest' means the context + 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. Valid values are 'Bidirectional', + 'Inbound', and 'Outbound'. + + .PARAMETER Ensure + Specifies whether the computer account is present or absent. Valid values + are 'Present' and 'Absent'. The default is 'Present'. +#> function Set-TargetResource { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "", - Justification = 'Verbose messaging in helper function')] [CmdletBinding()] param ( @@ -141,18 +186,167 @@ function Set-TargetResource $Ensure = 'Present' ) - if ($PSBoundParameters.ContainsKey('Debug')) + $directoryContextType = ConvertTo-DirectoryContextType -TrustType $TrustType + + # Create the target object. + $newADDirectoryContextParameters = @{ + DirectoryContextType = $directoryContextType + Name = $TargetDomainName + Credential = $TargetDomainAdministratorCredential + } + + $targetDirectoryContext = Get-ADDirectoryContext @newADDirectoryContextParameters + + # Create the source object. + $newADDirectoryContextParameters = @{ + DirectoryContextType = $directoryContextType + Name = $SourceDomainName + } + + $sourceDirectoryContext = Get-ADDirectoryContext @newADDirectoryContextParameters + + if ($directoryContextType -eq 'Domain') + { + $trustSource = Get-ActiveDirectoryDomain -DirectoryContext $sourceDirectoryContext + $trustTarget = Get-ActiveDirectoryDomain -DirectoryContext $targetDirectoryContext + } + else + { + $trustSource = Get-ActiveDirectoryForest -DirectoryContext $sourceDirectoryContext + $trustTarget = Get-ActiveDirectoryForest -DirectoryContext $targetDirectoryContext + } + + $compareTargetResourceStateResult = Compare-TargetResourceState @PSBoundParameters + + # Get all properties that are not in desired state. + $propertiesNotInDesiredState = $compareTargetResourceStateResult | + Where-Object -FilterScript { + -not $_.InDesiredState + } + + if ($propertiesNotInDesiredState.Where({ $_.ParameterName -eq 'Ensure' })) { - $null = $PSBoundParameters.Remove('Debug') + if ($Ensure -eq 'Present') + { + # Create trust. + $trustSource.CreateTrustRelationship($trustTarget, $TrustDirection) + + Write-Verbose -Message ( + $script:localizedData.AddedTrust -f @( + $SourceDomainName, + $TargetDomainName, + $TrustType, + $TrustDirection + ) + ) + + } + else + { + # Remove trust. + $trustSource.DeleteTrustRelationship($trustTarget) + + Write-Verbose -Message ( + $script:localizedData.RemovedTrust -f @( + $SourceDomainName, + $TargetDomainName, + $TrustType, + $TrustDirection + ) + ) + } } + else + { + if ($Ensure -eq 'Present') + { + $trustRecreated = $false + + # Check properties. + $trustTypeProperty = $propertiesNotInDesiredState.Where({ $_.ParameterName -eq 'TrustType' }) + if ($trustTypeProperty) + { + Write-Verbose -Message ( + $script:localizedData.NeedToRecreateTrust -f @( + $SourceDomainName, + $TargetDomainName, + (ConvertFrom-DirectoryContextType -DirectoryContextType $trustTypeProperty.Actual), + $TrustType + ) + ) + + $trustSource.DeleteTrustRelationship($trustTarget) + $trustSource.CreateTrustRelationship($trustTarget, $TrustDirection) + + Write-Verbose -Message ( + $script:localizedData.RecreatedTrustType -f @( + $SourceDomainName, + $TargetDomainName, + $TrustType, + $TrustDirection + ) + ) + + $trustRecreated = $true + } - Confirm-ResourceProperties @PSBoundParameters -Apply + <# + In case the trust direction property should be wrong, there + are no need to update that property twice since it was set + to the correct value when the trust was recreated. + #> + if (-not $trustRecreated) + { + if ($propertiesNotInDesiredState.Where({ $_.ParameterName -eq 'TrustDirection' })) + { + $trustSource.UpdateTrustRelationship($trustTarget, $TrustDirection) + + Write-Verbose -Message ( + $script:localizedData.SetTrustDirection -f $TrustDirection + ) + } + } + + Write-Verbose -Message $script:localizedData.InDesiredState + } + else + { + # The trust is already absent, so in desired state. + Write-Verbose -Message $script:localizedData.InDesiredState + } + } } +<# + .SYNOPSIS + Determines if the properties of the Active Directory trust is in + the desired state. + + .PARAMETER SourceDomainName + Specifies the name of the Active Directory domain that is requesting the + trust. + + .PARAMETER TargetDomainName + Specifies the name of the Active Directory domain that is being trusted. + + .PARAMETER TargetDomainAdministratorCredential + Specifies the credentials to authenticate to the target domain.. + + .PARAMETER TrustType + Specifies the type of trust. Valid values are 'External' or 'Forest'. + 'External' means the context Domain, while 'Forest' means the context + 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. Valid values are 'Bidirectional', + 'Inbound', and 'Outbound'. + + .PARAMETER Ensure + Specifies whether the computer account is present or absent. Valid values + are 'Present' and 'Absent'. The default is 'Present'. +#> function Test-TargetResource { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "", - Justification = 'Verbose messaging in helper function')] [CmdletBinding()] [OutputType([System.Boolean])] param @@ -185,34 +379,63 @@ function Test-TargetResource $Ensure = 'Present' ) - #region Input Validation + Write-Verbose -Message ( + $script:localizedData.TestConfiguration -f $SourceDomainName, $TargetDomainName, $TrustType + ) - # Load the .NET assembly - try + <# + This returns array of hashtables which contain the properties ParameterName, + Expected, Actual, and InDesiredState. + #> + $compareTargetResourceStateResult = Compare-TargetResourceState @PSBoundParameters + + if ($false -in $compareTargetResourceStateResult.InDesiredState) { - Add-type -AssemblyName System.DirectoryServices + $testTargetResourceReturnValue = $false + + Write-Verbose -Message $script:localizedData.NotInDesiredState } - # If not found, means ADDS role is not installed - catch + else { - $missingRoleMessage = $($script:localizedData.MissingRoleMessage) -f 'AD-Domain-Services' - New-ObjectNotFoundException -Message $missingRoleMessage -ErrorRecord $_ - } + $testTargetResourceReturnValue = $true - #endregion - - if ($PSBoundParameters.ContainsKey('Debug')) - { - $null = $PSBoundParameters.Remove('Debug') + Write-Verbose -Message $script:localizedData.InDesiredState } - Confirm-ResourceProperties @PSBoundParameters + return $testTargetResourceReturnValue } -function Confirm-ResourceProperties +<# + .SYNOPSIS + Compares the properties in the current state with the properties of the + desired state and returns a hashtable with the comaprison result. + + .PARAMETER SourceDomainName + Specifies the name of the Active Directory domain that is requesting the + trust. + + .PARAMETER TargetDomainName + Specifies the name of the Active Directory domain that is being trusted. + + .PARAMETER TargetDomainAdministratorCredential + Specifies the credentials to authenticate to the target domain.. + + .PARAMETER TrustType + Specifies the type of trust. Valid values are 'External' or 'Forest'. + 'External' means the context Domain, while 'Forest' means the context + 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. Valid values are 'Bidirectional', + 'Inbound', and 'Outbound'. + + .PARAMETER Ensure + Specifies whether the computer account is present or absent. Valid values + are 'Present' and 'Absent'. The default is 'Present'. +#> +function Compare-TargetResourceState { [CmdletBinding()] - [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] @@ -240,180 +463,179 @@ function Confirm-ResourceProperties [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = 'Present', - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Apply + $Ensure = 'Present' ) - try - { - $checkingTrustMessage = $script:localizedData.CheckingTrustMessage -f $SourceDomainName, $TargetDomainName - Write-Verbose -Message $checkingTrustMessage - - switch ($TrustType) - { - 'External' - { - $DomainOrForest = 'Domain' - } - - 'Forest' - { - $DomainOrForest = 'Forest' - } - } - - # Create the target object - $trgDirectoryContext = New-Object -TypeName 'System.DirectoryServices.ActiveDirectory.DirectoryContext' -ArgumentList @($DomainOrForest, $TargetDomainName, $TargetDomainAdministratorCredential.UserName, $TargetDomainAdministratorCredential.GetNetworkCredential().Password) - $trgDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($trgDirectoryContext) - - # Create the source object - $srcDirectoryContext = New-Object -TypeName 'System.DirectoryServices.ActiveDirectory.DirectoryContext' -ArgumentList @($DomainOrForest, $SourceDomainName) - $srcDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($srcDirectoryContext) + $getTargetResourceParameters = @{ + SourceDomainName = $SourceDomainName + TargetDomainName = $TargetDomainName + TargetDomainAdministratorCredential = $TargetDomainAdministratorCredential + TrustType = $TrustType + TrustDirection = $TrustDirection + } - # Find trust - try - { - # Find trust between source & destination. - $trust = $srcDomain.GetTrustRelationship($TargetDomainName) + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters - $TestTrustMessage = $script:localizedData.TestTrustMessage -f 'present', $Ensure - Write-Verbose -Message $TestTrustMessage + <# + If the desired state should be Absent, then there is no need to + compare properties other than 'Ensure'. If the other properties + would be compared, they would return a false negative during test. + #> + if ($Ensure -eq 'Present') + { + $propertiesToEvaluate = @( + 'Ensure' + 'TrustType' + 'TrustDirection' + ) + } + else + { + $propertiesToEvaluate = @( + 'Ensure' + ) + } - if ($Ensure -eq 'Present') - { - #region Test for trust direction - $CheckPropertyMessage = $script:localizedData.CheckPropertyMessage -f 'trust direction' - Write-Verbose -Message $CheckPropertyMessage + <# + If the user did not specify Ensure property, then it is not part of + the $PSBoundParameters, but it still need to be compared. + Copy the hashtable $PSBoundParameters and add 'Ensure' property to make + sure it is part of the DesiredValues. + #> + $desiredValues = @{ } + $PSBoundParameters + $desiredValues['Ensure'] = $Ensure + + $compareTargetResourceStateParameters = @{ + CurrentValues = $getTargetResourceResult + DesiredValues = $desiredValues + Properties = $propertiesToEvaluate + } - if ($trust.TrustDirection -ne $TrustDirection) - { - # Set the trust direction if not correct - - $notDesiredPropertyMessage = $script:localizedData.NotDesiredPropertyMessage -f 'Trust direction', $TrustDirection, $trust.TrustDirection - Write-Verbose -Message $notDesiredPropertyMessage - - if ($Apply) - { - $srcDomain.UpdateTrustRelationship($trgDomain, $TrustDirection) - - $setPropertyMessage = $script:localizedData.SetPropertyMessage -f 'Trust direction' - Write-Verbose -Message $setPropertyMessage - } - else - { - return $false - } - } # end trust direction is not correct - else - { - # Trust direction is correct + return Compare-ResourcePropertyState @compareTargetResourceStateParameters +} - $desiredPropertyMessage = $script:localizedData.DesiredPropertyMessage -f 'Trust direction' - Write-Verbose -Message $desiredPropertyMessage - } - #endregion trust direction +<# + .SYNOPSIS + This returns a new object of the type System.DirectoryServices.ActiveDirectory.Domain + which is a class that represents an Active Directory Domain Services domain. + + .PARAMETER DirectoryContext + The Active Directory context from which the domain object is returned. + Calling the Get-ADDirectoryContext gets a value that can be provided in + this parameter. + + .NOTES + This is a wrapper for enable unit testing of this resource. + see issue https://github.com/PowerShell/xActiveDirectory/issues/324 + for more information. +#> +function Get-ActiveDirectoryDomain +{ + [CmdletBinding()] + [OutputType([System.DirectoryServices.ActiveDirectory.Domain])] + param + ( + [Parameter(Mandatory = $true)] + [System.DirectoryServices.ActiveDirectory.DirectoryContext] + $DirectoryContext + ) - #region Test for trust type - $CheckPropertyMessage = $script:localizedData.CheckPropertyMessage -f 'trust type' - Write-Verbose -Message $CheckPropertyMessage + return [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DirectoryContext) +} - if ($trust.TrustType -ne $TrustType) - { - # Set the trust type if not correct - - $notDesiredPropertyMessage = $script:localizedData.NotDesiredPropertyMessage -f 'Trust type', $TrustType, $trust.TrustType - Write-Verbose -Message $notDesiredPropertyMessage - - if ($Apply) - { - # Only way to fix the trust direction is to delete it and create again - # TODO: Add a property to ask user permission to delete an existing trust - $srcDomain.DeleteTrustRelationship($trgDomain) - $srcDomain.CreateTrustRelationship($trgDomain, $TrustDirection) - - $setPropertyMessage = $script:localizedData.SetPropertyMessage -f 'Trust type' - Write-Verbose -Message $setPropertyMessage - } - else - { - return $false - } - } # end trust type is not correct - else - { - # Trust type is correct +<# + .SYNOPSIS + This returns a new object of the type System.DirectoryServices.ActiveDirectory.Forest + which is a class that represents an Active Directory Domain Services forest. + + .PARAMETER DirectoryContext + The Active Directory context from which the forest object is returned. + Calling the Get-ADDirectoryContext gets a value that can be provided in + this parameter. + + .NOTES + This is a wrapper for enable unit testing of this resource. + see issue https://github.com/PowerShell/xActiveDirectory/issues/324 + for more information. +#> +function Get-ActiveDirectoryForest +{ + [CmdletBinding()] + [OutputType([System.DirectoryServices.ActiveDirectory.Forest])] + param + ( + [Parameter(Mandatory = $true)] + [System.DirectoryServices.ActiveDirectory.DirectoryContext] + $DirectoryContext + ) - $desiredPropertyMessage = $script:localizedData.DesiredPropertyMessage -f 'Trust type' - Write-Verbose -Message $desiredPropertyMessage - } - #endregion Test for trust type + return [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($DirectoryContext) +} - # If both trust type and trust direction are correct, return true - if (-not $Apply) - { - return $true - } - } # end Ensure -eq present - else - { - # If the trust should be absent, remove the trust +<# + .SYNOPSIS + This returns the converted value from a Trust Type value to the correct + Directory Context Type value. - if ($Apply) - { - $removingTrustMessage = $script:localizedData.RemovingTrustMessage -f $SourceDomainName, $TargetDomainName - Write-Verbose -Message $removingTrustMessage + .PARAMETER TrustType + The trust type value to convert. +#> +function ConvertTo-DirectoryContextType +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $TrustType + ) - $srcDomain.DeleteTrustRelationship($trgDomain) + switch ($TrustType) + { + 'External' + { + $directoryContextType = 'Domain' + } - $deleteTrustMessage = $script:localizedData.DeleteTrustMessage - Write-Verbose -Message $deleteTrustMessage - } - else - { - return $false - } - } # end Ensure -eq absent - } # end find trust - catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] + 'Forest' { - # Trust does not exist between source and destination + $directoryContextType = 'Forest' + } + } - $TestTrustMessage = $script:localizedData.TestTrustMessage -f 'absent', $Ensure - Write-Verbose -Message $TestTrustMessage + return $directoryContextType +} - if ($Ensure -eq 'Present') - { - if ($Apply) - { - $addingTrustMessage = $script:localizedData.AddingTrustMessage -f $SourceDomainName, $TargetDomainName - Write-Verbose -Message $addingTrustMessage +<# + .SYNOPSIS + This returns the converted value from a Directory Context Type value to + the correct Trust Type value. - $srcDomain.CreateTrustRelationship($trgDomain, $TrustDirection) + .PARAMETER DirectoryContextType + The Directory Context Type value to convert. +#> +function ConvertFrom-DirectoryContextType +{ + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DirectoryContextType + ) - $setTrustMessage = $script:localizedData.SetTrustMessage - Write-Verbose -Message $setTrustMessage - } - else - { - return $false - } - } # end Ensure -eq Present - else - { - if (-not $Apply) - { - return $true - } - } - } # end no trust - } # end getting directory object - catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] + switch ($DirectoryContextType) { - throw + 'Domain' + { + $trustType = 'External' + } + + 'Forest' + { + $trustType = 'Forest' + } } + + return $trustType } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof index 4f35d5a6c..5055df7b1 100644 --- a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof +++ b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof @@ -1,13 +1,10 @@ - [ClassVersion("1.0.1.0"), FriendlyName("xADDomainTrust")] class MSFT_xADDomainTrust : OMI_BaseResource { - [Write, Description("Should this resource be present or absent"), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Required, EmbeddedInstance("MSFT_Credential"), Description("Credentials to authenticate to the target domain")] String TargetDomainAdministratorCredential; - [Key, Description("Name of the AD domain that is being trusted")] String TargetDomainName; - [Required, Description("Type of trust"), ValueMap{"External","Forest"}, Values{"External","Forest"}] String TrustType; - [Required, Description("Direction of trust"), ValueMap{"Bidirectional","Inbound","Outbound"}, Values{"Bidirectional","Inbound","Outbound"}] String TrustDirection; - [Key, Description("Name of the AD domain that is requesting the trust")] String SourceDomainName; + [Write, Description("Specifies whether the computer account is present or absent. Valid values are 'Present' and 'Absent'. The default is 'Present'."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Required, Description("Specifies the credentials to authenticate to the target domain."), EmbeddedInstance("MSFT_Credential")] String TargetDomainAdministratorCredential; + [Key, Description("Specifies the name of the Active Directory domain that is being trusted.")] String TargetDomainName; + [Required, Description("Specifies the type of trust. Valid values are 'External' or 'Forest'. 'External' means the context Domain, while 'Forest' means the context 'Forest'."), ValueMap{"External","Forest"}, Values{"External","Forest"}] String TrustType; + [Required, Description("Specifies the direction of the trust. Valid values are 'Bidirectional', 'Inbound', and 'Outbound'."), ValueMap{"Bidirectional","Inbound","Outbound"}, Values{"Bidirectional","Inbound","Outbound"}] String TrustDirection; + [Key, Description("Specifies the name of the Active Directory domain that is requesting the trust.")] String SourceDomainName; }; - - diff --git a/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 b/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 index 92be13b4d..779f9fd86 100644 --- a/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 +++ b/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 @@ -1,16 +1,14 @@ # culture="en-US" ConvertFrom-StringData @' -MissingRoleMessage = Please ensure that the {0} role is installed -CheckingTrustMessage = Checking if Trust between {0} and {1} exists ... -TestTrustMessage = Trust is {0} between source and target domains and it should be {1} -RemovingTrustMessage = Removing trust between {0} and {1} domains ... -DeleteTrustMessage = Trust between specified domains is now absent -AddingTrustMessage = Adding domain trust between {0} and {1} ... -SetTrustMessage = Trust between specified domains is now present -CheckPropertyMessage = Checking for {0} between domains ... -DesiredPropertyMessage = {0} between domains is set correctly -NotDesiredPropertyMessage = {0} between domains is not correct. Expected {1}, actual {2} -SetPropertyMessage = {0} between domains is set -TrustPresentMessage = Trust between domains {0} and {1} is present -TrustAbsentMessage = Trust between domains {0} and {1} is absent +CheckingTrustMessage = Determining if the trust between the '{0}' and the '{1}' with the context type '{2}' exists. (ADDT0001) +RemovedTrust = Trust between between domains '{0}' and '{1}' with the context type '{2}' has been removed. (ADDT0002) +AddedTrust = Created the trust between domains '{0}' and '{1}' with the context type '{2}' and direction '{3}'. (ADDT0003) +SetTrustDirection = The trust direction has been changed to '{0}'. (ADDT0004) +TrustPresentMessage = The trust between domains '{0}' and '{1}' with the context type '{2}' exist. (ADDT0005) +TrustAbsentMessage = There is no trust between domains '{0}' and '{1}' with the context type '{2}'. (ADDT0006) +TestConfiguration = Determining the current state of the Active Directory trust with source domain '{0}', target domain '{1}' and context type '{2}'. (ADDT0007) +InDesiredState = The Active Directory trust is in the desired state. (ADDT0008) +NotInDesiredState = The Active Directory trust is not in the desired state. (ADDT0009) +NeedToRecreateTrust = The trust type is not in desired state, removing the trust between the domains '{0}' and '{1}' with the context type '{2}' to be able to recreate the trust with the correct context type '{3}'. (ADDT0010) +RecreatedTrustType = Recreated the trust between domains '{0}' and '{1}' with the context type '{2}' and direction '{3}'. (ADDT0011) '@ diff --git a/DSCResources/MSFT_xADDomainTrust/en-US/about_xADDomainTrust.help.txt b/DSCResources/MSFT_xADDomainTrust/en-US/about_xADDomainTrust.help.txt index 9979d78de..30c553d60 100644 --- a/DSCResources/MSFT_xADDomainTrust/en-US/about_xADDomainTrust.help.txt +++ b/DSCResources/MSFT_xADDomainTrust/en-US/about_xADDomainTrust.help.txt @@ -11,29 +11,29 @@ .PARAMETER Ensure Write - String Allowed values: Present, Absent - Should this resource be present or absent + Specifies whether the computer account is present or absent. Valid values are 'Present' and 'Absent'. The default is 'Present'. .PARAMETER TargetDomainAdministratorCredential Required - String - Credentials to authenticate to the target domain + Specifies the credentials to authenticate to the target domain. .PARAMETER TargetDomainName Key - String - Name of the AD domain that is being trusted + Specifies the name of the Active Directory domain that is being trusted. .PARAMETER TrustType Required - String Allowed values: External, Forest - Type of trust + Specifies the type of trust. Valid values are 'External' or 'Forest'. 'External' means the context Domain, while 'Forest' means the context 'Forest'. .PARAMETER TrustDirection Required - String Allowed values: Bidirectional, Inbound, Outbound - Direction of trust + Specifies the direction of the trust. Valid values are 'Bidirectional', 'Inbound', and 'Outbound'. .PARAMETER SourceDomainName Key - String - Name of the AD domain that is requesting the trust + Specifies the name of the Active Directory domain that is requesting the trust. .EXAMPLE 1 diff --git a/Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 b/Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 new file mode 100644 index 000000000..ce8977633 --- /dev/null +++ b/Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 @@ -0,0 +1,938 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param () + +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADDomainTrust' + +#region HEADER + +# Unit Test Template Version: 1.2.4 +$script:moduleRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot) +if ( (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` + (-not (Test-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) +{ + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) +} + +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + +$TestEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` + -TestType Unit + +#endregion HEADER + +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + +# Begin Testing +try +{ + Invoke-TestSetup + + InModuleScope $script:dscResourceName { + $mockSourceDomainName = 'contoso.com' + $mockTargetDomainName = 'lab.local' + + $mockCredentialUserName = 'COMPANY\User' + $mockCredentialPassword = 'dummyPassw0rd' | ConvertTo-SecureString -AsPlainText -Force + $mockCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( + $mockCredentialUserName, $mockCredentialPassword + ) + + Describe 'MSFT_xADDomainTrust\Get-TargetResource' -Tag 'Get' { + BeforeAll { + Mock -CommandName Get-ADDirectoryContext -MockWith { + # This should work on any client, domain joined or not. + return [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new('Domain') + } + + $mockDefaultParameters = @{ + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + TrustDirection = 'Outbound' + Verbose = $true + } + } + + Context 'When the system is in the desired state' { + Context 'When the domain trust is present in Active Directory' { + Context 'When the called with the TrustType ''External''' { + BeforeAll { + Mock -CommandName Get-ActiveDirectoryDomain -MockWith { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'GetTrustRelationship' -Value { + $script:getTrustRelationshipMethodCallCount += 1 + + return @{ + TrustType = 'Domain' + TrustDirection = 'Outbound' + } + } -PassThru -Force + } + } + + BeforeEach { + $script:getTrustRelationshipMethodCallCount = 0 + + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + $mockGetTargetResourceParameters['TrustType'] = 'External' + } + + AfterEach { + $script:getTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + + It 'Should return the state as present' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.Ensure | Should -Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.SourceDomainName | Should -Be $mockGetTargetResourceParameters.SourceDomainName + $getTargetResourceResult.TargetDomainName | Should -Be $mockGetTargetResourceParameters.TargetDomainName + $getTargetResourceResult.TargetDomainAdministratorCredential.UserName | Should -Be $mockCredential.UserName + } + + It 'Should return the correct values for the other properties' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.TrustDirection | Should -Be $mockGetTargetResourceParameters.TrustDirection + $getTargetResourceResult.TrustType | Should -Be $mockGetTargetResourceParameters.TrustType + } + } + + Context 'When the called with the TrustType ''Forest''' { + BeforeAll { + Mock -CommandName Get-ActiveDirectoryForest -MockWith { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'GetTrustRelationship' -Value { + $script:getTrustRelationshipMethodCallCount += 1 + + return @{ + TrustType = 'Forest' + TrustDirection = 'Outbound' + } + } -PassThru -Force + } + } + + BeforeEach { + $script:getTrustRelationshipMethodCallCount = 0 + + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + $mockGetTargetResourceParameters['TrustType'] = 'Forest' + } + + AfterEach { + $script:getTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-ActiveDirectoryForest -Exactly -Times 2 -Scope It + } + + It 'Should return the state as present' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.Ensure | Should -Be 'Present' + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.SourceDomainName | Should -Be $mockGetTargetResourceParameters.SourceDomainName + $getTargetResourceResult.TargetDomainName | Should -Be $mockGetTargetResourceParameters.TargetDomainName + $getTargetResourceResult.TargetDomainAdministratorCredential.UserName | Should -Be $mockCredential.UserName + } + + It 'Should return the correct values for the other properties' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.TrustDirection | Should -Be $mockGetTargetResourceParameters.TrustDirection + $getTargetResourceResult.TrustType | Should -Be $mockGetTargetResourceParameters.TrustType + } + } + } + + Context 'When the domain trust is absent from Active Directory' { + BeforeAll { + Mock -CommandName Get-ActiveDirectoryForest -MockWith { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'GetTrustRelationship' -Value { + $script:GetTrustRelationshipMethodCallCount += 1 + + throw + } -PassThru -Force + } + } + + BeforeEach { + $script:GetTrustRelationshipMethodCallCount = 0 + + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + $mockGetTargetResourceParameters['TrustType'] = 'Forest' + } + + AfterEach { + $script:getTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-ActiveDirectoryForest -Exactly -Times 2 -Scope It + } + + It 'Should return the state as absent' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.Ensure | Should -Be 'Absent' + } + + It 'Should return the same values as passed as parameters' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.SourceDomainName | Should -Be $mockGetTargetResourceParameters.SourceDomainName + $getTargetResourceResult.TargetDomainName | Should -Be $mockGetTargetResourceParameters.TargetDomainName + $getTargetResourceResult.TargetDomainAdministratorCredential.UserName | Should -Be $mockCredential.UserName + } + + It 'Should return the correct values for the other properties' { + $getTargetResourceResult = Get-TargetResource @mockGetTargetResourceParameters + $getTargetResourceResult.TrustDirection | Should -BeNullOrEmpty + $getTargetResourceResult.TrustType | Should -BeNullOrEmpty + } + } + } + + } + + Describe 'MSFT_xADDomainTrust\Test-TargetResource' -Tag 'Test' { + BeforeAll { + $mockDefaultParameters = @{ + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + Verbose = $true + } + } + + Context 'When the system is in the desired state' { + Context 'When the trust is absent from Active Directory' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $true + } + ) + } + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['Ensure'] = 'Absent' + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return $true' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeTrue + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + + Context 'When the trust is present in Active Directory' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $true + } + ) + } + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return $true' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeTrue + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the trust should be absent from Active Directory' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $false + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $true + } + ) + } + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['Ensure'] = 'Absent' + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return $false' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeFalse + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + + Context 'When the trust should be present in Active Directory' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $false + } + ) + } + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return $false' { + $testTargetResourceResult = Test-TargetResource @testTargetResourceParameters + $testTargetResourceResult | Should -BeFalse + + Assert-MockCalled -CommandName Compare-TargetResourceState -Exactly -Times 1 -Scope It + } + } + } + } + + Describe 'MSFT_xADDomainTrust\Compare-TargetResourceState' -Tag 'Compare' { + BeforeAll { + $mockDefaultParameters = @{ + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + Verbose = $true + } + + $mockGetTargetResource_Absent = { + return @{ + Ensure = 'Absent' + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + TrustDirection = $null + TrustType = $null + } + } + + $mockGetTargetResource_Present = { + return @{ + Ensure = 'Present' + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + TrustDirection = 'Outbound' + TrustType = 'External' + } + } + } + + Context 'When the system is in the desired state' { + Context 'When the trust is absent from Active Directory' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith $mockGetTargetResource_Absent + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['Ensure'] = 'Absent' + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return the correct values' { + $compareTargetResourceStateResult = Compare-TargetResourceState @testTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Ensure' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'Absent' + $comparedReturnValue.Actual | Should -Be 'Absent' + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the trust is present in Active Directory' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith $mockGetTargetResource_Present + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return the correct values' { + $compareTargetResourceStateResult = Compare-TargetResourceState @testTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Ensure' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'Present' + $comparedReturnValue.Actual | Should -Be 'Present' + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TrustType' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'External' + $comparedReturnValue.Actual | Should -Be 'External' + $comparedReturnValue.InDesiredState | Should -BeTrue + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TrustDirection' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'Outbound' + $comparedReturnValue.Actual | Should -Be 'Outbound' + $comparedReturnValue.InDesiredState | Should -BeTrue + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the trust should be absent from Active Directory' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith $mockGetTargetResource_Present + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['Ensure'] = 'Absent' + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return the correct values' { + $compareTargetResourceStateResult = Compare-TargetResourceState @testTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Ensure' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'Absent' + $comparedReturnValue.Actual | Should -Be 'Present' + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When the trust should be present in Active Directory' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith $mockGetTargetResource_Absent + + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + } + + It 'Should return the correct values' { + $compareTargetResourceStateResult = Compare-TargetResourceState @testTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'Ensure' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'Present' + $comparedReturnValue.Actual | Should -Be 'Absent' + $comparedReturnValue.InDesiredState | Should -BeFalse + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TrustType' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'External' + $comparedReturnValue.Actual | Should -BeNullOrEmpty + $comparedReturnValue.InDesiredState | Should -BeFalse + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq 'TrustDirection' }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be 'Outbound' + $comparedReturnValue.Actual | Should -BeNullOrEmpty + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + + Context 'When a property is not in desired state' { + BeforeAll { + Mock -CommandName Get-TargetResource -MockWith $mockGetTargetResource_Present + + + # One test case per property with a value that differs from the desired state. + $testCases_Properties = @( + @{ + ParameterName = 'TrustType' + Value = 'Forest' + }, + @{ + ParameterName = 'TrustDirection' + Value = 'Inbound' + } + ) + } + + It 'Should return the correct values when the property is not in desired state' -TestCases $testCases_Properties { + param + ( + [Parameter()] + $ParameterName, + + [Parameter()] + $Value + ) + + # Set up all mandatory parameters to use the values in the mock. + $testTargetResourceParameters = $mockDefaultParameters.Clone() + $testTargetResourceParameters['TrustType'] = 'External' + $testTargetResourceParameters['TrustDirection'] = 'Outbound' + + # Change the property we are currently testing to a different value. + $testTargetResourceParameters[$ParameterName] = $Value + + $compareTargetResourceStateResult = Compare-TargetResourceState @testTargetResourceParameters + $compareTargetResourceStateResult | Should -HaveCount 3 + + $comparedReturnValue = $compareTargetResourceStateResult.Where( { $_.ParameterName -eq $ParameterName }) + $comparedReturnValue | Should -Not -BeNullOrEmpty + $comparedReturnValue.Expected | Should -Be $Value + $comparedReturnValue.Actual | Should -Be (& $mockGetTargetResource_Present).$ParameterName + $comparedReturnValue.InDesiredState | Should -BeFalse + + Assert-MockCalled -CommandName Get-TargetResource -Exactly -Times 1 -Scope It + } + } + } + } + + Describe 'MSFT_xADDomainTrust\Set-TargetResource' -Tag 'Set' { + BeforeAll { + Mock -CommandName Get-ADDirectoryContext -MockWith { + # This should work on any client, domain joined or not. + return [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new('Domain') + } + + $mockGetActiveDirectoryDomainOrForest = { + return New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'CreateTrustRelationship' -Value { + $script:createTrustRelationshipMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'DeleteTrustRelationship' -Value { + $script:deleteTrustRelationshipMethodCallCount += 1 + } -PassThru | + Add-Member -MemberType ScriptMethod -Name 'UpdateTrustRelationship' -Value { + $script:updateTrustRelationshipMethodCallCount += 1 + } -PassThru -Force + } + + Mock -CommandName Get-ActiveDirectoryDomain -MockWith $mockGetActiveDirectoryDomainOrForest + Mock -CommandName Get-ActiveDirectoryForest -MockWith $mockGetActiveDirectoryDomainOrForest + + $mockDefaultParameters = @{ + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + TrustDirection = 'Outbound' + Verbose = $true + } + } + + Context 'When the system is in the desired state' { + Context 'When the domain trust is present in Active Directory' { + Context 'When the called with the TrustType ''External''' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $true + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'External' + } + + It 'Should not throw and not call any methods' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 0 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 0 + $script:updateTrustRelationshipMethodCallCount | Should -Be 0 + + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + } + + Context 'When the called with the TrustType ''Forest''' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $true + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'Forest' + } + + It 'Should not throw and not call any methods' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 0 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 0 + $script:updateTrustRelationshipMethodCallCount | Should -Be 0 + + Assert-MockCalled -CommandName Get-ActiveDirectoryForest -Exactly -Times 2 -Scope It + } + } + } + + Context 'When the domain trust is absent in Active Directory' { + Context 'When the called with the TrustType ''External''' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $true + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'External' + $setTargetResourceParameters['Ensure'] = 'Absent' + } + + It 'Should not throw and not call any methods' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 0 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 0 + $script:updateTrustRelationshipMethodCallCount | Should -Be 0 + + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the domain trust should be present in Active Directory' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $false + } + @{ + ParameterName = 'TrustType' + InDesiredState = $false + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $false + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'External' + } + + It 'Should not throw and call the correct method' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 1 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 0 + $script:updateTrustRelationshipMethodCallCount | Should -Be 0 + + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + } + + Context 'When the domain trust should be absent from Active Directory' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + InDesiredState = $false + } + @{ + ParameterName = 'TrustType' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + InDesiredState = $true + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'External' + $setTargetResourceParameters['Ensure'] = 'Absent' + } + + It 'Should not throw and call the correct method' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 0 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 1 + $script:updateTrustRelationshipMethodCallCount | Should -Be 0 + + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + } + + Context 'When a property of a domain trust is not in desired state' { + Context 'When both properties TrustType and and TrustDirection is not in desired state' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + Actual = 'Present' + Expected = 'Present' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + Actual = 'Domain' + Expected = 'Forest' + InDesiredState = $false + } + @{ + ParameterName = 'TrustDirection' + Actual = 'Outbound' + Expected = 'Inbound' + InDesiredState = $false + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'Forest' + $setTargetResourceParameters['TrustDirection'] = 'Inbound' + } + + It 'Should not throw and call the correct methods' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 1 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 1 + $script:updateTrustRelationshipMethodCallCount | Should -Be 0 + + Assert-MockCalled -CommandName Get-ActiveDirectoryForest -Exactly -Times 2 -Scope It + } + } + + Context 'When property TrustDirection is not in desired state' { + BeforeAll { + Mock -CommandName Compare-TargetResourceState -MockWith { + return @( + @{ + ParameterName = 'Ensure' + Actual = 'Present' + Expected = 'Present' + InDesiredState = $true + } + @{ + ParameterName = 'TrustType' + Actual = 'Domain' + Expected = 'Domain' + InDesiredState = $true + } + @{ + ParameterName = 'TrustDirection' + Actual = 'Outbound' + Expected = 'Inbound' + InDesiredState = $false + } + ) + } + } + + BeforeEach { + $script:createTrustRelationshipMethodCallCount = 0 + $script:deleteTrustRelationshipMethodCallCount = 0 + $script:updateTrustRelationshipMethodCallCount = 0 + + $setTargetResourceParameters = $mockDefaultParameters.Clone() + $setTargetResourceParameters['TrustType'] = 'External' + $setTargetResourceParameters['TrustDirection'] = 'Inbound' + } + + It 'Should not throw and call the correct method' { + { Set-TargetResource @setTargetResourceParameters } | Should -Not -Throw + + $script:createTrustRelationshipMethodCallCount | Should -Be 0 + $script:deleteTrustRelationshipMethodCallCount | Should -Be 0 + $script:updateTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + } + } + } + } + + Describe 'MSFT_xADDomainTrust\ConvertTo-DirectoryContextType' -Tag 'Helper' { + BeforeAll { + $testCases = @( + @{ + TrustTypeValue = 'External' + DirectoryContextTypeValue = 'Domain' + }, + @{ + TrustTypeValue = 'Forest' + DirectoryContextTypeValue = 'Forest' + } + ) + } + + It 'Should return the correct converted value for the trust type value ' -TestCases $testCases { + param + ( + [Parameter()] + $TrustTypeValue, + + [Parameter()] + $DirectoryContextTypeValue + ) + + $convertToDirectoryContextTypeResult = ConvertTo-DirectoryContextType -TrustType $TrustTypeValue + $convertToDirectoryContextTypeResult | Should -Be $DirectoryContextTypeValue + } + + It 'Should return the correct converted value for the directory context type value ' -TestCases $testCases { + param + ( + [Parameter()] + $TrustTypeValue, + + [Parameter()] + $DirectoryContextTypeValue + ) + + $convertFromDirectoryContextTypeResult = ConvertFrom-DirectoryContextType -DirectoryContextType $DirectoryContextTypeValue + $convertFromDirectoryContextTypeResult | Should -Be $TrustTypeValue + } + } + } +} +finally +{ + Invoke-TestCleanup +}