From dd53b43a46a6e7ddd101a0b7f393af0d377b100e Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 13 Jul 2019 08:47:46 +0200 Subject: [PATCH] xADDomainTrust: Refactor resource and add tests (#423) - Changes to xActiveDirectory - Added new helper functions in xActiveDirectory.Common. - New-CimCredentialInstance - Add-TypeAssembly - New-ADDirectoryContext - 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). - Added comment-based help (issue #337). - Added integration tests (issue #348). --- CHANGELOG.md | 10 + .../en-US/about_xADComputer.help.txt | 8 +- .../en-US/about_xADDomain.help.txt | 6 +- .../en-US/about_xADDomainController.help.txt | 8 +- ...ut_xADDomainDefaultPasswordPolicy.help.txt | 6 +- .../MSFT_xADDomainTrust.psm1 | 707 +++++++++---- .../MSFT_xADDomainTrust.schema.mof | 15 +- DSCResources/MSFT_xADDomainTrust/README.md | 2 +- .../en-US/MSFT_xADDomainTrust.strings.psd1 | 24 +- .../en-US/about_xADDomainTrust.help.txt | 20 +- .../en-US/about_xADForestProperties.help.txt | 6 +- .../en-US/about_xADGroup.help.txt | 6 +- .../en-US/about_xADKDSKey.help.txt | 6 +- .../about_xADManagedServiceAccount.help.txt | 6 +- .../about_xADObjectEnabledState.help.txt | 8 +- .../about_xADObjectPermissionEntry.help.txt | 6 +- .../about_xADOrganizationalUnit.help.txt | 6 +- .../en-US/about_xADRecycleBin.help.txt | 6 +- .../en-US/about_xADReplicationSite.help.txt | 6 +- .../about_xADReplicationSiteLink.help.txt | 6 +- .../en-US/about_xADReplicationSubnet.help.txt | 6 +- .../about_xADServicePrincipalName.help.txt | 6 +- .../MSFT_xADUser/en-US/about_xADUser.help.txt | 6 +- .../en-US/about_xWaitForADDomain.help.txt | 6 +- .../xActiveDirectory.Common.strings.psd1 | 9 +- .../xActiveDirectory.Common.psm1 | 189 +++- .../MSFT_xADDomainTrust.Integration.Tests.ps1 | 349 ++++++ .../MSFT_xADDomainTrust.config.ps1 | 194 ++++ Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 | 989 ++++++++++++++++++ Tests/Unit/xActiveDirectory.Common.Tests.ps1 | 131 +++ 30 files changed, 2431 insertions(+), 322 deletions(-) create mode 100644 Tests/Integration/MSFT_xADDomainTrust.Integration.Tests.ps1 create mode 100644 Tests/Integration/MSFT_xADDomainTrust.config.ps1 create mode 100644 Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e41e8d997..ce8d041b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,10 @@ - The default value on resource parameters are now reflected in the parameter descriptions in the schema.mof (so that Wiki will be updated) ([issue #426](https://github.com/PowerShell/xActiveDirectory/issues/426)). + - 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" @@ -54,6 +58,12 @@ - Changes to xADServicePrincipalName - Minor change to the unit tests that did not correct assert the localized string when an account is not found. +- 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)). + - Added integration tests ([issue #348](https://github.com/PowerShell/xActiveDirectory/issues/348)). ## 3.0.0.0 diff --git a/DSCResources/MSFT_xADComputer/en-US/about_xADComputer.help.txt b/DSCResources/MSFT_xADComputer/en-US/about_xADComputer.help.txt index 2b62f1fba..04e838474 100644 --- a/DSCResources/MSFT_xADComputer/en-US/about_xADComputer.help.txt +++ b/DSCResources/MSFT_xADComputer/en-US/about_xADComputer.help.txt @@ -6,17 +6,17 @@ This resource can be used to provision a computer account before the computer is added to the domain. These pre-created computer objects can be used with offline domain join, unsecure domain Join and RODC domain join scenarios. - + >**Note:** An Offline Domain Join (ODJ) request file will only be created >when a computer account is first created in the domain. Setting an Offline >Domain Join (ODJ) Request file path for a configuration that updates a >computer account that already exists, or restore it from the recycle bin >will not cause the Offline Domain Join (ODJ) request file to be created. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER ComputerName Key - String Specifies the name of the Active Directory computer account to manage. You can identify a computer by its distinguished name, GUID, security identifier (SID) or Security Accounts Manager (SAM) account name. diff --git a/DSCResources/MSFT_xADDomain/en-US/about_xADDomain.help.txt b/DSCResources/MSFT_xADDomain/en-US/about_xADDomain.help.txt index 3a2ee02ca..704cc5962 100644 --- a/DSCResources/MSFT_xADDomain/en-US/about_xADDomain.help.txt +++ b/DSCResources/MSFT_xADDomain/en-US/about_xADDomain.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADDomain resource creates a new domain in a new forest or a child domain in an existing forest. While it is possible to set the forest functional level and the domain functional level during deployment with this resource the common restrictions apply. For more information see [TechNet](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/active-directory-functional-levels). - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER DomainName Key - String The fully qualified domain name (FQDN) of the new domain. diff --git a/DSCResources/MSFT_xADDomainController/en-US/about_xADDomainController.help.txt b/DSCResources/MSFT_xADDomainController/en-US/about_xADDomainController.help.txt index 5f2714fe3..4b5f4a8f9 100644 --- a/DSCResources/MSFT_xADDomainController/en-US/about_xADDomainController.help.txt +++ b/DSCResources/MSFT_xADDomainController/en-US/about_xADDomainController.help.txt @@ -4,7 +4,7 @@ .DESCRIPTION The xADDomainController DSC resource will install and configure domain controllers in Active Directory. - + >**Note:** If the account used for the parameter `DomainAdministratorCredential` >cannot connect to another domain controller, for example using a credential >without the domain name, then the cmdlet `Install-ADDSDomainController` will @@ -12,11 +12,11 @@ >information from another domain controller. >Make sure to use a correct domain account with the correct permission as >the account for the parameter `DomainAdministratorCredential`. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER DomainName Key - String The fully qualified domain name (FQDN) of the domain the Domain Controller will be joining. diff --git a/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/en-US/about_xADDomainDefaultPasswordPolicy.help.txt b/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/en-US/about_xADDomainDefaultPasswordPolicy.help.txt index fcbd513a1..4c110cc33 100644 --- a/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/en-US/about_xADDomainDefaultPasswordPolicy.help.txt +++ b/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/en-US/about_xADDomainDefaultPasswordPolicy.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADDomainDefaultPasswordPolicy DSC resource will manage an Active Directory domain's default password policy. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER DomainName Key - String Name of the domain to which the password policy will be applied. diff --git a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 index 16c0122fb..0bc70b70b 100644 --- a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 +++ b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.psm1 @@ -6,6 +6,27 @@ 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. The value 'External' means the context Domain, + while the value 'Forest' means the context 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. +#> function Get-TargetResource { [CmdletBinding()] @@ -32,84 +53,83 @@ 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 - { - Add-type -AssemblyName System.DirectoryServices + # Return a credential object without the password. + $cimCredentialInstance = New-CimCredentialInstance -Credential $TargetDomainAdministratorCredential + + $returnValue = @{ + SourceDomainName = $SourceDomainName + TargetDomainName = $TargetDomainName + TargetDomainAdministratorCredential = $cimCredentialInstance } - # If not found, means ADDS role is not installed - catch - { - $missingRoleMessage = $($script:localizedData.MissingRoleMessage) -f 'AD-Domain-Services' - New-ObjectNotFoundException -Message $missingRoleMessage -ErrorRecord $_ + + $getTrustTargetAndSourceObject = @{ + SourceDomainName = $SourceDomainName + TargetDomainName = $TargetDomainName + TargetDomainAdministratorCredential = $TargetDomainAdministratorCredential + TrustType = $TrustType } + $trustSource, $trustTarget = Get-TrustSourceAndTargetObject @getTrustTargetAndSourceObject + 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. The value 'External' means the context Domain, + while the value 'Forest' means the context 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. + + .PARAMETER Ensure + Specifies whether the computer account is present or absent. Default + value is 'Present'. +#> function Set-TargetResource { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "", - Justification = 'Verbose messaging in helper function')] [CmdletBinding()] param ( @@ -141,18 +161,144 @@ function Set-TargetResource $Ensure = 'Present' ) - if ($PSBoundParameters.ContainsKey('Debug')) + $getTrustTargetAndSourceObject = @{ + SourceDomainName = $SourceDomainName + TargetDomainName = $TargetDomainName + TargetDomainAdministratorCredential = $TargetDomainAdministratorCredential + TrustType = $TrustType + } + + $trustSource, $trustTarget = Get-TrustSourceAndTargetObject @getTrustTargetAndSourceObject + + $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 + } + + <# + In case the trust direction property should be wrong, there + is 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 + ) + } + } - Confirm-ResourceProperties @PSBoundParameters -Apply + 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. The value 'External' means the context Domain, + while the value 'Forest' means the context 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. + + .PARAMETER Ensure + Specifies whether the computer account is present or absent. Default + value is 'Present'. +#> function Test-TargetResource { - [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "", - Justification = 'Verbose messaging in helper function')] [CmdletBinding()] [OutputType([System.Boolean])] param @@ -185,34 +331,61 @@ 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 comparison 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. The value 'External' means the context Domain, + while the value 'Forest' means the context 'Forest'. + + .PARAMETER TrustDirection + Specifies the direction of the trust. + + .PARAMETER Ensure + Specifies whether the computer account is present or absent. Default + value is 'Present'. +#> +function Compare-TargetResourceState { [CmdletBinding()] - [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] @@ -240,180 +413,264 @@ function Confirm-ResourceProperties [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = 'Present', - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $Apply + $Ensure = 'Present' ) - try + $getTargetResourceParameters = @{ + SourceDomainName = $SourceDomainName + TargetDomainName = $TargetDomainName + TargetDomainAdministratorCredential = $TargetDomainAdministratorCredential + TrustType = $TrustType + TrustDirection = $TrustDirection + } + + $getTargetResourceResult = Get-TargetResource @getTargetResourceParameters + + <# + 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') { - $checkingTrustMessage = $script:localizedData.CheckingTrustMessage -f $SourceDomainName, $TargetDomainName - Write-Verbose -Message $checkingTrustMessage + $propertiesToEvaluate = @( + 'Ensure' + 'TrustType' + 'TrustDirection' + ) + } + else + { + $propertiesToEvaluate = @( + 'Ensure' + ) + } - switch ($TrustType) + <# + If the user did not specify Ensure property, then it is not part of + the $PSBoundParameters, but it still needs 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 + + $compareResourcePropertyStateParameters = @{ + CurrentValues = $getTargetResourceResult + DesiredValues = $desiredValues + Properties = $propertiesToEvaluate + } + + return Compare-ResourcePropertyState @compareResourcePropertyStateParameters +} + +<# + .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 to 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 + ) + + return [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DirectoryContext) +} + +<# + .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 to 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 + ) + + return [System.DirectoryServices.ActiveDirectory.Forest]::GetForest($DirectoryContext) +} + +<# + .SYNOPSIS + This returns the converted value from a Trust Type value to the correct + Directory Context Type value. + + .PARAMETER TrustType + The trust type value to convert. +#> +function ConvertTo-DirectoryContextType +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $TrustType + ) + + switch ($TrustType) + { + 'External' { - 'External' - { - $DomainOrForest = 'Domain' - } + $directoryContextType = 'Domain' + } - 'Forest' - { - $DomainOrForest = 'Forest' - } + 'Forest' + { + $directoryContextType = '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) + return $directoryContextType +} + +<# + .SYNOPSIS + This returns the converted value from a Directory Context Type value to + the correct Trust Type value. + + .PARAMETER DirectoryContextType + The Directory Context Type value to convert. +#> +function ConvertFrom-DirectoryContextType +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DirectoryContextType + ) - # Create the source object - $srcDirectoryContext = New-Object -TypeName 'System.DirectoryServices.ActiveDirectory.DirectoryContext' -ArgumentList @($DomainOrForest, $SourceDomainName) - $srcDomain = ([type]"System.DirectoryServices.ActiveDirectory.$DomainOrForest")::"Get$DomainOrForest"($srcDirectoryContext) + switch ($DirectoryContextType) + { + 'Domain' + { + $trustType = 'External' + } - # Find trust - try + 'Forest' { - # Find trust between source & destination. - $trust = $srcDomain.GetTrustRelationship($TargetDomainName) + $trustType = 'Forest' + } + } - $TestTrustMessage = $script:localizedData.TestTrustMessage -f 'present', $Ensure - Write-Verbose -Message $TestTrustMessage + return $trustType +} - if ($Ensure -eq 'Present') - { - #region Test for trust direction - $CheckPropertyMessage = $script:localizedData.CheckPropertyMessage -f 'trust direction' - Write-Verbose -Message $CheckPropertyMessage +<# + .SYNOPSIS + Returns two objects where the first object is for the source domain and + the second object is for the target domain. - 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 + .PARAMETER SourceDomainName + Specifies the name of the Active Directory domain that is requesting the + trust. - $desiredPropertyMessage = $script:localizedData.DesiredPropertyMessage -f 'Trust direction' - Write-Verbose -Message $desiredPropertyMessage - } - #endregion trust direction + .PARAMETER TargetDomainName + Specifies the name of the Active Directory domain that is being trusted. - #region Test for trust type - $CheckPropertyMessage = $script:localizedData.CheckPropertyMessage -f 'trust type' - Write-Verbose -Message $CheckPropertyMessage + .PARAMETER TargetDomainAdministratorCredential + Specifies the credentials to authenticate to the target domain. - 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 + .PARAMETER TrustType + Specifies the type of trust. The value 'External' means the context Domain, + while the value 'Forest' means the context 'Forest'. - $desiredPropertyMessage = $script:localizedData.DesiredPropertyMessage -f 'Trust type' - Write-Verbose -Message $desiredPropertyMessage - } - #endregion Test for trust type + .OUTPUTS + For both objects the type returned is either of the type + System.DirectoryServices.ActiveDirectory.Domain or of the type + System.DirectoryServices.ActiveDirectory.Forest. +#> +function Get-TrustSourceAndTargetObject +{ + [CmdletBinding()] + [OutputType([System.Object[]])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $SourceDomainName, - # 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 + [Parameter(Mandatory = $true)] + [System.String] + $TargetDomainName, - if ($Apply) - { - $removingTrustMessage = $script:localizedData.RemovingTrustMessage -f $SourceDomainName, $TargetDomainName - Write-Verbose -Message $removingTrustMessage + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $TargetDomainAdministratorCredential, - $srcDomain.DeleteTrustRelationship($trgDomain) + [Parameter(Mandatory = $true)] + [ValidateSet('External', 'Forest')] + [System.String] + $TrustType + ) - $deleteTrustMessage = $script:localizedData.DeleteTrustMessage - Write-Verbose -Message $deleteTrustMessage - } - else - { - return $false - } - } # end Ensure -eq absent - } # end find trust - catch [System.DirectoryServices.ActiveDirectory.ActiveDirectoryObjectNotFoundException] - { - # Trust does not exist between source and destination + $directoryContextType = ConvertTo-DirectoryContextType -TrustType $TrustType - $TestTrustMessage = $script:localizedData.TestTrustMessage -f 'absent', $Ensure - Write-Verbose -Message $TestTrustMessage + # Create the target object. + $getADDirectoryContextParameters = @{ + DirectoryContextType = $directoryContextType + Name = $TargetDomainName + Credential = $TargetDomainAdministratorCredential + } - if ($Ensure -eq 'Present') - { - if ($Apply) - { - $addingTrustMessage = $script:localizedData.AddingTrustMessage -f $SourceDomainName, $TargetDomainName - Write-Verbose -Message $addingTrustMessage + $targetDirectoryContext = Get-ADDirectoryContext @getADDirectoryContextParameters - $srcDomain.CreateTrustRelationship($trgDomain, $TrustDirection) + # Create the source object. + $getADDirectoryContextParameters = @{ + DirectoryContextType = $directoryContextType + Name = $SourceDomainName + } - $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] + $sourceDirectoryContext = Get-ADDirectoryContext @getADDirectoryContextParameters + + if ($directoryContextType -eq 'Domain') { - throw + $trustSource = Get-ActiveDirectoryDomain -DirectoryContext $sourceDirectoryContext + $trustTarget = Get-ActiveDirectoryDomain -DirectoryContext $targetDirectoryContext } + else + { + $trustSource = Get-ActiveDirectoryForest -DirectoryContext $sourceDirectoryContext + $trustTarget = Get-ActiveDirectoryForest -DirectoryContext $targetDirectoryContext + } + + return $trustSource, $trustTarget } Export-ModuleMember -Function *-TargetResource diff --git a/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof b/DSCResources/MSFT_xADDomainTrust/MSFT_xADDomainTrust.schema.mof index 4f35d5a6c..4c7a3837e 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. Default value 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. The value 'External' means the context Domain, while the value 'Forest' means the context 'Forest'."), ValueMap{"External","Forest"}, Values{"External","Forest"}] String TrustType; + [Required, Description("Specifies the direction of the trust."), 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/README.md b/DSCResources/MSFT_xADDomainTrust/README.md index 4505d111a..744487b31 100644 --- a/DSCResources/MSFT_xADDomainTrust/README.md +++ b/DSCResources/MSFT_xADDomainTrust/README.md @@ -1,6 +1,6 @@ # Description -The xADDomainTrust DSC resource will manage Domain Trusts within Active Directory. A trust is a relationship, which you establish between domains, that makes it possible for users in one domain to be authenticated by a domain controller in the other domain. +The xADDomainTrust DSC resource will manage Domain Trusts within Active Directory. A trust is a relationship, which you establish between domains or forests. To understand more about trusts in Active Directory, please see the article [Forest Design Models](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/forest-design-models) for more information. ## Requirements diff --git a/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 b/DSCResources/MSFT_xADDomainTrust/en-US/MSFT_xADDomainTrust.strings.psd1 index 92be13b4d..95360d27c 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 domains '{0}' and '{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..8353b6a53 100644 --- a/DSCResources/MSFT_xADDomainTrust/en-US/about_xADDomainTrust.help.txt +++ b/DSCResources/MSFT_xADDomainTrust/en-US/about_xADDomainTrust.help.txt @@ -2,38 +2,38 @@ xADDomainTrust .DESCRIPTION - The xADDomainTrust DSC resource will manage Domain Trusts within Active Directory. A trust is a relationship, which you establish between domains, that makes it possible for users in one domain to be authenticated by a domain controller in the other domain. - + The xADDomainTrust DSC resource will manage Domain Trusts within Active Directory. A trust is a relationship, which you establish between domains or forests. To understand more about trusts in Active Directory, please see the article [Forest Design Models](https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/forest-design-models) for more information. + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Ensure Write - String Allowed values: Present, Absent - Should this resource be present or absent + Specifies whether the computer account is present or absent. Default value 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. The value 'External' means the context Domain, while the value 'Forest' means the context 'Forest'. .PARAMETER TrustDirection Required - String Allowed values: Bidirectional, Inbound, Outbound - Direction of trust + Specifies the direction of the trust. .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/DSCResources/MSFT_xADForestProperties/en-US/about_xADForestProperties.help.txt b/DSCResources/MSFT_xADForestProperties/en-US/about_xADForestProperties.help.txt index bea4b35f8..44390a3c9 100644 --- a/DSCResources/MSFT_xADForestProperties/en-US/about_xADForestProperties.help.txt +++ b/DSCResources/MSFT_xADForestProperties/en-US/about_xADForestProperties.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADForestProperties DSC resource will manage User Principal Name (UPN) suffixes and Service Principal Name (SPN) suffixes in a forest. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Credential Write - String Specifies the user account credentials to use to perform this task. diff --git a/DSCResources/MSFT_xADGroup/en-US/about_xADGroup.help.txt b/DSCResources/MSFT_xADGroup/en-US/about_xADGroup.help.txt index 1ea60eb6e..a3bf01c89 100644 --- a/DSCResources/MSFT_xADGroup/en-US/about_xADGroup.help.txt +++ b/DSCResources/MSFT_xADGroup/en-US/about_xADGroup.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADGroup DSC resource will manage groups within Active Directory. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER GroupName Key - String Name of the Active Directory group. diff --git a/DSCResources/MSFT_xADKDSKey/en-US/about_xADKDSKey.help.txt b/DSCResources/MSFT_xADKDSKey/en-US/about_xADKDSKey.help.txt index 4c887ef16..4dd5721f3 100644 --- a/DSCResources/MSFT_xADKDSKey/en-US/about_xADKDSKey.help.txt +++ b/DSCResources/MSFT_xADKDSKey/en-US/about_xADKDSKey.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADKDSKey DSC resource will manage KDS Root Keys within Active Directory. The KDS root keys are used to begin generating Group Managed Service Account (gMSA) passwords. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER EffectiveTime Key - String Specifies the Effective time when a KDS root key can be used. There is a 10 hour minimum from creation date to allow active directory to properly replicate across all domain controllers. For this reason, the date must be set in the future for creation. While this parameter accepts a string, it will be converted into a DateTime object. This will also try to take into account cultural settings. Example: '05/01/1999 13:00 using default or 'en-US' culture would be May 1st, but using 'de-DE' culture would be 5th of January. The culture is automatically pulled from the operating system and this can be checked using 'Get-Culture'. diff --git a/DSCResources/MSFT_xADManagedServiceAccount/en-US/about_xADManagedServiceAccount.help.txt b/DSCResources/MSFT_xADManagedServiceAccount/en-US/about_xADManagedServiceAccount.help.txt index 663494974..a451495cb 100644 --- a/DSCResources/MSFT_xADManagedServiceAccount/en-US/about_xADManagedServiceAccount.help.txt +++ b/DSCResources/MSFT_xADManagedServiceAccount/en-US/about_xADManagedServiceAccount.help.txt @@ -4,12 +4,12 @@ .DESCRIPTION The xADManagedServiceAccount DSC resource will manage Single and Group Managed Service Accounts (MSAs) within Active Directory. A Managed Service Account is a managed domain account that provides automatic password management, simplified service principal name (SPN) management and the ability to delegate management to other administrators. A Single Managed Service Account can only be used on a single computer, whereas a Group Managed Service Account can be shared across multiple computers. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. * Group Managed Service Accounts need at least one Windows Server 2012 Domain Controller. - + .PARAMETER ServiceAccountName Key - String Specifies the Security Account Manager (SAM) account name of the managed service account (ldapDisplayName 'sAMAccountName'). To be compatible with older operating systems, create a SAM account name that is 20 characters or less. Once created, the user's SamAccountName and CN cannot be changed. diff --git a/DSCResources/MSFT_xADObjectEnabledState/en-US/about_xADObjectEnabledState.help.txt b/DSCResources/MSFT_xADObjectEnabledState/en-US/about_xADObjectEnabledState.help.txt index 5773b0adf..6bf4eb98d 100644 --- a/DSCResources/MSFT_xADObjectEnabledState/en-US/about_xADObjectEnabledState.help.txt +++ b/DSCResources/MSFT_xADObjectEnabledState/en-US/about_xADObjectEnabledState.help.txt @@ -3,7 +3,7 @@ .DESCRIPTION This resource enforces the property `Enabled` on the object class *Computer*. - + >This resource could support other object classes like *msDS-ManagedServiceAccount*, >*msDS-GroupManagedServiceAccount*, and *User*. But these object classes >are not yet supported due to that other resources already enforces the @@ -11,11 +11,11 @@ >then it should be made so that only one resource enforces the enabled >property. This is to prevent a potential "ping-pong" behavior if both >resource would be used in a configuration. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Identity Key - String Specifies the identity of an object that has the object class specified in the parameter ObjectClass. When ObjectClass is set to 'Computer' then this property can be set to either distinguished name, GUID (objectGUID), security identifier (objectSid), or security Accounts Manager account name (sAMAccountName). diff --git a/DSCResources/MSFT_xADObjectPermissionEntry/en-US/about_xADObjectPermissionEntry.help.txt b/DSCResources/MSFT_xADObjectPermissionEntry/en-US/about_xADObjectPermissionEntry.help.txt index dc70cfdf7..412365e51 100644 --- a/DSCResources/MSFT_xADObjectPermissionEntry/en-US/about_xADObjectPermissionEntry.help.txt +++ b/DSCResources/MSFT_xADObjectPermissionEntry/en-US/about_xADObjectPermissionEntry.help.txt @@ -6,11 +6,11 @@ designed to to manage just one entry in the list of permissios (ACL) for one AD object. It will only interact with the one permission and leave all others as they were. The resource can be used multiple times to add multiple entries into one ACL. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Ensure Write - String Allowed values: Present, Absent diff --git a/DSCResources/MSFT_xADOrganizationalUnit/en-US/about_xADOrganizationalUnit.help.txt b/DSCResources/MSFT_xADOrganizationalUnit/en-US/about_xADOrganizationalUnit.help.txt index f419680f9..d0919de37 100644 --- a/DSCResources/MSFT_xADOrganizationalUnit/en-US/about_xADOrganizationalUnit.help.txt +++ b/DSCResources/MSFT_xADOrganizationalUnit/en-US/about_xADOrganizationalUnit.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADOrganizational Unit DSC resource will manage Organizational Units (OUs) within Active Directory. An OU is a subdivision within an Active Directory into which you can place users, groups, computers, and other organizational units. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Name Key - String The name of Organization Unit (OU). diff --git a/DSCResources/MSFT_xADRecycleBin/en-US/about_xADRecycleBin.help.txt b/DSCResources/MSFT_xADRecycleBin/en-US/about_xADRecycleBin.help.txt index 11afb88bd..506cb98ed 100644 --- a/DSCResources/MSFT_xADRecycleBin/en-US/about_xADRecycleBin.help.txt +++ b/DSCResources/MSFT_xADRecycleBin/en-US/about_xADRecycleBin.help.txt @@ -6,11 +6,11 @@ This resource first verifies that the forest mode is Windows Server 2008 R2 or greater. If the forest mode is insufficient, then the resource will exit with an error message. The change is executed against the Domain Naming Master FSMO of the forest. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER ForestFQDN Key - String The fully qualified domain name (FQDN) of the forest in which to change the Recycle Bin feature. diff --git a/DSCResources/MSFT_xADReplicationSite/en-US/about_xADReplicationSite.help.txt b/DSCResources/MSFT_xADReplicationSite/en-US/about_xADReplicationSite.help.txt index 5edd66a7d..11bf6efd7 100644 --- a/DSCResources/MSFT_xADReplicationSite/en-US/about_xADReplicationSite.help.txt +++ b/DSCResources/MSFT_xADReplicationSite/en-US/about_xADReplicationSite.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADReplicationSite DSC resource will manage Replication Sites within Active Directory. Sites are used in Active Directory to either enable clients to discover network resources (published shares, domain controllers) close to the physical location of a client computer or to reduce network traffic over wide area network (WAN) links. Sites can also be used to optimize replication between domain controllers. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Ensure Write - String Allowed values: Present, Absent diff --git a/DSCResources/MSFT_xADReplicationSiteLink/en-US/about_xADReplicationSiteLink.help.txt b/DSCResources/MSFT_xADReplicationSiteLink/en-US/about_xADReplicationSiteLink.help.txt index fe47d6965..711c939a6 100644 --- a/DSCResources/MSFT_xADReplicationSiteLink/en-US/about_xADReplicationSiteLink.help.txt +++ b/DSCResources/MSFT_xADReplicationSiteLink/en-US/about_xADReplicationSiteLink.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADReplicationSiteLink DSC resource will manage Replication Site Links within Active Directory. A site link connects two or more sites. Site links reflect the administrative policy for how sites are to be interconnected and the methods used to transfer replication traffic. You must connect sites with site links so that domain controllers at each site can replicate Active Directory changes. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Name Key - String Specifies the name of the site link. diff --git a/DSCResources/MSFT_xADReplicationSubnet/en-US/about_xADReplicationSubnet.help.txt b/DSCResources/MSFT_xADReplicationSubnet/en-US/about_xADReplicationSubnet.help.txt index ab3121313..8fc5dbf05 100644 --- a/DSCResources/MSFT_xADReplicationSubnet/en-US/about_xADReplicationSubnet.help.txt +++ b/DSCResources/MSFT_xADReplicationSubnet/en-US/about_xADReplicationSubnet.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADReplicationSubnet DSC resource will manage replication subnets. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Ensure Write - String Allowed values: Present, Absent diff --git a/DSCResources/MSFT_xADServicePrincipalName/en-US/about_xADServicePrincipalName.help.txt b/DSCResources/MSFT_xADServicePrincipalName/en-US/about_xADServicePrincipalName.help.txt index dde03cb89..fb092c723 100644 --- a/DSCResources/MSFT_xADServicePrincipalName/en-US/about_xADServicePrincipalName.help.txt +++ b/DSCResources/MSFT_xADServicePrincipalName/en-US/about_xADServicePrincipalName.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADServicePrincipalName DSC resource will manage service principal names. A service principal name (SPN) is a unique identifier of a service instance. SPNs are used by Kerberos authentication to associate a service instance with a service logon account. This allows a client application to request that the service authenticate an account even if the client does not have the account name. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER Ensure Write - String Allowed values: Present, Absent diff --git a/DSCResources/MSFT_xADUser/en-US/about_xADUser.help.txt b/DSCResources/MSFT_xADUser/en-US/about_xADUser.help.txt index 3bf8b18f6..0f093235b 100644 --- a/DSCResources/MSFT_xADUser/en-US/about_xADUser.help.txt +++ b/DSCResources/MSFT_xADUser/en-US/about_xADUser.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xADUser DSC resource will manage Users within Active Directory. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER DomainName Key - String Name of the domain where the user account is located (only used if password is managed). diff --git a/DSCResources/MSFT_xWaitForADDomain/en-US/about_xWaitForADDomain.help.txt b/DSCResources/MSFT_xWaitForADDomain/en-US/about_xWaitForADDomain.help.txt index c21d99c71..a23231bc3 100644 --- a/DSCResources/MSFT_xWaitForADDomain/en-US/about_xWaitForADDomain.help.txt +++ b/DSCResources/MSFT_xWaitForADDomain/en-US/about_xWaitForADDomain.help.txt @@ -3,11 +3,11 @@ .DESCRIPTION The xWaitForADDomain resource is used to wait for Active Directory to become available. - + ## Requirements - + * Target machine must be running Windows Server 2008 R2 or later. - + .PARAMETER DomainName Key - String The name of the Active Directory domain to wait for. diff --git a/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 b/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 index 6bf9054ba..16692289e 100644 --- a/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 +++ b/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 @@ -10,7 +10,7 @@ ConvertFrom-StringData @' ArrayValueThatDoesNotMatch = {0} - {1} (ADCOMMON0007) PropertyValueOfTypeDoesNotMatch = {0} value does not match. Current value is '{1}', but expected the value '{2}'. (ADCOMMON0008) UnableToCompareType = Unable to compare the type {0} as it is not handled by the Test-DscPropertyState cmdlet. (ADCOMMON0009) - RoleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed. (ADCOMMON0010) + ModuleNotFoundError = Please ensure that the PowerShell module for role '{0}' is installed. (ADCOMMON0010) MembersAndIncludeExcludeError = The '{0}' and '{1}' and/or '{2}' parameters conflict. The '{0}' parameter should not be used in any combination with the '{1}' and '{2}' parameters. (ADCOMMON0011) MembersIsNullError = The Members parameter value is null. The '{0}' parameter must be provided if neither '{1}' nor '{2}' is provided. (ADCOMMON0012) IncludeAndExcludeConflictError = The member '{0}' is included in both '{1}' and '{2}' parameter values. The same member must not be included in both '{1}' and '{2}' parameter values. (ADCOMMON0014) @@ -39,4 +39,11 @@ ConvertFrom-StringData @' ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. (ADCOMMON0039) UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-DscParameterState cmdlet. (ADCOMMON0040) StartProcess = Started the process with id {0} using the path '{1}', and with a timeout value of {2} seconds. (ADCOMMON0041) + CouldNotLoadAssembly = The assembly '{0}' could not be loaded into the PowerShell session. (ADCOMMON0042) + TypeAlreadyExistInSession = The type '{0}' is already loaded into the PowerShell session. (ADCOMMON0043) + TypeDoesNotExistInSession = Missing the type '{0}' from the PowerShell session. (ADCOMMON0044) + AddingAssemblyToSession = Adding the assembly '{0}' into the PowerShell session. (ADCOMMON0045) + NewDirectoryContext = Get a new Active Directory context of the type '{0}'. (ADCOMMON0046) + NewDirectoryContextTarget = The Active Directory context will target '{0}'. (ADCOMMON0047) + NewDirectoryContextCredential = The Active Directory context will be accessed using the '{0}' credentials. (ADCOMMON0048) '@ diff --git a/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 b/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 index f91c8ca83..ab44aebc1 100644 --- a/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 +++ b/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 @@ -488,7 +488,7 @@ function Assert-Module if (-not (Get-Module -Name $ModuleName -ListAvailable)) { - $errorMessage = $script:localizedData.RoleNotFoundError -f $moduleName + $errorMessage = $script:localizedData.ModuleNotFoundError -f $moduleName New-ObjectNotFoundException -Message $errorMessage } @@ -1500,10 +1500,9 @@ function Convert-PropertyMapToObjectProperties values. Normally set to $PSBoundParameters. .PARAMETER Properties - An array of property names to filter out from the keys provided in - DesiredValues. If left out, only those keys in the DesiredValues will - be compared. This parameter can be used to remove certain keys from - the comparison. + An array of property names, from the keys provided in DesiredValues, that + will be compared. If this parameter is left out, all the keys in the + DesiredValues will be compared. #> function Compare-ResourcePropertyState { @@ -1730,7 +1729,8 @@ function Assert-ADPSDrive if ($null -eq $activeDirectoryPSDrive) { - Write-Verbose -Message $script:localizedData.CreatingNewADPSDrive + Write-Verbose -Message $script:localizedData.CreatingNewADPSDrive -Verbose + try { New-PSDrive -Name AD -PSProvider 'ActiveDirectory' -Root $Root -Scope Script -ErrorAction 'Stop' | @@ -1768,6 +1768,180 @@ function Set-DscADComputer Set-ADComputer @Parameters | Out-Null } +<# + .SYNOPSIS + This returns a new MSFT_Credential CIM instance credential object to be + used when returning credential objects from Get-TargetResource. + This returns a credential object without the password. + + .PARAMETER Credential + The PSCredential object to return as a MSFT_Credential CIM instance + credential object. + + .NOTES + When returning a PSCredential object from Get-TargetResource, the + credential object does not contain the username. The object is empty. + + Password UserName PSComputerName + -------- -------- -------------- + localhost + + When the MSFT_Credential CIM instance credential object is returned by + the Get-TargetResource then the credential object contains the values + provided in the object. + + Password UserName PSComputerName + -------- -------- -------------- + COMPANY\TestAccount localhost +#> +function New-CimCredentialInstance +{ + [CmdletBinding()] + [OutputType([Microsoft.Management.Infrastructure.CimInstance])] + param + ( + [Parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential + ) + + $newCimInstanceParameters = @{ + ClassName = 'MSFT_Credential' + ClientOnly = $true + Namespace = 'root/microsoft/windows/desiredstateconfiguration' + Property = @{ + UserName = [System.String] $Credential.UserName + Password = [System.String] $null + } + } + + return New-CimInstance @newCimInstanceParameters +} + +<# + .SYNOPSIS + This loads the assembly type, optionally after a check + if the type is missing in the PowerShell session. + + .PARAMETER AssemblyName + The assembly to load into the PowerShell session. + + .PARAMETER TypeName + An optional parameter to check if the type exist, if it exist then the + assembly is not loaded again. +#> +function Add-TypeAssembly +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $AssemblyName, + + [Parameter()] + [System.String] + $TypeName + ) + + if ($PSBoundParameters.ContainsKey('TypeName')) + { + if ($TypeName -as [Type]) + { + Write-Verbose -Message ($script:localizedData.TypeAlreadyExistInSession -f $TypeName) -Verbose + + # The type already exists so no need to load the type again. + return + } + else + { + Write-Verbose -Message ($script:localizedData.TypeDoesNotExistInSession -f $TypeName) -Verbose + } + } + + try + { + Write-Verbose -Message ($script:localizedData.AddingAssemblyToSession -f $AssemblyName) -Verbose + + Add-Type -AssemblyName $AssemblyName + } + catch + { + $missingRoleMessage = $script:localizedData.CouldNotLoadAssembly -f $AssemblyName + New-ObjectNotFoundException -Message $missingRoleMessage -ErrorRecord $_ + } +} + +<# + .SYNOPSIS + This returns a new object of the type System.DirectoryServices.ActiveDirectory.DirectoryContext. + + .PARAMETER DirectoryContextType + The context type of the object to return. Valid values are 'Domain', 'Forest', + 'ApplicationPartition', 'ConfigurationSet' or 'DirectoryServer'. + + .PARAMETER Name + An optional parameter for the target of the directory context. + For the correct format for this parameter depending on context type, see + the article https://docs.microsoft.com/en-us/dotnet/api/system.directoryservices.activedirectory.directorycontext?view=netframework-4.8 +#> +function Get-ADDirectoryContext +{ + [CmdletBinding()] + [OutputType([System.DirectoryServices.ActiveDirectory.DirectoryContext])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateSet('Domain', 'Forest', 'ApplicationPartition', 'ConfigurationSet', 'DirectoryServer')] + [System.String] + $DirectoryContextType, + + [Parameter()] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential + ) + + $typeName = 'System.DirectoryServices.ActiveDirectory.DirectoryContext' + + Add-TypeAssembly -AssemblyName 'System.DirectoryServices' -TypeName $typeName + + Write-Verbose -Message ($script:localizedData.NewDirectoryContext -f $DirectoryContextType) -Verbose + + $newObjectArgumentList = @( + $DirectoryContextType + ) + + if ($PSBoundParameters.ContainsKey('Name')) + { + Write-Verbose -Message ($script:localizedData.NewDirectoryContextTarget -f $Name) -Verbose + + $newObjectArgumentList += @( + $Name + ) + } + + if ($PSBoundParameters.ContainsKey('Credential')) + { + Write-Verbose -Message ($script:localizedData.NewDirectoryContextCredential -f $Credential.UserName) -Verbose + + $newObjectArgumentList += @( + $Credential.UserName + $Credential.GetNetworkCredential().Password + ) + } + + $newObjectParameters = @{ + TypeName = $typeName + ArgumentList = $newObjectArgumentList + } + + return New-Object @newObjectParameters +} + $script:localizedData = Get-LocalizedData -ResourceName 'xActiveDirectory.Common' -ScriptRoot $PSScriptRoot Export-ModuleMember -Function @( @@ -1802,4 +1976,7 @@ Export-ModuleMember -Function @( 'Test-DscPropertyState' 'Assert-ADPSDrive' 'Set-DscADComputer' + 'New-CimCredentialInstance' + 'Add-TypeAssembly' + 'Get-ADDirectoryContext' ) diff --git a/Tests/Integration/MSFT_xADDomainTrust.Integration.Tests.ps1 b/Tests/Integration/MSFT_xADDomainTrust.Integration.Tests.ps1 new file mode 100644 index 000000000..9652e98ce --- /dev/null +++ b/Tests/Integration/MSFT_xADDomainTrust.Integration.Tests.ps1 @@ -0,0 +1,349 @@ +if ($env:APPVEYOR -eq $true) +{ + Write-Warning -Message 'Integration test is not supported in AppVeyor.' + return +} + +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceFriendlyName = 'xADDomainTrust' +$script:dscResourceName = "MSFT_$($script:dscResourceFriendlyName)" + +#region HEADER +# Integration Test Template Version: 1.3.3 +[System.String] $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 ` + -TestType Integration +#endregion + +try +{ + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_CreateDomainTrust_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.SourceDomainName | Should -Be $configurationData.AllNodes.SourceDomain + $resourceCurrentState.TargetDomainName | Should -Be $configurationData.AllNodes.TargetDomain + $resourceCurrentState.TargetDomainAdministratorCredential.UserName | Should -Be $configurationData.AllNodes.TargetUserName + $resourceCurrentState.TrustType | Should -Be 'External' + $resourceCurrentState.TrustDirection | Should -Be 'Outbound' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -BeTrue + } + } + + $configurationName = "$($script:dscResourceName)_ChangeDomainTrustDirection_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.SourceDomainName | Should -Be $configurationData.AllNodes.SourceDomain + $resourceCurrentState.TargetDomainName | Should -Be $configurationData.AllNodes.TargetDomain + $resourceCurrentState.TargetDomainAdministratorCredential.UserName | Should -Be $configurationData.AllNodes.TargetUserName + $resourceCurrentState.TrustType | Should -Be 'External' + $resourceCurrentState.TrustDirection | Should -Be 'Inbound' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -BeTrue + } + } + + $configurationName = "$($script:dscResourceName)_RemoveDomainTrust_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.SourceDomainName | Should -Be $configurationData.AllNodes.SourceDomain + $resourceCurrentState.TargetDomainName | Should -Be $configurationData.AllNodes.TargetDomain + $resourceCurrentState.TargetDomainAdministratorCredential.UserName | Should -Be $configurationData.AllNodes.TargetUserName + $resourceCurrentState.TrustType | Should -BeNullOrEmpty + $resourceCurrentState.TrustDirection | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -BeTrue + } + } + + $configurationName = "$($script:dscResourceName)_CreateForestTrust_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.SourceDomainName | Should -Be $configurationData.AllNodes.SourceForest + $resourceCurrentState.TargetDomainName | Should -Be $configurationData.AllNodes.TargetForest + $resourceCurrentState.TargetDomainAdministratorCredential.UserName | Should -Be $configurationData.AllNodes.TargetUserName + $resourceCurrentState.TrustType | Should -Be 'Forest' + $resourceCurrentState.TrustDirection | Should -Be 'Outbound' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -BeTrue + } + } + + $configurationName = "$($script:dscResourceName)_ChangeForestTrustDirection_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Present' + $resourceCurrentState.SourceDomainName | Should -Be $configurationData.AllNodes.SourceForest + $resourceCurrentState.TargetDomainName | Should -Be $configurationData.AllNodes.TargetForest + $resourceCurrentState.TargetDomainAdministratorCredential.UserName | Should -Be $configurationData.AllNodes.TargetUserName + $resourceCurrentState.TrustType | Should -Be 'Forest' + $resourceCurrentState.TrustDirection | Should -Be 'Inbound' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -BeTrue + } + } + + $configurationName = "$($script:dscResourceName)_RemoveForestTrust_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + $resourceCurrentState.Ensure | Should -Be 'Absent' + $resourceCurrentState.SourceDomainName | Should -Be $configurationData.AllNodes.SourceForest + $resourceCurrentState.TargetDomainName | Should -Be $configurationData.AllNodes.TargetForest + $resourceCurrentState.TargetDomainAdministratorCredential.UserName | Should -Be $configurationData.AllNodes.TargetUserName + $resourceCurrentState.TrustType | Should -BeNullOrEmpty + $resourceCurrentState.TrustDirection | Should -BeNullOrEmpty + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -BeTrue + } + } + } +} +finally +{ + #region FOOTER + Restore-TestEnvironment -TestEnvironment $TestEnvironment + #endregion +} diff --git a/Tests/Integration/MSFT_xADDomainTrust.config.ps1 b/Tests/Integration/MSFT_xADDomainTrust.config.ps1 new file mode 100644 index 000000000..305eb166e --- /dev/null +++ b/Tests/Integration/MSFT_xADDomainTrust.config.ps1 @@ -0,0 +1,194 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +<# + .NOTES + To run this integration test there are prerequisites that need to + be setup. + + 1. One Domain Controller with forest contoso.com. + 2. One Domain Controller with forest lab.local. + 3. DNS working between the forests (conditional forwarder). + 4. Credentials with permission in the target domain (lab.local). + 5. If no certificate path is set to the environment variable + `$env:DscPublicCertificatePath` then `PSDscAllowPlainTextPassword = $true` + must be added to the ConfigurationData-block. +#> + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') + +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, for real testing + scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + + SourceDomain = 'contoso.com' + TargetDomain = 'lab.local' + + SourceForest = 'contoso.com' + TargetForest = 'lab.local' + + TargetUserName = 'LAB\Administrator' + TargetPassword = 'P@ssw0rd1' + } + ) + } +} + +<# + .SYNOPSIS + Creates a domain trust. +#> +Configuration MSFT_xADDomainTrust_CreateDomainTrust_Config +{ + Import-DscResource -ModuleName 'xActiveDirectory' + + node $AllNodes.NodeName + { + xADDomainTrust 'Integration_Test' + { + SourceDomainName = $Node.SourceDomain + TargetDomainName = $Node.TargetDomain + TrustType = 'External' + TrustDirection = 'Outbound' + TargetDomainAdministratorCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.TargetUserName, (ConvertTo-SecureString -String $Node.TargetPassword -AsPlainText -Force)) + + } + } +} + +<# + .SYNOPSIS + Changes trust direction on an existing domain trust. +#> +Configuration MSFT_xADDomainTrust_ChangeDomainTrustDirection_Config +{ + Import-DscResource -ModuleName 'xActiveDirectory' + + node $AllNodes.NodeName + { + xADDomainTrust 'Integration_Test' + { + SourceDomainName = $Node.SourceDomain + TargetDomainName = $Node.TargetDomain + TrustType = 'External' + TrustDirection = 'Inbound' + TargetDomainAdministratorCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.TargetUserName, (ConvertTo-SecureString -String $Node.TargetPassword -AsPlainText -Force)) + + } + } +} + +<# + .SYNOPSIS + Removes the domain trust. +#> +Configuration MSFT_xADDomainTrust_RemoveDomainTrust_Config +{ + Import-DscResource -ModuleName 'xActiveDirectory' + + node $AllNodes.NodeName + { + xADDomainTrust 'Integration_Test' + { + Ensure = 'Absent' + SourceDomainName = $Node.SourceDomain + TargetDomainName = $Node.TargetDomain + TrustType = 'External' + TrustDirection = 'Bidirectional' + TargetDomainAdministratorCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.TargetUserName, (ConvertTo-SecureString -String $Node.TargetPassword -AsPlainText -Force)) + + } + } +} + +<# + .SYNOPSIS + Creates a forest trust. +#> +Configuration MSFT_xADDomainTrust_CreateForestTrust_Config +{ + Import-DscResource -ModuleName 'xActiveDirectory' + + node $AllNodes.NodeName + { + xADDomainTrust 'Integration_Test' + { + SourceDomainName = $Node.SourceForest + TargetDomainName = $Node.TargetForest + TrustType = 'Forest' + TrustDirection = 'Outbound' + TargetDomainAdministratorCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.TargetUserName, (ConvertTo-SecureString -String $Node.TargetPassword -AsPlainText -Force)) + + } + } +} + +<# + .SYNOPSIS + Changes trust direction on an existing forest trust. +#> +Configuration MSFT_xADDomainTrust_ChangeForestTrustDirection_Config +{ + Import-DscResource -ModuleName 'xActiveDirectory' + + node $AllNodes.NodeName + { + xADDomainTrust 'Integration_Test' + { + SourceDomainName = $Node.SourceForest + TargetDomainName = $Node.TargetForest + TrustType = 'Forest' + TrustDirection = 'Inbound' + TargetDomainAdministratorCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.TargetUserName, (ConvertTo-SecureString -String $Node.TargetPassword -AsPlainText -Force)) + + } + } +} + +<# + .SYNOPSIS + Removes the domain trust. +#> +Configuration MSFT_xADDomainTrust_RemoveForestTrust_Config +{ + Import-DscResource -ModuleName 'xActiveDirectory' + + node $AllNodes.NodeName + { + xADDomainTrust 'Integration_Test' + { + Ensure = 'Absent' + SourceDomainName = $Node.SourceForest + TargetDomainName = $Node.TargetForest + TrustType = 'Forest' + TrustDirection = 'Bidirectional' + TargetDomainAdministratorCredential = New-Object ` + -TypeName System.Management.Automation.PSCredential ` + -ArgumentList @($Node.TargetUserName, (ConvertTo-SecureString -String $Node.TargetPassword -AsPlainText -Force)) + + } + } +} diff --git a/Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 b/Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 new file mode 100644 index 000000000..198089363 --- /dev/null +++ b/Tests/Unit/MSFT_xADDomainTrust.Tests.ps1 @@ -0,0 +1,989 @@ +[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 { + $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-TrustSourceAndTargetObject -MockWith { + $mockTrustSource = New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'GetTrustRelationship' -Value { + $script:getTrustRelationshipMethodCallCount += 1 + + return @{ + TrustType = 'Domain' + TrustDirection = 'Outbound' + } + } -PassThru -Force + + $mockTrustTarget = New-Object -TypeName Object + + return $mockTrustSource, $mockTrustTarget + } + } + + BeforeEach { + $script:getTrustRelationshipMethodCallCount = 0 + + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + $mockGetTargetResourceParameters['TrustType'] = 'External' + } + + AfterEach { + $script:getTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -MockWith { + $mockTrustSource = New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'GetTrustRelationship' -Value { + $script:getTrustRelationshipMethodCallCount += 1 + + return @{ + TrustType = 'Forest' + TrustDirection = 'Outbound' + } + } -PassThru -Force + + $mockTrustTarget = New-Object -TypeName Object + + return $mockTrustSource, $mockTrustTarget + } + } + + BeforeEach { + $script:getTrustRelationshipMethodCallCount = 0 + + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + $mockGetTargetResourceParameters['TrustType'] = 'Forest' + } + + AfterEach { + $script:getTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -MockWith { + $mockTrustSource = New-Object -TypeName Object | + Add-Member -MemberType ScriptMethod -Name 'GetTrustRelationship' -Value { + $script:getTrustRelationshipMethodCallCount += 1 + + throw + } -PassThru -Force + + $mockTrustTarget = New-Object -TypeName Object + + return $mockTrustSource, $mockTrustTarget + } + } + + BeforeEach { + $script:GetTrustRelationshipMethodCallCount = 0 + + $mockGetTargetResourceParameters = $mockDefaultParameters.Clone() + $mockGetTargetResourceParameters['TrustType'] = 'Forest' + } + + AfterEach { + $script:getTrustRelationshipMethodCallCount | Should -Be 1 + + Assert-MockCalled -CommandName Get-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -MockWith { + $mockTrustSource = 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 + + $mockTrustTarget = New-Object -TypeName Object + + return $mockTrustSource, $mockTrustTarget + } + + $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-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -Exactly -Times 1 -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-TrustSourceAndTargetObject -Exactly -Times 1 -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 + } + } + + Describe 'MSFT_xADDomainTrust\Get-TrustSourceAndTargetObject' -Tag 'Helper' { + BeforeAll { + Mock -CommandName Get-ADDirectoryContext -MockWith { + # This should work on any client, domain joined or not. + return [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new('Domain') + } + + Mock -CommandName Get-ActiveDirectoryDomain + Mock -CommandName Get-ActiveDirectoryForest + + $testCases = @( + @{ + TrustType = 'External' + }, + @{ + TrustType = 'Forest' + } + ) + } + + It 'Should not throw and call the correct mocks when called with the trust type value ''''' -TestCases $testCases { + param + ( + [Parameter()] + $TrustType + ) + + $testParameters = @{ + SourceDomainName = $mockSourceDomainName + TargetDomainName = $mockTargetDomainName + TargetDomainAdministratorCredential = $mockCredential + TrustType = $TrustType + Verbose = $true + } + + { Get-TrustSourceAndTargetObject @testParameters } | Should -Not -Throw + + if ($TrustType -eq 'External') + { + Assert-MockCalled -CommandName Get-ActiveDirectoryDomain -Exactly -Times 2 -Scope It + } + else + { + Assert-MockCalled -CommandName Get-ActiveDirectoryForest -Exactly -Times 2 -Scope It + } + } + } + } +} +finally +{ + Invoke-TestCleanup +} diff --git a/Tests/Unit/xActiveDirectory.Common.Tests.ps1 b/Tests/Unit/xActiveDirectory.Common.Tests.ps1 index 1d0b7b3b3..6d82bb66e 100644 --- a/Tests/Unit/xActiveDirectory.Common.Tests.ps1 +++ b/Tests/Unit/xActiveDirectory.Common.Tests.ps1 @@ -2133,4 +2133,135 @@ InModuleScope 'xActiveDirectory.Common' { } } } + + Describe 'xActiveDirectory.Common\New-CimCredentialInstance' { + Context 'When creating a new MSFT_Credential CIM instance credential object' { + BeforeAll { + $mockAdministratorUser = 'admin@contoso.com' + $mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12' + $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( + $mockAdministratorUser, + ($mockAdministratorPassword | ConvertTo-SecureString -AsPlainText -Force) + ) + } + + It 'Should return the correct values' { + $newCimCredentialInstanceResult = New-CimCredentialInstance -Credential $mockAdministratorCredential + $newCimCredentialInstanceResult | Should -BeOfType 'Microsoft.Management.Infrastructure.CimInstance' + $newCimCredentialInstanceResult.CimClass.CimClassName | Should -Be 'MSFT_Credential' + $newCimCredentialInstanceResult.UserName | Should -Be $mockAdministratorUser + $newCimCredentialInstanceResult.Password | Should -BeNullOrEmpty + } + } + } + + Describe 'xActiveDirectory.Common\Add-TypeAssembly' { + Context 'When assembly fails to load' { + BeforeAll { + Mock -CommandName Add-Type -MockWith { + throw + } + + $mockAssembly = 'MyAssembly' + } + + It 'Should throw the correct error' { + { Add-TypeAssembly -AssemblyName $mockAssembly } | Should -Throw ($script:localizedData.CouldNotLoadAssembly -f $mockAssembly) + } + } + + Context 'When loading an assembly into the session' { + BeforeAll { + Mock -CommandName Add-Type + + $mockAssembly = 'MyAssembly' + } + + It 'Should not throw and call the correct mocks' { + { Add-TypeAssembly -AssemblyName $mockAssembly } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-Type -ParameterFilter { + $AssemblyName -eq $mockAssembly + } -Exactly -Times 1 -Scope It + } + + Context 'When the type is already loaded into the session' { + It 'Should not throw and not call any mocks' { + { Add-TypeAssembly -AssemblyName $mockAssembly -TypeName 'System.String' } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-Type -Exactly -Times 0 -Scope It + } + } + + Context 'When the type is missing from the session' { + It 'Should not throw and call the correct mocks' { + { Add-TypeAssembly -AssemblyName $mockAssembly -TypeName 'My.Type' } | Should -Not -Throw + + Assert-MockCalled -CommandName Add-Type -ParameterFilter { + $AssemblyName -eq $mockAssembly + } -Exactly -Times 1 -Scope It + } + } + } + } + + Describe 'xActiveDirectory.Common\New-ADDirectoryContext' { + Context 'When creating a new Active Directory context' { + BeforeAll { + # This credential object must be created before we mock New-Object. + $mockAdministratorUser = 'admin@contoso.com' + $mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12' + $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @( + $mockAdministratorUser, + ($mockAdministratorPassword | ConvertTo-SecureString -AsPlainText -Force) + ) + + Mock -CommandName Add-TypeAssembly -Verifiable + Mock -CommandName New-Object + } + + Context 'When the calling with only parameter DirectoryContextType' { + It 'Should not throw and call the correct mocks' { + { Get-ADDirectoryContext -DirectoryContextType 'Domain' } | Should -Not -Throw + + Assert-MockCalled -CommandName New-Object -ParameterFilter { + $ArgumentList.Count -eq 1 ` + -and $ArgumentList[0] -eq 'Domain' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When the calling with parameters DirectoryContextType and Name' { + It 'Should not throw and call the correct mocks' { + { + Get-ADDirectoryContext -DirectoryContextType 'Domain' -Name 'my.domain' + } | Should -Not -Throw + + Assert-MockCalled -CommandName New-Object -ParameterFilter { + $ArgumentList.Count -eq 2 ` + -and $ArgumentList[0] -eq 'Domain' ` + -and $ArgumentList[1] -eq 'my.domain' + } -Exactly -Times 1 -Scope It + } + } + + Context 'When the calling with parameters DirectoryContextType, Name and Credential' { + It 'Should not throw and call the correct mocks' { + { + Get-ADDirectoryContext -DirectoryContextType 'Domain' -Name 'my.domain' -Credential $mockAdministratorCredential + } | Should -Not -Throw + + Assert-MockCalled -CommandName New-Object -ParameterFilter { + $ArgumentList.Count -eq 4 ` + -and $ArgumentList[0] -eq 'Domain' ` + -and $ArgumentList[1] -eq 'my.domain' ` + -and $ArgumentList[2] -eq $mockAdministratorUser ` + -and $ArgumentList[3] -eq $mockAdministratorPassword + } -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMock + } + } }