From 44ca75e062beff431cf41e1efb89d71bc4a5d044 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 7 Jun 2019 09:35:22 +0200 Subject: [PATCH] xActiveDirectory: Move MSFT_xADCommon helper functions (#320) - Changes to xActiveDirectory - Move helper functions from MSFT_xADCommon to the module xActiveDirectory.Common ([issue #288](https://github.com/PowerShell/xActiveDirectory/issues/288)) - Removed helper function `Test-ADDomain` since it was not used. The helper function had design flaws too. - Now the helper function `Test-Members` outputs all the members that are not in desired state when verbose output is enabled. - Update all unit tests to latest unit test template. --- CHANGELOG.md | 7 + .../MSFT_xADCommon/MSFT_xADCommon.psm1 | 1314 ------------- .../en-US/MSFT_xADCommon.strings.psd1 | 36 - .../MSFT_xADComputer/MSFT_xADComputer.psm1 | 3 - .../MSFT_xADDomain/MSFT_xADDomain.psm1 | 3 - .../MSFT_xADDomainController.psm1 | 6 - .../MSFT_xADDomainDefaultPasswordPolicy.psm1 | 3 - .../MSFT_xADForestProperties.psm1 | 3 - DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 | 3 - .../MSFT_xADKDSKey/MSFT_xADKDSKey.psm1 | 3 - .../MSFT_xADManagedServiceAccount.psm1 | 3 - .../MSFT_xADObjectPermissionEntry.psm1 | 3 - .../MSFT_xADOrganizationalUnit.psm1 | 3 - DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 | 3 - .../xActiveDirectory.Common.strings.psd1 | 46 +- .../xActiveDirectory.Common.psm1 | 1339 ++++++++++++++ Tests/Unit/MSFT_xADCommon.Tests.ps1 | 1524 --------------- Tests/Unit/MSFT_xADDomain.Tests.ps1 | 82 +- ...T_xADDomainDefaultPasswordPolicy.Tests.ps1 | 60 +- Tests/Unit/MSFT_xADForestProperties.Tests.ps1 | 32 +- Tests/Unit/MSFT_xADGroup.Tests.ps1 | 74 +- Tests/Unit/MSFT_xADKDSKey.Tests.ps1 | 13 +- .../MSFT_xADManagedServiceAccount.Tests.ps1 | 14 +- .../MSFT_xADObjectPermissionEntry.Tests.ps1 | 49 +- .../Unit/MSFT_xADOrganizationalUnit.Tests.ps1 | 58 +- Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 | 32 +- Tests/Unit/MSFT_xADReplicationSite.Tests.ps1 | 55 +- .../MSFT_xADReplicationSiteLink.tests.ps1 | 25 +- .../Unit/MSFT_xADReplicationSubnet.Tests.ps1 | 51 +- .../MSFT_xADServicePrincipalName.Tests.ps1 | 53 +- Tests/Unit/MSFT_xADUser.Tests.ps1 | 51 +- Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1 | 56 +- Tests/Unit/xActiveDirectory.Common.Tests.ps1 | 1639 ++++++++++++++++- 33 files changed, 3397 insertions(+), 3249 deletions(-) delete mode 100644 DSCResources/MSFT_xADCommon/MSFT_xADCommon.psm1 delete mode 100644 DSCResources/MSFT_xADCommon/en-US/MSFT_xADCommon.strings.psd1 delete mode 100644 Tests/Unit/MSFT_xADCommon.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd5499b8..c16e5c0d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,13 @@ - Common Tests - Validate Localization ([Issue #281](https://github.com/PowerShell/xActiveDirectory/issues/281)) - Common Tests - Validate Example Files ([Issue #279](https://github.com/PowerShell/xActiveDirectory/issues/279)) - Move resource descriptions to Wiki using auto-documentation ([Issue #289](https://github.com/PowerShell/xActiveDirectory/issues/289)) + - Move helper functions from MSFT_xADCommon to the module + xActiveDirectory.Common ([issue #288](https://github.com/PowerShell/xActiveDirectory/issues/288)) + - Removed helper function `Test-ADDomain` since it was not used. The + helper function had design flaws too. + - Now the helper function `Test-Members` outputs all the members that + are not in desired state when verbose output is enabled. + - Update all unit tests to latest unit test template. - Changes to xADComputer - Refactored the resource and the unit tests. - BREAKING CHANGE: The `Enabled` property is **DEPRECATED** and is no diff --git a/DSCResources/MSFT_xADCommon/MSFT_xADCommon.psm1 b/DSCResources/MSFT_xADCommon/MSFT_xADCommon.psm1 deleted file mode 100644 index fe891b0cc..000000000 --- a/DSCResources/MSFT_xADCommon/MSFT_xADCommon.psm1 +++ /dev/null @@ -1,1314 +0,0 @@ -$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent -$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules' - -$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' -Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') - -$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADCommon' - -# Internal function to assert if the role specific module is installed or not -function Assert-Module -{ - [CmdletBinding()] - param - ( - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ModuleName = 'ActiveDirectory', - - [Parameter()] - [switch] - $ImportModule - ) - - if (-not (Get-Module -Name $ModuleName -ListAvailable)) - { - $errorId = '{0}_ModuleNotFound' -f $ModuleName; - $errorMessage = $script:localizedData.RoleNotFoundError -f $moduleName; - ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage; - } - - if ($ImportModule) - { - Import-Module -Name $ModuleName - } -} #end function Assert-Module - -# Internal function to test whether computer is a member of a domain -function Test-DomainMember -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - ) - - $isDomainMember = [System.Boolean] (Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false).PartOfDomain; - return $isDomainMember; -} - - -# Internal function to get the domain name of the computer -function Get-DomainName -{ - [CmdletBinding()] - [OutputType([System.String])] - param - ( - ) - - $domainName = [System.String] (Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false).Domain; - return $domainName; -} # function Get-DomainName - -# Internal function to build domain FQDN -function Resolve-DomainFQDN -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [OutputType([System.String])] - [System.String] $DomainName, - - [Parameter()] [AllowNull()] - [System.String] $ParentDomainName - ) - - $domainFQDN = $DomainName - if ($ParentDomainName) - { - $domainFQDN = '{0}.{1}' -f $DomainName, $ParentDomainName; - } - return $domainFQDN -} - -## Internal function to test/ domain availability -function Test-ADDomain -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] $DomainName, - - [Parameter()] - [System.Management.Automation.PSCredential] - [System.Management.Automation.CredentialAttribute()] - $Credential - ) - - Write-Verbose -Message ($script:localizedData.CheckingDomain -f $DomainName); - $ldapDomain = 'LDAP://{0}' -f $DomainName; - if ($PSBoundParameters.ContainsKey('Credential')) - { - $domain = New-Object DirectoryServices.DirectoryEntry($ldapDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password); - } - else - { - $domain = New-Object DirectoryServices.DirectoryEntry($ldapDomain); - } - return ($null -ne $domain); -} - -# Internal function to get an Active Directory object's parent Distinguished Name -function Get-ADObjectParentDN -{ - <# - Copyright (c) 2016 The University Of Vermont - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are permitted provided that - the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the - following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote - products derived from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - http://www.uvm.edu/~gcd/code-license/ - #> - [CmdletBinding()] - [OutputType([System.String])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $DN - ) - - # https://www.uvm.edu/~gcd/2012/07/listing-parent-of-ad-object-in-powershell/ - $distinguishedNameParts = $DN -split '(? -function ConvertFrom-TimeSpan -{ - [CmdletBinding()] - [OutputType([System.Int32])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.TimeSpan] - $TimeSpan, - - [Parameter(Mandatory = $true)] - [ValidateSet('Seconds', 'Minutes', 'Hours', 'Days')] - [System.String] - $TimeSpanType - ) - - switch ($TimeSpanType) - { - 'Seconds' - { - return $TimeSpan.TotalSeconds -as [System.UInt32] - } - 'Minutes' - { - return $TimeSpan.TotalMinutes -as [System.UInt32] - } - 'Hours' - { - return $TimeSpan.TotalHours -as [System.UInt32] - } - 'Days' - { - return $TimeSpan.TotalDays -as [System.UInt32] - } - } -} #end function ConvertFrom-TimeSpan - -<# - .SYNOPSIS - Returns common AD cmdlet connection parameter for splatting - .PARAMETER CommonName - When specified, a CommonName overrides theUsed by the xADUser cmdletReturns the Identity as the Name key. For example, the Get-ADUser, Set-ADUser and - Remove-ADUser cmdlets take an Identity parameter, but the New-ADUser cmdlet uses the - Name parameter. - .PARAMETER UseNameParameter - Returns the Identity as the Name key. For example, the Get-ADUser, Set-ADUser and - Remove-ADUser cmdlets take an Identity parameter, but the New-ADUser cmdlet uses the - Name parameter. - .EXAMPLE - $getADUserParams = Get-CommonADParameters @PSBoundParameters - - Returns connection parameters suitable for Get-ADUser using the splatted cmdlet - parameters. - .EXAMPLE - $newADUserParams = Get-CommonADParameters @PSBoundParameters -UseNameParameter - - Returns connection parameters suitable for New-ADUser using the splatted cmdlet - parameters. -#> -function Get-ADCommonParameters -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [Alias('UserName', 'GroupName', 'ComputerName', 'ServiceAccountName')] - [System.String] - $Identity, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $CommonName, - - [Parameter()] - [ValidateNotNull()] - [Alias('DomainAdministratorCredential')] - [System.Management.Automation.PSCredential] - [System.Management.Automation.CredentialAttribute()] - $Credential, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [Alias('DomainController')] - [System.String] - $Server, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $UseNameParameter, - - [Parameter()] - [System.Management.Automation.SwitchParameter] - $PreferCommonName, - - ## Catch all to enable splatted $PSBoundParameters - [Parameter(ValueFromRemainingArguments)] - $RemainingArguments - ) - - if ($UseNameParameter) - { - if ($PreferCommonName -and ($PSBoundParameters.ContainsKey('CommonName'))) - { - $adConnectionParameters = @{ Name = $CommonName; } - } - else - { - $adConnectionParameters = @{ Name = $Identity; } - } - } - else - { - if ($PreferCommonName -and ($PSBoundParameters.ContainsKey('CommonName'))) - { - $adConnectionParameters = @{ Identity = $CommonName; } - } - else - { - $adConnectionParameters = @{ Identity = $Identity; } - } - } - - if ($Credential) - { - $adConnectionParameters['Credential'] = $Credential; - } - - if ($Server) - { - $adConnectionParameters['Server'] = $Server; - } - - return $adConnectionParameters; -} #end function Get-ADCommonParameters - -function ThrowInvalidOperationError -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorMessage - ) - - $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $ErrorMessage; - $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation; - $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $errorCategory, $null; - throw $errorRecord; -} - -function ThrowInvalidArgumentError -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorMessage - ) - - $exception = New-Object -TypeName System.ArgumentException -ArgumentList $ErrorMessage; - $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument; - $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $errorCategory, $null; - throw $errorRecord; - -} #end function ThrowInvalidArgumentError - -## Internal function to test site availability -function Test-ADReplicationSite -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] $SiteName, - - [Parameter(Mandatory = $true)] - [System.String] $DomainName, - - [Parameter()] - [System.Management.Automation.PSCredential] - $Credential - ) - - Write-Verbose -Message ($script:localizedData.CheckingSite -f $SiteName); - - $existingDC = "$((Get-ADDomainController -Discover -DomainName $DomainName -ForceDiscover).HostName)"; - - try - { - $site = Get-ADReplicationSite -Identity $SiteName -Server $existingDC -Credential $Credential; - } - catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] - { - return $false; - } - - return ($null -ne $site); -} - -function ConvertTo-DeploymentForestMode -{ - [CmdletBinding()] - [OutputType([Microsoft.DirectoryServices.Deployment.Types.ForestMode])] - param - ( - [Parameter( - Mandatory = $true, - ParameterSetName = 'ById')] - [UInt16] - $ModeId, - - [Parameter( - Mandatory = $true, - ParameterSetName = 'ByName')] - [AllowNull()] - [System.Nullable``1[Microsoft.ActiveDirectory.Management.ADForestMode]] - $Mode, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ModuleName = 'xActiveDirectory' - ) - - $convertedMode = $null - - if ($PSCmdlet.ParameterSetName -eq 'ByName' -and $Mode) - { - $convertedMode = $Mode -as [Microsoft.DirectoryServices.Deployment.Types.ForestMode] - } - - if ($PSCmdlet.ParameterSetName -eq 'ById') - { - $convertedMode = $ModeId -as [Microsoft.DirectoryServices.Deployment.Types.ForestMode] - } - - if ([enum]::GetValues([Microsoft.DirectoryServices.Deployment.Types.ForestMode]) -notcontains $convertedMode) - { - return $null - } - - return $convertedMode -} - -function ConvertTo-DeploymentDomainMode -{ - [CmdletBinding()] - [OutputType([Microsoft.DirectoryServices.Deployment.Types.DomainMode])] - param - ( - [Parameter( - Mandatory = $true, - ParameterSetName = 'ById')] - [UInt16] - $ModeId, - - [Parameter( - Mandatory = $true, - ParameterSetName = 'ByName')] - [AllowNull()] - [System.Nullable``1[Microsoft.ActiveDirectory.Management.ADDomainMode]] - $Mode, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ModuleName = 'xActiveDirectory' - ) - - $convertedMode = $null - - if ($PSCmdlet.ParameterSetName -eq 'ByName' -and $Mode) - { - $convertedMode = $Mode -as [Microsoft.DirectoryServices.Deployment.Types.DomainMode] - } - - if ($PSCmdlet.ParameterSetName -eq 'ById') - { - $convertedMode = $ModeId -as [Microsoft.DirectoryServices.Deployment.Types.DomainMode] - } - - if ([enum]::GetValues([Microsoft.DirectoryServices.Deployment.Types.DomainMode]) -notcontains $convertedMode) - { - return $null - } - - return $convertedMode -} - -function Restore-ADCommonObject -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [Alias('UserName', 'GroupName', 'ComputerName', 'ServiceAccountName')] - [System.String] - $Identity, - - [Parameter(Mandatory = $true)] - [ValidateSet('Computer', 'OrganizationalUnit', 'User', 'Group')] - [System.String] - $ObjectClass, - - [Parameter()] - [ValidateNotNull()] - [Alias('DomainAdministratorCredential')] - [System.Management.Automation.PSCredential] - [System.Management.Automation.CredentialAttribute()] - $Credential, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [Alias('DomainController')] - [System.String] - $Server - ) - - $restoreFilter = 'msDS-LastKnownRDN -eq "{0}" -and objectClass -eq "{1}" -and isDeleted -eq $true' -f $Identity, $ObjectClass - Write-Verbose -Message ($script:localizedData.FindInRecycleBin -f $restoreFilter) -Verbose - - <# - Using IsDeleted and IncludeDeletedObjects will mean that the cmdlet does not throw - any more, and simply returns $null instead - #> - $commonParams = Get-ADCommonParameters @PSBoundParameters - $getAdObjectParams = $commonParams.Clone() - $getAdObjectParams.Remove('Identity') - $getAdObjectParams['Filter'] = $restoreFilter - $getAdObjectParams['IncludeDeletedObjects'] = $true - $getAdObjectParams['Properties'] = @('whenChanged') - - # If more than one object is returned, we pick the one that was changed last. - $restorableObject = Get-ADObject @getAdObjectParams | - Sort-Object -Descending -Property 'whenChanged' | - Select-Object -First 1 - - $restoredObject = $null - - if ($restorableObject) - { - Write-Verbose -Message ($script:localizedData.FoundRestoreTargetInRecycleBin -f $Identity, $ObjectClass, $restorableObject.DistinguishedName) -Verbose - - try - { - $restoreParams = $commonParams.Clone() - $restoreParams['PassThru'] = $true - $restoreParams['ErrorAction'] = 'Stop' - $restoreParams['Identity'] = $restorableObject.DistinguishedName - $restoredObject = Restore-ADObject @restoreParams - Write-Verbose -Message ($script:localizedData.RecycleBinRestoreSuccessful -f $Identity, $ObjectClass) -Verbose - } - catch [Microsoft.ActiveDirectory.Management.ADException] - { - # After Get-TargetResource is through, only one error can occur here: Object parent does not exist - ThrowInvalidOperationError -ErrorId "$($Identity)_RecycleBinRestoreFailed" -ErrorMessage ($script:localizedData.RecycleBinRestoreFailed -f $Identity, $ObjectClass, $_.Exception.Message) - } - } - - return $restoredObject -} - -<# - .SYNOPSIS - Author: Robert D. Biddle (https://github.com/RobBiddle) - Created: December.20.2017 - - .DESCRIPTION - Takes an Active Directory DistinguishedName as input, returns the domain FQDN - - .EXAMPLE - Get-ADDomainNameFromDistinguishedName -DistinguishedName 'CN=ExampleObject,OU=ExampleOU,DC=example,DC=com' -#> -function Get-ADDomainNameFromDistinguishedName -{ - [CmdletBinding()] - param - ( - [Parameter()] - [System.String] - $DistinguishedName - ) - - if ($DistinguishedName -notlike '*DC=*') - { - return - } - - $splitDistinguishedName = ($DistinguishedName -split 'DC=') - $splitDistinguishedNameParts = $splitDistinguishedName[1..$splitDistinguishedName.Length] - $domainFqdn = "" - foreach ($part in $splitDistinguishedNameParts) - { - $domainFqdn += "DC=$part" - } - - $domainName = $domainFqdn -replace 'DC=', '' -replace ',', '.' - return $domainName - -} #end function Get-ADDomainNameFromDistinguishedName - -<# - .SYNOPSIS - Add group member from current or different domain - - .NOTES - Author original code: Robert D. Biddle (https://github.com/RobBiddle) - Author refactored code: Jan-Hendrik Peters (https://github.com/nyanhp) -#> -function Add-ADCommonGroupMember -{ - [CmdletBinding()] - param - ( - [Parameter()] - [string[]] - $Members, - - [Parameter()] - [hashtable] - $Parameters, - - [Parameter()] - [switch] - $MembersInMultipleDomains - ) - - Assert-Module -ModuleName ActiveDirectory - - if ($MembersInMultipleDomains.IsPresent) - { - foreach ($member in $Members) - { - $memberDomain = Get-ADDomainNameFromDistinguishedName -DistinguishedName $member - - if (-not $memberDomain) - { - ThrowInvalidArgumentError -ErrorId "$($member)_EmptyDomainError" -ErrorMessage ($script:localizedData.EmptyDomainError -f $member, $Parameters.GroupName) - } - - Write-Verbose -Message ($script:localizedData.AddingGroupMember -f $member, $memberDomain, $Parameters.GroupName) - $memberObjectClass = (Get-ADObject -Identity $member -Server $memberDomain -Properties ObjectClass).ObjectClass - if ($memberObjectClass -eq 'computer') - { - $memberObject = Get-ADComputer -Identity $member -Server $memberDomain - } - elseif ($memberObjectClass -eq 'group') - { - $memberObject = Get-ADGroup -Identity $member -Server $memberDomain - } - elseif ($memberObjectClass -eq 'user') - { - $memberObject = Get-ADUser -Identity $member -Server $memberDomain - } - - Add-ADGroupMember @Parameters -Members $memberObject - } - } - else - { - Add-ADGroupMember @Parameters -Members $Members - } -} - -<# - .SYNOPSIS - Returns the domain controller object if the node is a domain controller, - otherwise it return $null. - - .PARAMETER DomainName - The name of the domain that should contain the domain controller. - - .PARAMETER ComputerName - The name of the node to return the domain controller object for. - Defaults to $env:COMPUTERNAME. - - .OUTPUTS - If the domain controller is not found, an empty object ($null) is returned. - - .NOTES - Throws an exception of Microsoft.ActiveDirectory.Management.ADServerDownException - if the domain cannot be contacted. -#> -function Get-DomainControllerObject -{ - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $DomainName, - - [Parameter()] - [System.String] - $ComputerName = $env:COMPUTERNAME, - - [Parameter()] - [System.Management.Automation.PSCredential] - $Credential - ) - - <# - It is not possible to use `-ErrorAction 'SilentlyContinue` on the - cmdlet Get-ADDomainController, it will throw an error regardless. - #> - try - { - $getADDomainControllerParameters = @{ - Filter = 'Name -eq "{0}"' -f $ComputerName - Server = $DomainName - } - - if ($PSBoundParameters.ContainsKey('Credential')) - { - $getADDomainControllerParameters['Credential'] = $Credential - } - - $domainControllerObject = Get-ADDomainController @getADDomainControllerParameters - - if (-not $domainControllerObject -and (Test-IsDomainController) -eq $true) - { - $errorMessage = $script:localizedData.WasExpectingDomainController - New-InvalidResultException -Message $errorMessage - } - } - catch - { - $errorMessage = $script:localizedData.FailedEvaluatingDomainController - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - - return $domainControllerObject -} - -<# - .SYNOPSIS - Returns $true if the node is a domain controller, otherwise it returns - $false -#> -function Test-IsDomainController -{ - [CmdletBinding()] - param - ( - ) - - $operatingSystemInformation = Get-CimInstance -ClassName 'Win32_OperatingSystem' - - return $operatingSystemInformation.ProductType -eq 2 -} - -<# - .SYNOPSIS - Converts a hashtable containing the parameter to property mappings to - an array of properties that can be used to call cmdlets that supports the - parameter Properties. - - .PARAMETER PropertyMap - The property map, as an array of hashtables, to convert to a properties array. - - .EXAMPLE - $computerObjectPropertyMap = @( - @{ - ParameterName = 'ComputerName' - PropertyName = 'cn' - }, - @{ - ParameterName = 'Location' - } - ) - - $computerObjectProperties = Convert-PropertyMapToObjectProperties $computerObjectPropertyMap - $getADComputerResult = Get-ADComputer -Identity 'APP01' -Properties $computerObjectProperties -#> -function Convert-PropertyMapToObjectProperties -{ - [CmdletBinding()] - [OutputType([System.Array])] - param - ( - [Parameter(Mandatory = $true)] - [System.Array] - $PropertyMap - ) - - $objectProperties = @() - - # Create an array of the AD property names to retrieve from the property map - foreach ($property in $PropertyMap) - { - if ($property -isnot [System.Collections.Hashtable]) - { - $errorMessage = $script:localizedData.PropertyMapArrayIsWrongType - New-InvalidOperationException -Message $errorMessage - } - - if ($property.ContainsKey('PropertyName')) - { - $objectProperties += @($property.PropertyName) - } - else - { - $objectProperties += $property.ParameterName - } - } - - return $objectProperties -} - -<# - .SYNOPSIS - This function is used to compare current and desired values for any DSC - resource, and return a hashtable with the result from the comparison. - - .PARAMETER CurrentValues - The current values that should be compared to to desired values. Normally - the values returned from Get-TargetResource. - - .PARAMETER DesiredValues - The values set in the configuration and is provided in the call to the - functions *-TargetResource, and that will be compared against current - 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. -#> -function Compare-ResourcePropertyState -{ - [CmdletBinding()] - [OutputType([System.Collections.Hashtable[]])] - param - ( - [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $CurrentValues, - - [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $DesiredValues, - - [Parameter()] - [System.String[]] - $Properties, - - [Parameter()] - [System.String[]] - $IgnoreProperties - ) - - if ($PSBoundParameters.ContainsKey('Properties')) - { - # Filter out the parameters (keys) not specified in Properties - $desiredValuesToRemove = $DesiredValues.Keys | Where-Object -FilterScript { - $_ -notin $Properties - } - - $desiredValuesToRemove | ForEach-Object -Process { - $DesiredValues.Remove($_) - } - } - else - { - <# - Remove any common parameters that might be part of DesiredValues, - if it $PSBoundParameters was used to pass the desired values. - #> - $commonParametersToRemove = $DesiredValues.Keys | Where-Object -FilterScript { - $_ -in [System.Management.Automation.PSCmdlet]::CommonParameters ` - -or $_ -in [System.Management.Automation.PSCmdlet]::OptionalCommonParameters - } - - $commonParametersToRemove | ForEach-Object -Process { - $DesiredValues.Remove($_) - } - } - - # Remove any properties that should be ignored. - if ($PSBoundParameters.ContainsKey('IgnoreProperties')) - { - $IgnoreProperties | ForEach-Object -Process { - if ($DesiredValues.ContainsKey($_)) - { - $DesiredValues.Remove($_) - } - } - } - - $compareTargetResourceStateReturnValue = @() - - foreach ($parameterName in $DesiredValues.Keys) - { - Write-Verbose -Message ($script:localizedData.EvaluatePropertyState -f $parameterName) -Verbose - - $parameterState = @{ - ParameterName = $parameterName - Expected = $DesiredValues.$parameterName - Actual = $CurrentValues.$parameterName - } - - # Check if the parameter is in compliance. - $isPropertyInDesiredState = Test-DscPropertyState -Values @{ - CurrentValue = $CurrentValues.$parameterName - DesiredValue = $DesiredValues.$parameterName - } - - if ($isPropertyInDesiredState) - { - Write-Verbose -Message ($script:localizedData.PropertyInDesiredState -f $parameterName) -Verbose - - $parameterState['InDesiredState'] = $true - } - else - { - Write-Verbose -Message ($script:localizedData.PropertyNotInDesiredState -f $parameterName) -Verbose - - $parameterState['InDesiredState'] = $false - } - - $compareTargetResourceStateReturnValue += $parameterState - } - - return $compareTargetResourceStateReturnValue -} - -<# - .SYNOPSIS - This function is used to compare the current and the desired value of a - property. - - .PARAMETER Values - This is set to a hash table with the current value (the CurrentValue key) - and desired value (the DesiredValue key). - - .EXAMPLE - Test-DscPropertyState -Values @{ - CurrentValue = 'John' - DesiredValue = 'Alice' - } - .EXAMPLE - Test-DscPropertyState -Values @{ - CurrentValue = 1 - DesiredValue = 2 - } -#> -function Test-DscPropertyState -{ - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $Values - ) - - $returnValue = $true - - if ($Values.CurrentValue -ne $Values.DesiredValue -or $Values.DesiredValue.GetType().IsArray) - { - $desiredType = $Values.DesiredValue.GetType() - - if ($desiredType.IsArray -eq $true) - { - if ($Values.CurrentValue -and $Values.DesiredValue) - { - $compareObjectParameters = @{ - ReferenceObject = $Values.CurrentValue - DifferenceObject = $Values.DesiredValue - } - - $arrayCompare = Compare-Object @compareObjectParameters - - if ($null -ne $arrayCompare) - { - Write-Verbose -Message $script:localizedData.ArrayDoesNotMatch -Verbose - - $arrayCompare | ForEach-Object -Process { - Write-Verbose -Message ($script:localizedData.ArrayValueThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose - } - - $returnValue = $false - } - } - else - { - $returnValue = $false - } - } - else - { - $returnValue = $false - - $supportedTypes = @( - 'String' - 'Int32' - 'Int16' - 'UInt16' - 'Single' - 'Boolean' - ) - - if ($desiredType.Name -notin $supportedTypes) - { - Write-Warning -Message ($script:localizedData.UnableToCompareType ` - -f $fieldName, $desiredType.Name) - } - else - { - Write-Verbose -Message ( - $script:localizedData.PropertyValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $Values.CurrentValue, $Values.DesiredValue - ) -Verbose - } - } - } - - return $returnValue -} - -<# - .SYNOPSIS - Asserts if the AD PS Drive has been created, and creates one if not. - - .PARAMETER Root - Specifies the AD path to which the drive is mapped. - - .NOTES - Throws an exception if the PS Drive cannot be created. -#> -function Assert-ADPSDrive -{ - [CmdletBinding()] - param - ( - [Parameter()] - [String] - $Root = '//RootDSE/' - ) - - Assert-Module -ModuleName 'ActiveDirectory' -ImportModule - - $activeDirectoryPSDrive = Get-PSDrive -Name AD -ErrorAction SilentlyContinue - - if ($null -eq $activeDirectoryPSDrive) - { - Write-Verbose -Message $script:localizedData.CreatingNewADPSDrive - try - { - New-PSDrive -Name AD -PSProvider 'ActiveDirectory' -Root $Root -Scope Script -ErrorAction Stop | Out-Null - } - catch - { - $errorMessage = $script:localizedData.CreatingNewADPSDriveError - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } -} diff --git a/DSCResources/MSFT_xADCommon/en-US/MSFT_xADCommon.strings.psd1 b/DSCResources/MSFT_xADCommon/en-US/MSFT_xADCommon.strings.psd1 deleted file mode 100644 index c69ead7e3..000000000 --- a/DSCResources/MSFT_xADCommon/en-US/MSFT_xADCommon.strings.psd1 +++ /dev/null @@ -1,36 +0,0 @@ -# culture="en-US" -ConvertFrom-StringData @' - WasExpectingDomainController = The operating system product type code returned 2, which indicates that this is domain controller, but was unable to retrieve the domain controller object. (ADCOMMON0001) - FailedEvaluatingDomainController = Could not evaluate if the node is a domain controller. (ADCOMMON0002) - EvaluatePropertyState = Evaluating the state of the property '{0}'. (ADCOMMON0003) - PropertyInDesiredState = The parameter '{0}' is in desired state. (ADCOMMON0004) - PropertyNotInDesiredState = The parameter '{0}' is not in desired state. (ADCOMMON0005) - ArrayDoesNotMatch = One or more values in an array does not match the desired state. Details of the changes are below. (ADCOMMON0006) - 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) - 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) -# MembersIsEmptyError = The Members parameter is empty. At least one group member must be provided. (ADCOMMON0013) - 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) - IncludeAndExcludeAreEmptyError = The '{0}' and '{1}' parameters are either both null or empty. At least one member must be specified in one of these parameters. (ADCOMMON0015) -# ModeConversionError = Converted mode {0} is not a {1}. (ADCOMMON0016) - RecycleBinRestoreFailed = Restoring {0} ({1}) from the recycle bin failed. Error message: {2}. (ADCOMMON0017) - EmptyDomainError = No domain name retrieved for group member {0} in group {1}. (ADCOMMON0018) - CheckingMembers = Checking for '{0}' members. (ADCOMMON0019) - MembershipCountMismatch = Membership count is not correct. Expected '{0}' members, actual '{1}' members. (ADCOMMON0020) - MemberNotInDesiredState = Member '{0}' is not in the desired state. (ADCOMMON0021) - RemovingDuplicateMember = Removing duplicate member '{0}' definition. (ADCOMMON0022) - MembershipInDesiredState = Membership is in the desired state. (ADCOMMON0023) -# MembershipNotDesiredState = Membership is NOT in the desired state. (ADCOMMON0024) - CheckingDomain = Checking for domain '{0}'. (ADCOMMON0025) - CheckingSite = Checking for site '{0}'. (ADCOMMON0026) - FindInRecycleBin = Finding objects in the recycle bin matching the filter {0}. (ADCOMMON0027) - FoundRestoreTargetInRecycleBin = Found object {0} ({1}) in the recycle bin as {2}. Attempting to restore the object. (ADCOMMON0028) - RecycleBinRestoreSuccessful = Successfully restored object {0} ({1}) from the recycle bin. (ADCOMMON0029) - AddingGroupMember = Adding member '{0}' from domain '{1}' to AD group '{2}'. (ADCOMMON0030) - PropertyMapArrayIsWrongType = An object in the property map array is not of the type [System.Collections.Hashtable]. (ADCOMMON0031) - CreatingNewADPSDrive = Creating new AD: PSDrive. (ADCOMMON0032) - CreatingNewADPSDriveError = Error creating AD: PS Drive. (ADCOMMON0033) -'@ diff --git a/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 b/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 index 7852c584e..5dd17e36b 100644 --- a/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 +++ b/DSCResources/MSFT_xADComputer/MSFT_xADComputer.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADComputer' <# diff --git a/DSCResources/MSFT_xADDomain/MSFT_xADDomain.psm1 b/DSCResources/MSFT_xADDomain/MSFT_xADDomain.psm1 index 941f1450f..3ecad350f 100644 --- a/DSCResources/MSFT_xADDomain/MSFT_xADDomain.psm1 +++ b/DSCResources/MSFT_xADDomain/MSFT_xADDomain.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADDomain' <# diff --git a/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 b/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 index e67178264..6da62eb90 100644 --- a/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 +++ b/DSCResources/MSFT_xADDomainController/MSFT_xADDomainController.psm1 @@ -6,12 +6,6 @@ Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath ' $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADDomainController' -## Import the common AD functions -$adCommonFunctions = Join-Path ` - -Path (Split-Path -Path $PSScriptRoot -Parent) ` - -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1' -Import-Module -Name $adCommonFunctions - <# .SYNOPSIS Returns the current state of the domain controller. diff --git a/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 b/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 index d33848871..1271c66f3 100644 --- a/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 +++ b/DSCResources/MSFT_xADDomainDefaultPasswordPolicy/MSFT_xADDomainDefaultPasswordPolicy.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADDomainDefaultPasswordPolicy' ## List of changeable policy properties diff --git a/DSCResources/MSFT_xADForestProperties/MSFT_xADForestProperties.psm1 b/DSCResources/MSFT_xADForestProperties/MSFT_xADForestProperties.psm1 index 19a84c52c..9616ea770 100644 --- a/DSCResources/MSFT_xADForestProperties/MSFT_xADForestProperties.psm1 +++ b/DSCResources/MSFT_xADForestProperties/MSFT_xADForestProperties.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADForestProperties' <# diff --git a/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 b/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 index 00ee7c506..e14365963 100644 --- a/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 +++ b/DSCResources/MSFT_xADGroup/MSFT_xADGroup.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADGroup' function Get-TargetResource diff --git a/DSCResources/MSFT_xADKDSKey/MSFT_xADKDSKey.psm1 b/DSCResources/MSFT_xADKDSKey/MSFT_xADKDSKey.psm1 index 7be03e993..5a5fd28d9 100644 --- a/DSCResources/MSFT_xADKDSKey/MSFT_xADKDSKey.psm1 +++ b/DSCResources/MSFT_xADKDSKey/MSFT_xADKDSKey.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADKDSKey' <# diff --git a/DSCResources/MSFT_xADManagedServiceAccount/MSFT_xADManagedServiceAccount.psm1 b/DSCResources/MSFT_xADManagedServiceAccount/MSFT_xADManagedServiceAccount.psm1 index 5841c8059..1d1b6a57e 100644 --- a/DSCResources/MSFT_xADManagedServiceAccount/MSFT_xADManagedServiceAccount.psm1 +++ b/DSCResources/MSFT_xADManagedServiceAccount/MSFT_xADManagedServiceAccount.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADManagedServiceAccount' <# diff --git a/DSCResources/MSFT_xADObjectPermissionEntry/MSFT_xADObjectPermissionEntry.psm1 b/DSCResources/MSFT_xADObjectPermissionEntry/MSFT_xADObjectPermissionEntry.psm1 index d2f8576d3..256a12739 100644 --- a/DSCResources/MSFT_xADObjectPermissionEntry/MSFT_xADObjectPermissionEntry.psm1 +++ b/DSCResources/MSFT_xADObjectPermissionEntry/MSFT_xADObjectPermissionEntry.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADObjectPermissionEntry' <# diff --git a/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 b/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 index bdafa2f49..71f75122a 100644 --- a/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 +++ b/DSCResources/MSFT_xADOrganizationalUnit/MSFT_xADOrganizationalUnit.psm1 @@ -4,9 +4,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADOrganizationalUnit' function Get-TargetResource diff --git a/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 b/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 index 5a8314a7e..6805ee1dc 100644 --- a/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 +++ b/DSCResources/MSFT_xADUser/MSFT_xADUser.psm1 @@ -8,9 +8,6 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat $script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common' Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath 'xActiveDirectory.Common.psm1') -$script:dscResourcePath = Split-Path -Path $PSScriptRoot -Parent -Import-Module -Name (Join-Path -Path $script:dscResourcePath -ChildPath '\MSFT_xADCommon\MSFT_xADCommon.psm1') - $script:localizedData = Get-LocalizedData -ResourceName 'MSFT_xADUser' # Create a property map that maps the DSC resource parameters to the diff --git a/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 b/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 index 3cb793b9f..079f6ffa4 100644 --- a/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 +++ b/Modules/xActiveDirectory.Common/en-US/xActiveDirectory.Common.strings.psd1 @@ -1,12 +1,42 @@ # Localized resources for helper module xActiveDirectory.Common. ConvertFrom-StringData @' - PropertyTypeInvalidForDesiredValues = Property 'DesiredValues' must be either a [System.Collections.Hashtable], [CimInstance] or [PSBoundParametersDictionary]. The type detected was {0}. - PropertyTypeInvalidForValuesToCheck = If 'DesiredValues' is a CimInstance, then property 'ValuesToCheck' must contain a value. - PropertyValidationError = Expected to find an array value for property {0} in the current values, but it was either not present or was null. This has caused the test method to return false. - PropertiesDoesNotMatch = Found an array for property {0} in the current values, but this array does not match the desired state. Details of the changes are below. - PropertyThatDoesNotMatch = {0} - {1} - ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. - UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-DscParameterState cmdlet. - StartProcess = Started the process with id {0} using the path '{1}', and with a timeout value of {2} seconds. + WasExpectingDomainController = The operating system product type code returned 2, which indicates that this is domain controller, but was unable to retrieve the domain controller object. (ADCOMMON0001) + FailedEvaluatingDomainController = Could not evaluate if the node is a domain controller. (ADCOMMON0002) + EvaluatePropertyState = Evaluating the state of the property '{0}'. (ADCOMMON0003) + PropertyInDesiredState = The parameter '{0}' is in desired state. (ADCOMMON0004) + PropertyNotInDesiredState = The parameter '{0}' is not in desired state. (ADCOMMON0005) + ArrayDoesNotMatch = One or more values in an array does not match the desired state. Details of the changes are below. (ADCOMMON0006) + 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) + 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) + IncludeAndExcludeAreEmptyError = The '{0}' and '{1}' parameters are either both null or empty. At least one member must be specified in one of these parameters. (ADCOMMON0015) + RecycleBinRestoreFailed = Restoring {0} ({1}) from the recycle bin failed. Error message: {2}. (ADCOMMON0017) + EmptyDomainError = No domain name retrieved for group member {0} in group {1}. (ADCOMMON0018) + CheckingMembers = Checking for '{0}' members. (ADCOMMON0019) + MembershipCountMismatch = Membership count is not correct. Expected '{0}' members, actual '{1}' members. (ADCOMMON0020) + MemberNotInDesiredState = Member '{0}' is not in the desired state. (ADCOMMON0021) + RemovingDuplicateMember = Removing duplicate member '{0}' definition. (ADCOMMON0022) + MembershipInDesiredState = Membership is in the desired state. (ADCOMMON0023) + MembershipNotDesiredState = Membership is NOT in the desired state. (ADCOMMON0024) + CheckingSite = Checking for site '{0}'. (ADCOMMON0026) + FindInRecycleBin = Finding objects in the recycle bin matching the filter {0}. (ADCOMMON0027) + FoundRestoreTargetInRecycleBin = Found object {0} ({1}) in the recycle bin as {2}. Attempting to restore the object. (ADCOMMON0028) + RecycleBinRestoreSuccessful = Successfully restored object {0} ({1}) from the recycle bin. (ADCOMMON0029) + AddingGroupMember = Adding member '{0}' from domain '{1}' to AD group '{2}'. (ADCOMMON0030) + PropertyMapArrayIsWrongType = An object in the property map array is not of the type [System.Collections.Hashtable]. (ADCOMMON0031) + CreatingNewADPSDrive = Creating new AD: PSDrive. (ADCOMMON0032) + CreatingNewADPSDriveError = Error creating AD: PS Drive. (ADCOMMON0033) + PropertyTypeInvalidForDesiredValues = Property 'DesiredValues' must be either a [System.Collections.Hashtable], [CimInstance] or [PSBoundParametersDictionary]. The type detected was {0}. (ADCOMMON0034) + PropertyTypeInvalidForValuesToCheck = If 'DesiredValues' is a CimInstance, then property 'ValuesToCheck' must contain a value. (ADCOMMON0035) + PropertyValidationError = Expected to find an array value for property {0} in the current values, but it was either not present or was null. This has caused the test method to return false. (ADCOMMON0036) + PropertiesDoesNotMatch = Found an array for property {0} in the current values, but this array does not match the desired state. Details of the changes are below. (ADCOMMON0037) + PropertyThatDoesNotMatch = {0} - {1} (ADCOMMON0038) + 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) '@ diff --git a/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 b/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 index e18dec420..c5dc3ee78 100644 --- a/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 +++ b/Modules/xActiveDirectory.Common/xActiveDirectory.Common.psm1 @@ -468,6 +468,1320 @@ function Start-ProcessWithTimeout return $sqlSetupProcess.ExitCode } +# Internal function to assert if the role specific module is installed or not +function Assert-Module +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName = 'ActiveDirectory', + + [Parameter()] + [switch] + $ImportModule + ) + + if (-not (Get-Module -Name $ModuleName -ListAvailable)) + { + $errorId = '{0}_ModuleNotFound' -f $ModuleName + $errorMessage = $script:localizedData.RoleNotFoundError -f $moduleName + ThrowInvalidOperationError -ErrorId $errorId -ErrorMessage $errorMessage + } + + if ($ImportModule) + { + Import-Module -Name $ModuleName + } +} #end function Assert-Module + +# Internal function to test whether computer is a member of a domain +function Test-DomainMember +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + ) + + $isDomainMember = [System.Boolean] (Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false).PartOfDomain + return $isDomainMember +} + + +# Internal function to get the domain name of the computer +function Get-DomainName +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + ) + + $domainName = [System.String] (Get-CimInstance -ClassName Win32_ComputerSystem -Verbose:$false).Domain + return $domainName +} # function Get-DomainName + +# Internal function to build domain FQDN +function Resolve-DomainFQDN +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [OutputType([System.String])] + [System.String] $DomainName, + + [Parameter()] [AllowNull()] + [System.String] $ParentDomainName + ) + + $domainFQDN = $DomainName + if ($ParentDomainName) + { + $domainFQDN = '{0}.{1}' -f $DomainName, $ParentDomainName + } + return $domainFQDN +} + +# Internal function to get an Active Directory object's parent Distinguished Name +function Get-ADObjectParentDN +{ + <# + Copyright (c) 2016 The University Of Vermont + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that + the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the + following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other materials provided with the distribution. + 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote + products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + http://www.uvm.edu/~gcd/code-license/ + #> + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DN + ) + + # https://www.uvm.edu/~gcd/2012/07/listing-parent-of-ad-object-in-powershell/ + $distinguishedNameParts = $DN -split '(? +function ConvertFrom-TimeSpan +{ + [CmdletBinding()] + [OutputType([System.Int32])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.TimeSpan] + $TimeSpan, + + [Parameter(Mandatory = $true)] + [ValidateSet('Seconds', 'Minutes', 'Hours', 'Days')] + [System.String] + $TimeSpanType + ) + + switch ($TimeSpanType) + { + 'Seconds' + { + return $TimeSpan.TotalSeconds -as [System.UInt32] + } + 'Minutes' + { + return $TimeSpan.TotalMinutes -as [System.UInt32] + } + 'Hours' + { + return $TimeSpan.TotalHours -as [System.UInt32] + } + 'Days' + { + return $TimeSpan.TotalDays -as [System.UInt32] + } + } +} #end function ConvertFrom-TimeSpan + +<# + .SYNOPSIS + Returns common AD cmdlet connection parameter for splatting + .PARAMETER CommonName + When specified, a CommonName overrides theUsed by the xADUser cmdletReturns the Identity as the Name key. For example, the Get-ADUser, Set-ADUser and + Remove-ADUser cmdlets take an Identity parameter, but the New-ADUser cmdlet uses the + Name parameter. + .PARAMETER UseNameParameter + Returns the Identity as the Name key. For example, the Get-ADUser, Set-ADUser and + Remove-ADUser cmdlets take an Identity parameter, but the New-ADUser cmdlet uses the + Name parameter. + .EXAMPLE + $getADUserParams = Get-CommonADParameters @PSBoundParameters + + Returns connection parameters suitable for Get-ADUser using the splatted cmdlet + parameters. + .EXAMPLE + $newADUserParams = Get-CommonADParameters @PSBoundParameters -UseNameParameter + + Returns connection parameters suitable for New-ADUser using the splatted cmdlet + parameters. +#> +function Get-ADCommonParameters +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Alias('UserName', 'GroupName', 'ComputerName', 'ServiceAccountName')] + [System.String] + $Identity, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $CommonName, + + [Parameter()] + [ValidateNotNull()] + [Alias('DomainAdministratorCredential')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.CredentialAttribute()] + $Credential, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [System.String] + $Server, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UseNameParameter, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $PreferCommonName, + + ## Catch all to enable splatted $PSBoundParameters + [Parameter(ValueFromRemainingArguments)] + $RemainingArguments + ) + + if ($UseNameParameter) + { + if ($PreferCommonName -and ($PSBoundParameters.ContainsKey('CommonName'))) + { + $adConnectionParameters = @{ Name = $CommonName } + } + else + { + $adConnectionParameters = @{ Name = $Identity } + } + } + else + { + if ($PreferCommonName -and ($PSBoundParameters.ContainsKey('CommonName'))) + { + $adConnectionParameters = @{ Identity = $CommonName } + } + else + { + $adConnectionParameters = @{ Identity = $Identity } + } + } + + if ($Credential) + { + $adConnectionParameters['Credential'] = $Credential + } + + if ($Server) + { + $adConnectionParameters['Server'] = $Server + } + + return $adConnectionParameters +} #end function Get-ADCommonParameters + +function ThrowInvalidOperationError +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorMessage + ) + + $exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $ErrorMessage + $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidOperation + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $errorCategory, $null + throw $errorRecord +} + +function ThrowInvalidArgumentError +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorMessage + ) + + $exception = New-Object -TypeName System.ArgumentException -ArgumentList $ErrorMessage + $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList $exception, $ErrorId, $errorCategory, $null + throw $errorRecord + +} #end function ThrowInvalidArgumentError + +## Internal function to test site availability +function Test-ADReplicationSite +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $SiteName, + + [Parameter(Mandatory = $true)] + [System.String] + $DomainName, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential + ) + + Write-Verbose -Message ($script:localizedData.CheckingSite -f $SiteName) + + $existingDC = "$((Get-ADDomainController -Discover -DomainName $DomainName -ForceDiscover).HostName)" + + try + { + $site = Get-ADReplicationSite -Identity $SiteName -Server $existingDC -Credential $Credential + } + catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] + { + return $false + } + + return ($null -ne $site) +} + +function ConvertTo-DeploymentForestMode +{ + [CmdletBinding()] + [OutputType([Microsoft.DirectoryServices.Deployment.Types.ForestMode])] + param + ( + [Parameter( + Mandatory = $true, + ParameterSetName = 'ById')] + [UInt16] + $ModeId, + + [Parameter( + Mandatory = $true, + ParameterSetName = 'ByName')] + [AllowNull()] + [System.Nullable``1[Microsoft.ActiveDirectory.Management.ADForestMode]] + $Mode, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName = 'xActiveDirectory' + ) + + $convertedMode = $null + + if ($PSCmdlet.ParameterSetName -eq 'ByName' -and $Mode) + { + $convertedMode = $Mode -as [Microsoft.DirectoryServices.Deployment.Types.ForestMode] + } + + if ($PSCmdlet.ParameterSetName -eq 'ById') + { + $convertedMode = $ModeId -as [Microsoft.DirectoryServices.Deployment.Types.ForestMode] + } + + if ([enum]::GetValues([Microsoft.DirectoryServices.Deployment.Types.ForestMode]) -notcontains $convertedMode) + { + return $null + } + + return $convertedMode +} + +function ConvertTo-DeploymentDomainMode +{ + [CmdletBinding()] + [OutputType([Microsoft.DirectoryServices.Deployment.Types.DomainMode])] + param + ( + [Parameter( + Mandatory = $true, + ParameterSetName = 'ById')] + [UInt16] + $ModeId, + + [Parameter( + Mandatory = $true, + ParameterSetName = 'ByName')] + [AllowNull()] + [System.Nullable``1[Microsoft.ActiveDirectory.Management.ADDomainMode]] + $Mode, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName = 'xActiveDirectory' + ) + + $convertedMode = $null + + if ($PSCmdlet.ParameterSetName -eq 'ByName' -and $Mode) + { + $convertedMode = $Mode -as [Microsoft.DirectoryServices.Deployment.Types.DomainMode] + } + + if ($PSCmdlet.ParameterSetName -eq 'ById') + { + $convertedMode = $ModeId -as [Microsoft.DirectoryServices.Deployment.Types.DomainMode] + } + + if ([enum]::GetValues([Microsoft.DirectoryServices.Deployment.Types.DomainMode]) -notcontains $convertedMode) + { + return $null + } + + return $convertedMode +} + +function Restore-ADCommonObject +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Alias('UserName', 'GroupName', 'ComputerName', 'ServiceAccountName')] + [System.String] + $Identity, + + [Parameter(Mandatory = $true)] + [ValidateSet('Computer', 'OrganizationalUnit', 'User', 'Group')] + [System.String] + $ObjectClass, + + [Parameter()] + [ValidateNotNull()] + [Alias('DomainAdministratorCredential')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.CredentialAttribute()] + $Credential, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [System.String] + $Server + ) + + $restoreFilter = 'msDS-LastKnownRDN -eq "{0}" -and objectClass -eq "{1}" -and isDeleted -eq $true' -f $Identity, $ObjectClass + Write-Verbose -Message ($script:localizedData.FindInRecycleBin -f $restoreFilter) -Verbose + + <# + Using IsDeleted and IncludeDeletedObjects will mean that the cmdlet does not throw + any more, and simply returns $null instead + #> + $commonParams = Get-ADCommonParameters @PSBoundParameters + $getAdObjectParams = $commonParams.Clone() + $getAdObjectParams.Remove('Identity') + $getAdObjectParams['Filter'] = $restoreFilter + $getAdObjectParams['IncludeDeletedObjects'] = $true + $getAdObjectParams['Properties'] = @('whenChanged') + + # If more than one object is returned, we pick the one that was changed last. + $restorableObject = Get-ADObject @getAdObjectParams | + Sort-Object -Descending -Property 'whenChanged' | + Select-Object -First 1 + + $restoredObject = $null + + if ($restorableObject) + { + Write-Verbose -Message ($script:localizedData.FoundRestoreTargetInRecycleBin -f $Identity, $ObjectClass, $restorableObject.DistinguishedName) -Verbose + + try + { + $restoreParams = $commonParams.Clone() + $restoreParams['PassThru'] = $true + $restoreParams['ErrorAction'] = 'Stop' + $restoreParams['Identity'] = $restorableObject.DistinguishedName + $restoredObject = Restore-ADObject @restoreParams + Write-Verbose -Message ($script:localizedData.RecycleBinRestoreSuccessful -f $Identity, $ObjectClass) -Verbose + } + catch [Microsoft.ActiveDirectory.Management.ADException] + { + # After Get-TargetResource is through, only one error can occur here: Object parent does not exist + ThrowInvalidOperationError -ErrorId "$($Identity)_RecycleBinRestoreFailed" -ErrorMessage ($script:localizedData.RecycleBinRestoreFailed -f $Identity, $ObjectClass, $_.Exception.Message) + } + } + + return $restoredObject +} + +<# + .SYNOPSIS + Author: Robert D. Biddle (https://github.com/RobBiddle) + Created: December.20.2017 + + .DESCRIPTION + Takes an Active Directory DistinguishedName as input, returns the domain FQDN + + .EXAMPLE + Get-ADDomainNameFromDistinguishedName -DistinguishedName 'CN=ExampleObject,OU=ExampleOU,DC=example,DC=com' +#> +function Get-ADDomainNameFromDistinguishedName +{ + [CmdletBinding()] + param + ( + [Parameter()] + [System.String] + $DistinguishedName + ) + + if ($DistinguishedName -notlike '*DC=*') + { + return + } + + $splitDistinguishedName = ($DistinguishedName -split 'DC=') + $splitDistinguishedNameParts = $splitDistinguishedName[1..$splitDistinguishedName.Length] + $domainFqdn = "" + foreach ($part in $splitDistinguishedNameParts) + { + $domainFqdn += "DC=$part" + } + + $domainName = $domainFqdn -replace 'DC=', '' -replace ',', '.' + return $domainName + +} #end function Get-ADDomainNameFromDistinguishedName + +<# + .SYNOPSIS + Add group member from current or different domain + + .NOTES + Author original code: Robert D. Biddle (https://github.com/RobBiddle) + Author refactored code: Jan-Hendrik Peters (https://github.com/nyanhp) +#> +function Add-ADCommonGroupMember +{ + [CmdletBinding()] + param + ( + [Parameter()] + [string[]] + $Members, + + [Parameter()] + [hashtable] + $Parameters, + + [Parameter()] + [switch] + $MembersInMultipleDomains + ) + + Assert-Module -ModuleName ActiveDirectory + + if ($MembersInMultipleDomains.IsPresent) + { + foreach ($member in $Members) + { + $memberDomain = Get-ADDomainNameFromDistinguishedName -DistinguishedName $member + + if (-not $memberDomain) + { + ThrowInvalidArgumentError -ErrorId "$($member)_EmptyDomainError" -ErrorMessage ($script:localizedData.EmptyDomainError -f $member, $Parameters.GroupName) + } + + Write-Verbose -Message ($script:localizedData.AddingGroupMember -f $member, $memberDomain, $Parameters.GroupName) + $memberObjectClass = (Get-ADObject -Identity $member -Server $memberDomain -Properties ObjectClass).ObjectClass + if ($memberObjectClass -eq 'computer') + { + $memberObject = Get-ADComputer -Identity $member -Server $memberDomain + } + elseif ($memberObjectClass -eq 'group') + { + $memberObject = Get-ADGroup -Identity $member -Server $memberDomain + } + elseif ($memberObjectClass -eq 'user') + { + $memberObject = Get-ADUser -Identity $member -Server $memberDomain + } + + Add-ADGroupMember @Parameters -Members $memberObject + } + } + else + { + Add-ADGroupMember @Parameters -Members $Members + } +} + +<# + .SYNOPSIS + Returns the domain controller object if the node is a domain controller, + otherwise it return $null. + + .PARAMETER DomainName + The name of the domain that should contain the domain controller. + + .PARAMETER ComputerName + The name of the node to return the domain controller object for. + Defaults to $env:COMPUTERNAME. + + .OUTPUTS + If the domain controller is not found, an empty object ($null) is returned. + + .NOTES + Throws an exception of Microsoft.ActiveDirectory.Management.ADServerDownException + if the domain cannot be contacted. +#> +function Get-DomainControllerObject +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $DomainName, + + [Parameter()] + [System.String] + $ComputerName = $env:COMPUTERNAME, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential + ) + + <# + It is not possible to use `-ErrorAction 'SilentlyContinue` on the + cmdlet Get-ADDomainController, it will throw an error regardless. + #> + try + { + $getADDomainControllerParameters = @{ + Filter = 'Name -eq "{0}"' -f $ComputerName + Server = $DomainName + } + + if ($PSBoundParameters.ContainsKey('Credential')) + { + $getADDomainControllerParameters['Credential'] = $Credential + } + + $domainControllerObject = Get-ADDomainController @getADDomainControllerParameters + + if (-not $domainControllerObject -and (Test-IsDomainController) -eq $true) + { + $errorMessage = $script:localizedData.WasExpectingDomainController + New-InvalidResultException -Message $errorMessage + } + } + catch + { + $errorMessage = $script:localizedData.FailedEvaluatingDomainController + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + + return $domainControllerObject +} + +<# + .SYNOPSIS + Returns $true if the node is a domain controller, otherwise it returns + $false +#> +function Test-IsDomainController +{ + [CmdletBinding()] + param + ( + ) + + $operatingSystemInformation = Get-CimInstance -ClassName 'Win32_OperatingSystem' + + return $operatingSystemInformation.ProductType -eq 2 +} + +<# + .SYNOPSIS + Converts a hashtable containing the parameter to property mappings to + an array of properties that can be used to call cmdlets that supports the + parameter Properties. + + .PARAMETER PropertyMap + The property map, as an array of hashtables, to convert to a properties array. + + .EXAMPLE + $computerObjectPropertyMap = @( + @{ + ParameterName = 'ComputerName' + PropertyName = 'cn' + }, + @{ + ParameterName = 'Location' + } + ) + + $computerObjectProperties = Convert-PropertyMapToObjectProperties $computerObjectPropertyMap + $getADComputerResult = Get-ADComputer -Identity 'APP01' -Properties $computerObjectProperties +#> +function Convert-PropertyMapToObjectProperties +{ + [CmdletBinding()] + [OutputType([System.Array])] + param + ( + [Parameter(Mandatory = $true)] + [System.Array] + $PropertyMap + ) + + $objectProperties = @() + + # Create an array of the AD property names to retrieve from the property map + foreach ($property in $PropertyMap) + { + if ($property -isnot [System.Collections.Hashtable]) + { + $errorMessage = $script:localizedData.PropertyMapArrayIsWrongType + New-InvalidOperationException -Message $errorMessage + } + + if ($property.ContainsKey('PropertyName')) + { + $objectProperties += @($property.PropertyName) + } + else + { + $objectProperties += $property.ParameterName + } + } + + return $objectProperties +} + +<# + .SYNOPSIS + This function is used to compare current and desired values for any DSC + resource, and return a hashtable with the result from the comparison. + + .PARAMETER CurrentValues + The current values that should be compared to to desired values. Normally + the values returned from Get-TargetResource. + + .PARAMETER DesiredValues + The values set in the configuration and is provided in the call to the + functions *-TargetResource, and that will be compared against current + 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. +#> +function Compare-ResourcePropertyState +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable[]])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $DesiredValues, + + [Parameter()] + [System.String[]] + $Properties, + + [Parameter()] + [System.String[]] + $IgnoreProperties + ) + + if ($PSBoundParameters.ContainsKey('Properties')) + { + # Filter out the parameters (keys) not specified in Properties + $desiredValuesToRemove = $DesiredValues.Keys | Where-Object -FilterScript { + $_ -notin $Properties + } + + $desiredValuesToRemove | ForEach-Object -Process { + $DesiredValues.Remove($_) + } + } + else + { + <# + Remove any common parameters that might be part of DesiredValues, + if it $PSBoundParameters was used to pass the desired values. + #> + $commonParametersToRemove = $DesiredValues.Keys | Where-Object -FilterScript { + $_ -in [System.Management.Automation.PSCmdlet]::CommonParameters ` + -or $_ -in [System.Management.Automation.PSCmdlet]::OptionalCommonParameters + } + + $commonParametersToRemove | ForEach-Object -Process { + $DesiredValues.Remove($_) + } + } + + # Remove any properties that should be ignored. + if ($PSBoundParameters.ContainsKey('IgnoreProperties')) + { + $IgnoreProperties | ForEach-Object -Process { + if ($DesiredValues.ContainsKey($_)) + { + $DesiredValues.Remove($_) + } + } + } + + $compareTargetResourceStateReturnValue = @() + + foreach ($parameterName in $DesiredValues.Keys) + { + Write-Verbose -Message ($script:localizedData.EvaluatePropertyState -f $parameterName) -Verbose + + $parameterState = @{ + ParameterName = $parameterName + Expected = $DesiredValues.$parameterName + Actual = $CurrentValues.$parameterName + } + + # Check if the parameter is in compliance. + $isPropertyInDesiredState = Test-DscPropertyState -Values @{ + CurrentValue = $CurrentValues.$parameterName + DesiredValue = $DesiredValues.$parameterName + } + + if ($isPropertyInDesiredState) + { + Write-Verbose -Message ($script:localizedData.PropertyInDesiredState -f $parameterName) -Verbose + + $parameterState['InDesiredState'] = $true + } + else + { + Write-Verbose -Message ($script:localizedData.PropertyNotInDesiredState -f $parameterName) -Verbose + + $parameterState['InDesiredState'] = $false + } + + $compareTargetResourceStateReturnValue += $parameterState + } + + return $compareTargetResourceStateReturnValue +} + +<# + .SYNOPSIS + This function is used to compare the current and the desired value of a + property. + + .PARAMETER Values + This is set to a hash table with the current value (the CurrentValue key) + and desired value (the DesiredValue key). + + .EXAMPLE + Test-DscPropertyState -Values @{ + CurrentValue = 'John' + DesiredValue = 'Alice' + } + .EXAMPLE + Test-DscPropertyState -Values @{ + CurrentValue = 1 + DesiredValue = 2 + } +#> +function Test-DscPropertyState +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $Values + ) + + $returnValue = $true + + if ($Values.CurrentValue -ne $Values.DesiredValue -or $Values.DesiredValue.GetType().IsArray) + { + $desiredType = $Values.DesiredValue.GetType() + + if ($desiredType.IsArray -eq $true) + { + if ($Values.CurrentValue -and $Values.DesiredValue) + { + $compareObjectParameters = @{ + ReferenceObject = $Values.CurrentValue + DifferenceObject = $Values.DesiredValue + } + + $arrayCompare = Compare-Object @compareObjectParameters + + if ($null -ne $arrayCompare) + { + Write-Verbose -Message $script:localizedData.ArrayDoesNotMatch -Verbose + + $arrayCompare | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.ArrayValueThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose + } + + $returnValue = $false + } + } + else + { + $returnValue = $false + } + } + else + { + $returnValue = $false + + $supportedTypes = @( + 'String' + 'Int32' + 'Int16' + 'UInt16' + 'Single' + 'Boolean' + ) + + if ($desiredType.Name -notin $supportedTypes) + { + Write-Warning -Message ($script:localizedData.UnableToCompareType ` + -f $fieldName, $desiredType.Name) + } + else + { + Write-Verbose -Message ( + $script:localizedData.PropertyValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $Values.CurrentValue, $Values.DesiredValue + ) -Verbose + } + } + } + + return $returnValue +} + +<# + .SYNOPSIS + Asserts if the AD PS Drive has been created, and creates one if not. + + .PARAMETER Root + Specifies the AD path to which the drive is mapped. + + .NOTES + Throws an exception if the PS Drive cannot be created. +#> +function Assert-ADPSDrive +{ + [CmdletBinding()] + param + ( + [Parameter()] + [String] + $Root = '//RootDSE/' + ) + + Assert-Module -ModuleName 'ActiveDirectory' -ImportModule + + $activeDirectoryPSDrive = Get-PSDrive -Name AD -ErrorAction SilentlyContinue + + if ($null -eq $activeDirectoryPSDrive) + { + Write-Verbose -Message $script:localizedData.CreatingNewADPSDrive + try + { + New-PSDrive -Name AD -PSProvider 'ActiveDirectory' -Root $Root -Scope Script -ErrorAction Stop | Out-Null + } + catch + { + $errorMessage = $script:localizedData.CreatingNewADPSDriveError + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } +} + + $script:localizedData = Get-LocalizedData -ResourceName 'xActiveDirectory.Common' -ScriptRoot $PSScriptRoot Export-ModuleMember -Function @( @@ -478,4 +1792,29 @@ Export-ModuleMember -Function @( 'Get-LocalizedData' 'Test-DscParameterState' 'Start-ProcessWithTimeout' + 'Assert-Module' + 'Test-DomainMember' + 'Get-DomainName' + 'Resolve-DomainFQDN' + 'Get-ADObjectParentDN' + 'Assert-MemberParameters' + 'Remove-DuplicateMembers' + 'Test-Members' + 'ConvertTo-TimeSpan' + 'ConvertFrom-TimeSpan' + 'Get-ADCommonParameters' + 'ThrowInvalidOperationError' + 'ThrowInvalidArgumentError' + 'Test-ADReplicationSite' + 'ConvertTo-DeploymentForestMode' + 'ConvertTo-DeploymentDomainMode' + 'Restore-ADCommonObject' + 'Get-ADDomainNameFromDistinguishedName' + 'Add-ADCommonGroupMember' + 'Get-DomainControllerObject' + 'Test-IsDomainController' + 'Convert-PropertyMapToObjectProperties' + 'Compare-ResourcePropertyState' + 'Test-DscPropertyState' + 'Assert-ADPSDrive' ) diff --git a/Tests/Unit/MSFT_xADCommon.Tests.ps1 b/Tests/Unit/MSFT_xADCommon.Tests.ps1 deleted file mode 100644 index ac8bbee19..000000000 --- a/Tests/Unit/MSFT_xADCommon.Tests.ps1 +++ /dev/null @@ -1,1524 +0,0 @@ -[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] -param() - -$Global:DSCModuleName = 'xActiveDirectory' # Example xNetworking -$Global:DSCResourceName = 'MSFT_xADCommon' # Example MSFT_xFirewall - -#region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) -{ - & git @('clone','https://github.com/PowerShell/DscResource.Tests.git',(Join-Path -Path $moduleRoot -ChildPath '\DSCResource.Tests\')) -} - -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force -$TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` - -TestType Unit -#endregion - -function Invoke-TestSetup -{ - # If one type does not exist, it's assumed the other ones does not exist either. - if (-not ('Microsoft.DirectoryServices.Deployment.Types.ForestMode' -as [Type])) - { - Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'Microsoft.DirectoryServices.Deployment.Types.cs') - } - - # If one type does not exist, it's assumed the other ones does not exist either. - if (-not ('Microsoft.ActiveDirectory.Management.ADForestMode' -as [Type])) - { - Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'Microsoft.ActiveDirectory.Management.cs') - } -} - -# Begin Testing -try -{ - Invoke-TestSetup - - #region Pester Tests - - # The InModuleScope command allows you to perform white-box unit testing on the internal - # (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { - - #region Pester Test Initialization - - #endregion - - #region Function ResolveDomainFQDN - Describe "$($Global:DSCResourceName)\Resolve-DomainFQDN" { - - It 'Returns "DomainName" when "ParentDomainName" not supplied' { - $testDomainName = 'contoso.com'; - $testParentDomainName = $null; - - $result = Resolve-DomainFQDN -DomainName $testDomainName -ParentDomainName $testParentDOmainName; - - $result | Should Be $testDomainName; - } - - It 'Returns compound "DomainName.ParentDomainName" when "ParentDomainName" supplied' { - $testDomainName = 'subdomain'; - $testParentDomainName = 'contoso.com'; - - $result = Resolve-DomainFQDN -DomainName $testDomainName -ParentDomainName $testParentDomainName; - - $result | Should Be "$testDomainName.$testParentDomainName"; - } - - } - #endregion - - #region Function TestDomainMember - Describe "$($Global:DSCResourceName)\Test-DomainMember" { - - It 'Returns "True" when domain member' { - Mock -CommandName Get-CimInstance -MockWith { return @{ Name = $env:COMPUTERNAME; PartOfDomain = $true; } } - - Test-DomainMember | Should Be $true; - } - - It 'Returns "False" when workgroup member' { - Mock -CommandName Get-CimInstance -MockWith { return @{ Name = $env:COMPUTERNAME; } } - - Test-DomainMember | Should Be $false; - } - - } - #endregion - - #region Function Get-DomainName - Describe "$($Global:DSCResourceName)\Get-DomainName" { - - It 'Returns exepected domain name' { - Mock -CommandName Get-CimInstance -MockWith { return @{ Name = $env:COMPUTERNAME; Domain = 'contoso.com'; } } - - Get-DomainName | Should Be 'contoso.com'; - } - - } - #endregion - - #region Function Assert-Module - Describe "$($Global:DSCResourceName)\Assert-Module" { - - It 'Does not throw when module is installed' { - $testModuleName = 'TestModule'; - Mock -CommandName Get-Module -ParameterFilter { $Name -eq $testModuleName } -MockWith { return $true; } - - { Assert-Module -ModuleName $testModuleName } | Should Not Throw; - } - - It 'Should call Import-Module when the module is installed and ImportModule is specified' { - $testModuleName = 'TestModule' - Mock -CommandName Get-Module -ParameterFilter { $Name -eq $testModuleName } -MockWith { return $true; } - Mock -CommandName Import-Module -ParameterFilter { $Name -eq $testModuleName } - - Assert-Module -ModuleName $testModuleName -ImportModule - - Assert-MockCalled -CommandName Import-Module - } - - It 'Throws when module is not installed' { - $testModuleName = 'TestModule'; - Mock -CommandName Get-Module -ParameterFilter { $Name -eq $testModuleName } - - { Assert-Module -ModuleName $testModuleName } | Should Throw; - } - - } - #endregion - - #region Function Assert-Module - Describe "$($Global:DSCResourceName)\Get-ADObjectParentDN" { - - It "Returns CN object parent path" { - Get-ADObjectParentDN -DN 'CN=Administrator,CN=Users,DC=contoso,DC=com' | Should Be 'CN=Users,DC=contoso,DC=com'; - } - - It "Returns OU object parent path" { - Get-ADObjectParentDN -DN 'CN=Administrator,OU=Custom Organizational Unit,DC=contoso,DC=com' | Should Be 'OU=Custom Organizational Unit,DC=contoso,DC=com'; - } - - } - #endregion - - #region Function Remove-DuplicateMembers - Describe "$($Global:DSCResourceName)\Remove-DuplicateMembers" { - - It 'Removes one duplicate' { - $members = Remove-DuplicateMembers -Members 'User1','User2','USER1'; - - $members.Count | Should Be 2; - $members -contains 'User1' | Should Be $true; - $members -contains 'User2' | Should Be $true; - } - - It 'Removes two duplicates' { - $members = Remove-DuplicateMembers -Members 'User1','User2','USER1','USER2'; - - $members.Count | Should Be 2; - $members -contains 'User1' | Should Be $true; - $members -contains 'User2' | Should Be $true; - } - - It 'Removes double duplicates' { - $members = Remove-DuplicateMembers -Members 'User1','User2','USER1','user1'; - - $members.Count | Should Be 2; - $members -contains 'User1' | Should Be $true; - $members -contains 'User2' | Should Be $true; - } - - } - #endregion - - #region Function Test-Members - Describe "$($Global:DSCResourceName)\Test-Members" { - - It 'Passes when nothing is passed' { - Test-Members -ExistingMembers $null | Should Be $true; - } - - It 'Passes when there are existing members but members are required' { - $testExistingMembers = @('USER1', 'USER2'); - - Test-Members -ExistingMembers $testExistingMembers | Should Be $true; - } - - It 'Passes when existing members match required members' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembers = @('USER2', 'USER1'); - - Test-Members -ExistingMembers $testExistingMembers -Members $testMembers | Should Be $true; - } - - It 'Fails when there are no existing members and members are required' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembers = @('USER1', 'USER3'); - - Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false; - } - - It 'Fails when there are more existing members than the members required' { - $testExistingMembers = @('USER1', 'USER2', 'USER3'); - $testMembers = @('USER1', 'USER3'); - - Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false; - } - - It 'Fails when there are more existing members than the members required' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembers = @('USER1', 'USER3', 'USER2'); - - Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false; - } - - It 'Fails when existing members do not match required members' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembers = @('USER1', 'USER3'); - - Test-Members -ExistingMembers $testExistingMembers -Members $testMembers | Should Be $false; - } - - It 'Passes when existing members include required member' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembersToInclude = @('USER2'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $true; - } - - It 'Passes when existing members include required members' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembersToInclude = @('USER2', 'USER1'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $true; - } - - It 'Fails when existing members is missing a required member' { - $testExistingMembers = @('USER1'); - $testMembersToInclude = @('USER2'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $false; - } - - It 'Fails when existing members is missing a required member' { - $testExistingMembers = @('USER1', 'USER3'); - $testMembersToInclude = @('USER2'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $false; - } - - It 'Fails when existing members is missing a required members' { - $testExistingMembers = @('USER3'); - $testMembersToInclude = @('USER1', 'USER2'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should Be $false; - } - - It 'Passes when existing member does not include excluded member' { - $testExistingMembers = @('USER1'); - $testMembersToExclude = @('USER2'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToInclude | Should Be $true; - } - - It 'Passes when existing member does not include excluded members' { - $testExistingMembers = @('USER1'); - $testMembersToExclude = @('USER2', 'USER3'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToInclude | Should Be $true; - } - - It 'Passes when existing members does not include excluded member' { - $testExistingMembers = @('USER1', 'USER2'); - $testMembersToExclude = @('USER3'); - - Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToInclude | Should Be $true; - } - } - #endregion - - #region Function Assert-MemberParameters - Describe "$($Global:DSCResourceName)\Assert-MemberParameters" { - - It "Throws if 'Members' is specified but is empty" { - { Assert-MemberParameters -Members @() } | Should Throw 'The Members parameter value is null'; - } - - It "Throws if 'Members' and 'MembersToInclude' are specified" { - { Assert-MemberParameters -Members @('User1') -MembersToInclude @('User1') } | Should Throw 'parameters conflict'; - } - - It "Throws if 'Members' and 'MembersToExclude' are specified" { - { Assert-MemberParameters -Members @('User1') -MembersToExclude @('User2') } | Should Throw 'parameters conflict'; - } - - It "Throws if 'MembersToInclude' and 'MembersToExclude' contain the same member" { - { Assert-MemberParameters -MembersToExclude @('user1') -MembersToInclude @('USER1') } | Should Throw 'member must not be included in both'; - } - - It "Throws if 'MembersToInclude' and 'MembersToExclude' are empty" { - { Assert-MemberParameters -MembersToExclude @() -MembersToInclude @() } | Should Throw 'At least one member must be specified'; - } - - } - #endregion - - #region Function ConvertTo-Timespan - Describe "$($Global:DSCResourceName)\ConvertTo-Timespan" { - - It "Returns 'System.TimeSpan' object type" { - $testIntTimeSpan = 60; - - $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Minutes; - - $result -is [System.TimeSpan] | Should Be $true; - } - - It "Creates TimeSpan from seconds" { - $testIntTimeSpan = 60; - - $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Seconds; - - $result.TotalSeconds | Should Be $testIntTimeSpan; - } - - It "Creates TimeSpan from minutes" { - $testIntTimeSpan = 60; - - $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Minutes; - - $result.TotalMinutes | Should Be $testIntTimeSpan; - } - - It "Creates TimeSpan from hours" { - $testIntTimeSpan = 60; - - $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Hours; - - $result.TotalHours | Should Be $testIntTimeSpan; - } - - It "Creates TimeSpan from days" { - $testIntTimeSpan = 60; - - $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Days; - - $result.TotalDays | Should Be $testIntTimeSpan; - } - - } - #endregion - - #region Function ConvertTo-Timespan - Describe "$($Global:DSCResourceName)\ConvertFrom-Timespan" { - - It "Returns 'System.UInt32' object type" { - $testIntTimeSpan = 60; - $testTimeSpan = New-TimeSpan -Seconds $testIntTimeSpan; - - $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Seconds; - - $result -is [System.UInt32] | Should Be $true; - } - - It "Converts TimeSpan to total seconds" { - $testIntTimeSpan = 60; - $testTimeSpan = New-TimeSpan -Seconds $testIntTimeSpan; - - $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Seconds; - - $result | Should Be $testTimeSpan.TotalSeconds; - } - - It "Converts TimeSpan to total minutes" { - $testIntTimeSpan = 60; - $testTimeSpan = New-TimeSpan -Minutes $testIntTimeSpan; - - $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Minutes; - - $result | Should Be $testTimeSpan.TotalMinutes; - } - - It "Converts TimeSpan to total hours" { - $testIntTimeSpan = 60; - $testTimeSpan = New-TimeSpan -Hours $testIntTimeSpan; - - $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Hours; - - $result | Should Be $testTimeSpan.TotalHours; - } - - It "Converts TimeSpan to total days" { - $testIntTimeSpan = 60; - $testTimeSpan = New-TimeSpan -Days $testIntTimeSpan; - - $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Days; - - $result | Should Be $testTimeSpan.TotalDays; - } - - } - #endregion - - #region Function Get-ADCommonParameters - Describe "$($Global:DSCResourceName)\Get-ADCommonParameters" { - - It "Returns 'System.Collections.Hashtable' object type" { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity; - - $result -is [System.Collections.Hashtable] | Should Be $true; - } - - It "Returns 'Identity' key by default" { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity; - - $result['Identity'] | Should Be $testIdentity; - } - - It "Returns 'Name' key when 'UseNameParameter' is specified" { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity -UseNameParameter; - - $result['Name'] | Should Be $testIdentity; - } - - foreach ($identityParam in @('UserName','GroupName','ComputerName')) - { - It "Returns 'Identity' key when '$identityParam' alias is specified" { - $testIdentity = 'contoso.com'; - $getADCommonParameters = @{ - $identityParam = $testIdentity; - } - - $result = Get-ADCommonParameters @getADCommonParameters; - - $result['Identity'] | Should Be $testIdentity; - } - } - - It "Returns 'Identity' key by default when 'Identity' and 'CommonName' are specified" { - $testIdentity = 'contoso.com'; - $testCommonName = 'Test Common Name'; - - $result = Get-ADCommonParameters -Identity $testIdentity -CommonName $testCommonName; - - $result['Identity'] | Should Be $testIdentity; - } - - It "Returns 'Identity' key with 'CommonName' when 'Identity', 'CommonName' and 'PreferCommonName' are specified" { - $testIdentity = 'contoso.com'; - $testCommonName = 'Test Common Name'; - - $result = Get-ADCommonParameters -Identity $testIdentity -CommonName $testCommonName -PreferCommonName; - - $result['Identity'] | Should Be $testCommonName; - } - - It "Returns 'Identity' key with 'Identity' when 'Identity' and 'PreferCommonName' are specified" { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity -PreferCommonName; - - $result['Identity'] | Should Be $testIdentity; - } - - it "Returns 'Name' key when 'UseNameParameter' and 'PreferCommonName' are supplied" { - $testIdentity = 'contoso.com'; - $testCommonName = 'Test Common Name'; - - $result = Get-ADCommonParameters -Identity $testIdentity -UseNameParameter -CommonName $testCommonName -PreferCommonName; - - $result['Name'] | Should Be $testCommonName; - } - - It "Does not return 'Credential' key by default" { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity; - - $result.ContainsKey('Credential') | Should Be $false; - } - - It "Returns 'Credential' key when specified" { - $testIdentity = 'contoso.com'; - $testCredential = [System.Management.Automation.PSCredential]::Empty; - - $result = Get-ADCommonParameters -Identity $testIdentity -Credential $testCredential; - - $result['Credential'] | Should Be $testCredential; - } - - It "Does not return 'Server' key by default" { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity; - - $result.ContainsKey('Server') | Should Be $false; - } - - It "Returns 'Server' key when specified" { - $testIdentity = 'contoso.com'; - $testServer = 'testserver.contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity -Server $testServer; - - $result['Server'] | Should Be $testServer; - } - - It "Converts 'DomainAdministratorCredential' parameter to 'Credential' key" { - $testIdentity = 'contoso.com'; - $testCredential = [System.Management.Automation.PSCredential]::Empty; - - $result = Get-ADCommonParameters -Identity $testIdentity -DomainAdministratorCredential $testCredential; - - $result['Credential'] | Should Be $testCredential; - } - - It "Converts 'DomainController' parameter to 'Server' key" { - $testIdentity = 'contoso.com'; - $testServer = 'testserver.contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity -DomainController $testServer; - - $result['Server'] | Should Be $testServer; - } - - It 'Accepts remaining arguments' { - $testIdentity = 'contoso.com'; - - $result = Get-ADCommonParameters -Identity $testIdentity -UnexpectedParameter 42; - - $result['Identity'] | Should Be $testIdentity; - } - - } - #endregion - - #region Function ConvertTo-DeploymentForestMode - Describe "$($Global:DSCResourceName)\ConvertTo-DeploymentForestMode" { - It 'Converts an Microsoft.ActiveDirectory.Management.ForestMode to Microsoft.DirectoryServices.Deployment.Types.ForestMode' { - ConvertTo-DeploymentForestMode -Mode Windows2012Forest | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.ForestMode] - } - - It 'Converts an Microsoft.ActiveDirectory.Management.ForestMode to the correct Microsoft.DirectoryServices.Deployment.Types.ForestMode' { - ConvertTo-DeploymentForestMode -Mode Windows2012Forest | Should Be ([Microsoft.DirectoryServices.Deployment.Types.ForestMode]::Win2012) - } - - It 'Converts valid integer to Microsoft.DirectoryServices.Deployment.Types.ForestMode' { - ConvertTo-DeploymentForestMode -ModeId 5 | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.ForestMode] - } - - It 'Converts a valid integer to the correct Microsoft.DirectoryServices.Deployment.Types.ForestMode' { - ConvertTo-DeploymentForestMode -ModeId 5 | Should Be ([Microsoft.DirectoryServices.Deployment.Types.ForestMode]::Win2012) - } - - It 'Throws an exception when an invalid forest mode is selected' { - { ConvertTo-DeploymentForestMode -Mode Nonexistant } | Should Throw - } - - It 'Throws no exception when a null value is passed' { - { ConvertTo-DeploymentForestMode -Mode $null } | Should Not Throw - } - - It 'Throws no exception when an invalid mode id is selected' { - { ConvertTo-DeploymentForestMode -ModeId 666 } | Should Not Throw - } - - It 'Returns $null when a null value is passed' { - ConvertTo-DeploymentForestMode -Mode $null | Should Be $null - } - - It 'Returns $null when an invalid mode id is selected' { - ConvertTo-DeploymentForestMode -ModeId 666 | Should Be $null - } - } - #endregion - - #region Function ConvertTo-DeploymentDomainMode - Describe "$($Global:DSCResourceName)\ConvertTo-DeploymentDomainMode" { - It 'Converts an Microsoft.ActiveDirectory.Management.DomainMode to Microsoft.DirectoryServices.Deployment.Types.DomainMode' { - ConvertTo-DeploymentDomainMode -Mode Windows2012Domain | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.DomainMode] - } - - It 'Converts an Microsoft.ActiveDirectory.Management.DomainMode to the correct Microsoft.DirectoryServices.Deployment.Types.DomainMode' { - ConvertTo-DeploymentDomainMode -Mode Windows2012Domain | Should Be ([Microsoft.DirectoryServices.Deployment.Types.DomainMode]::Win2012) - } - - It 'Converts valid integer to Microsoft.DirectoryServices.Deployment.Types.DomainMode' { - ConvertTo-DeploymentDomainMode -ModeId 5 | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.DomainMode] - } - - It 'Converts a valid integer to the correct Microsoft.DirectoryServices.Deployment.Types.DomainMode' { - ConvertTo-DeploymentDomainMode -ModeId 5 | Should Be ([Microsoft.DirectoryServices.Deployment.Types.DomainMode]::Win2012) - } - - It 'Throws an exception when an invalid domain mode is selected' { - { ConvertTo-DeploymentDomainMode -Mode Nonexistant } | Should Throw - } - - It 'Throws no exception when a null value is passed' { - { ConvertTo-DeploymentDomainMode -Mode $null } | Should Not Throw - } - - It 'Throws no exception when an invalid mode id is selected' { - { ConvertTo-DeploymentDomainMode -ModeId 666 } | Should Not Throw - } - - It 'Returns $null when a null value is passed' { - ConvertTo-DeploymentDomainMode -Mode $null | Should Be $null - } - - It 'Returns $null when an invalid mode id is selected' { - ConvertTo-DeploymentDomainMode -ModeId 666 | Should Be $null - } - } - #endregion - - #region Function Restore-ADCommonObject - Describe "$($Global:DSCResourceName)\Restore-ADCommonObject" { - $getAdObjectReturnValue = @( - [PSCustomObject] @{ - Deleted = $true - DistinguishedName = 'CN=a375347\0ADEL:f0e3f4fe-212b-43e7-83dd-c8f3b47ebb9c,CN=Deleted Objects,DC=contoso,DC=com' - Name = 'a375347' - ObjectClass = 'user' - ObjectGUID = 'f0e3f4fe-212b-43e7-83dd-c8f3b47ebb9c' - # Make this one day older. - whenChanged = (Get-Date).AddDays(-1) - }, - [PSCustomObject] @{ - Deleted = $true - DistinguishedName = 'CN=a375347\0ADEL:d3c8b8c1-c42b-4533-af7d-3aa73ecd2216,CN=Deleted Objects,DC=contoso,DC=com' - Name = 'a375347' - ObjectClass = 'user' - ObjectGUID = 'd3c8b8c1-c42b-4533-af7d-3aa73ecd2216' - whenChanged = Get-Date - } - ) - - $restoreAdObjectReturnValue = [PSCustomObject]@{ - DistinguishedName = 'CN=a375347,CN=Accounts,DC=contoso,DC=com' - Name = 'a375347' - ObjectClass = 'user' - ObjectGUID = 'd3c8b8c1-c42b-4533-af7d-3aa73ecd2216' - } - - function Restore-ADObject - { - } - - $getAdCommonParameterReturnValue = @{Identity = 'something'} - $restoreIdentity = 'SomeObjectName' - $restoreObjectClass = 'user' - $restoreObjectWrongClass = 'wrong' - - Context 'When there are objects in the recycle bin' { - Mock -CommandName Get-ADObject -MockWith { return $getAdObjectReturnValue } -Verifiable - Mock -CommandName Get-ADCommonParameters -MockWith { return $getAdCommonParameterReturnValue } - Mock -CommandName Restore-ADObject -Verifiable - - It 'Should not throw when called with the correct parameters' { - {Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass} | Should -Not -Throw - } - - It 'Should return the correct restored object' { - Mock -CommandName Restore-ADObject -MockWith { return $restoreAdObjectReturnValue} - (Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass).ObjectClass | Should -Be 'user' - } - - It 'Should throw the correct error when invalid parameters are used' { - {Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectWrongClass} | Should -Throw "Cannot validate argument on parameter 'ObjectClass'" - } - - It 'Should call Get-ADObject as well as Restore-ADObject' { - Assert-VerifiableMock - } - - It 'Should throw an InvalidOperationException when object parent does not exist' { - Mock -CommandName Restore-ADObject -MockWith { throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADException)} - - {Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass} | Should -Throw -ExceptionType ([System.InvalidOperationException]) - } - } - - Context 'When there are no objects in the recycle bin' { - Mock -CommandName Get-ADObject - Mock -CommandName Get-ADCommonParameters -MockWith { return $getAdCommonParameterReturnValue} - Mock -CommandName Restore-ADObject - - It 'Should return $null' { - Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass | Should -Be $null - } - - It 'Should not call Restore-ADObject' { - Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass - Assert-MockCalled -CommandName Restore-ADObject -Exactly -Times 0 -Scope It - } - } - } - #endregion - - #region Get-ADDomainNameFromDistinguishedName - Describe "$($Global:DSCResourceName)\Get-ADDomainNameFromDistinguishedName" { - $validDistinguishedNames = @( - @{ - DN = 'CN=group1,OU=Group,OU=Wacken,DC=contoso,DC=com' - Domain = 'contoso.com' - } - @{ - DN = 'CN=group1,OU=Group,OU=Wacken,DC=sub,DC=contoso,DC=com' - Domain = 'sub.contoso.com' - } - @{ - DN = 'CN=group1,OU=Group,OU=Wacken,DC=child,DC=sub,DC=contoso,DC=com' - Domain = 'child.sub.contoso.com' - } - ) - $invalidDistinguishedNames = @( - 'Group1' - 'contoso\group1' - 'user1@contoso.com' - ) - - Context 'The distinguished name is valid' { - foreach ($name in $validDistinguishedNames) - { - It "Should match domain $($name.Domain)" { - Get-ADDomainNameFromDistinguishedName -DistinguishedName $name.Dn | Should -Be $name.Domain - } - } - } - - Context 'The distinguished name is invalid' { - foreach ($name in $invalidDistinguishedNames) - { - It "Should return `$null for $name" { - Get-ADDomainNameFromDistinguishedName -DistinguishedName $name | Should -Be $null - } - } - } - } - #endregion - - #region Add-AdCommonGroupMember - Describe "$($Global:DSCResourceName)\Add-ADCommonGroupMember" { - Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } - - $memberData = @( - [pscustomobject]@{ - Name = 'CN=Account1,DC=contoso,DC=com' - Domain = 'contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Group1,DC=contoso,DC=com' - Domain = 'contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Computer1,DC=contoso,DC=com' - Domain = 'contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Account1,DC=a,DC=contoso,DC=com' - Domain = 'a.contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Group1,DC=a,DC=contoso,DC=com' - Domain = 'a.contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Computer1,DC=a,DC=contoso,DC=com' - Domain = 'a.contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Account1,DC=b,DC=contoso,DC=com' - Domain = 'b.contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Group1,DC=b,DC=contoso,DC=com' - Domain = 'b.contoso.com' - } - [pscustomobject]@{ - Name = 'CN=Computer1,DC=b,DC=contoso,DC=com' - Domain = 'b.contoso.com' - } - ) - - $invalidMemberData = @( - 'contoso.com\group1' - 'user1@contoso.com' - 'computer1.contoso.com' - ) - - $fakeParameters = @{ - Identity = 'SomeGroup' - } - - Context 'When all members are in the same domain' { - Mock -CommandName Add-ADGroupMember - $groupCount = 0 - foreach ($domainGroup in ($memberData | Group-Object -Property Domain)) - { - $groupCount ++ - It 'Should not throw an error when calling Add-ADCommonGroupMember' { - Add-ADCommonGroupMember -Members $domainGroup.Group.Name -Parameters $fakeParameters - } - } - - It "Should have called Add-ADGroupMember $groupCount times" { - Assert-MockCalled -CommandName Add-ADGroupMember -Exactly -Times $groupCount - } - } - - Context 'When members are in different domains' { - Mock -CommandName Add-ADGroupMember - Mock -CommandName Get-ADObject -MockWith { - param ( - [Parameter()] - [string] - $Identity, - - [Parameter()] - [string] - $Server, - - [Parameter()] - [string[]] - $Properties - ) - - $objectClass = switch ($Identity) - { - {$Identity -match 'Group'} { 'group' } - {$Identity -match 'Account'} { 'user' } - {$Identity -match 'Computer'} { 'computer' } - } - - return ([PSCustomObject]@{ - objectClass = $objectClass - }) - } - # Mocks should return something that is used with Add-ADGroupMember - Mock -CommandName Get-ADComputer -MockWith { return 'placeholder' } - Mock -CommandName Get-ADGroup -MockWith { return 'placeholder' } - Mock -CommandName Get-ADUser -MockWith { return 'placeholder' } - - It 'Should not throw an error' { - {Add-ADCommonGroupMember -Members $memberData.Name -Parameters $fakeParameters -MembersInMultipleDomains} | Should -Not -Throw - } - - It 'Should have called all mocked cmdlets' { - Assert-MockCalled -CommandName Get-ADComputer -Exactly -Times $memberData.Where( {$_.Name -like '*Computer*'}).Count - Assert-MockCalled -CommandName Get-ADUser -Exactly -Times $memberData.Where( {$_.Name -like '*Account*'}).Count - Assert-MockCalled -CommandName Get-ADGroup -Exactly -Times $memberData.Where( {$_.Name -like '*Group*'}).Count - Assert-MockCalled -CommandName Add-ADGroupMember -Exactly -Times $memberData.Count - } - } - - Context 'When the domain name cannot be determined' { - It 'Should throw an InvalidArgumentException' { - {Add-ADCommonGroupMember -Members $invalidMemberData -Parameters $fakeParameters -MembersInMultipleDomains} | Should -Throw -ExceptionType ([System.ArgumentException]) - } - } - } - #endregion - - Describe "$($Global:DSCResourceName)\Get-DomainControllerObject" { - Context 'When domain name cannot be reached' { - BeforeAll { - Mock -CommandName Get-ADDomainController -MockWith { - throw New-Object -TypeName 'Microsoft.ActiveDirectory.Management.ADServerDownException' - } - } - - It 'Should throw the correct error' { - { Get-DomainControllerObject -DomainName 'contoso.com' -Verbose } | Should -Throw $localizedString.FailedEvaluatingDomainController - - Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It - } - } - - Context 'When current node is not a domain controller' { - BeforeAll { - Mock -CommandName Get-ADDomainController - Mock -CommandName Test-IsDomainController -MockWith { - return $false - } - } - - It 'Should return $null' { - $getDomainControllerObjectResult = Get-DomainControllerObject -DomainName 'contoso.com' -Verbose - $getDomainControllerObjectResult | Should -BeNullOrEmpty - - Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It - } - } - - Context 'When current node is not a domain controller, but operating system information says it should be' { - BeforeAll { - Mock -CommandName Get-ADDomainController - Mock -CommandName Test-IsDomainController -MockWith { - return $true - } - } - - It 'Should throw the correct error' { - { Get-DomainControllerObject -DomainName 'contoso.com' -Verbose } | Should -Throw $script:localizedData.WasExpectingDomainController - - Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It - } - } - - Context 'When current node is a domain controller' { - BeforeAll { - Mock -CommandName Get-ADDomainController -MockWith { - return @{ - Site = 'MySite' - Domain = 'contoso.com' - IsGlobalCatalog = $true - } - } - } - - It 'Should return the correct values for each property' { - $getDomainControllerObjectResult = Get-DomainControllerObject -DomainName 'contoso.com' -Verbose - - $getDomainControllerObjectResult.Site | Should -Be 'MySite' - $getDomainControllerObjectResult.Domain | Should -Be 'contoso.com' - $getDomainControllerObjectResult.IsGlobalCatalog | Should -BeTrue - - Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It - } - } - - Context 'When current node is a domain controller, and using specific credential' { - BeforeAll { - Mock -CommandName Get-ADDomainController -MockWith { - return @{ - Site = 'MySite' - Domain = 'contoso.com' - IsGlobalCatalog = $true - } - } - - $mockAdministratorUser = 'admin@contoso.com' - $mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12' | ConvertTo-SecureString -AsPlainText -Force - $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @($mockAdministratorUser, $mockAdministratorPassword) - } - - It 'Should return the correct values for each property' { - $getDomainControllerObjectResult = Get-DomainControllerObject -DomainName 'contoso.com' -Credential $mockAdministratorCredential -Verbose - - $getDomainControllerObjectResult.Site | Should -Be 'MySite' - $getDomainControllerObjectResult.Domain | Should -Be 'contoso.com' - $getDomainControllerObjectResult.IsGlobalCatalog | Should -BeTrue - - Assert-MockCalled -CommandName Get-ADDomainController -ParameterFilter { - $PSBoundParameters.ContainsKey('Credential') -eq $true - } -Exactly -Times 1 -Scope It - } - } - } - - Describe "$($Global:DSCResourceName)\Test-IsDomainController" { - Context 'When operating system information says the node is a domain controller' { - BeforeAll { - Mock -CommandName Get-CimInstance -MockWith { - return @{ - ProductType = 2 - } - } - } - - It 'Should return $true' { - $testIsDomainControllerResult = Test-IsDomainController - $testIsDomainControllerResult | Should -BeTrue - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It - } - } - - Context 'When operating system information says the node is not a domain controller' { - BeforeAll { - Mock -CommandName Get-CimInstance -MockWith { - return @{ - ProductType = 3 - } - } - } - - It 'Should return $false' { - $testIsDomainControllerResult = Test-IsDomainController - $testIsDomainControllerResult | Should -BeFalse - - Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It - } - } - } - - Describe "$($Global:DSCResourceName)\Convert-PropertyMapToObjectProperties" { - Context 'When a property map should be converted to object properties' { - BeforeAll { - $propertyMapValue = @( - @{ - ParameterName = 'ComputerName' - PropertyName = 'cn' - }, - @{ - ParameterName = 'Location' - } - ) - } - - It 'Should return the correct values' { - $convertPropertyMapToObjectPropertiesResult = Convert-PropertyMapToObjectProperties $propertyMapValue - $convertPropertyMapToObjectPropertiesResult | Should -HaveCount 2 - $convertPropertyMapToObjectPropertiesResult[0] | Should -Be 'cn' - $convertPropertyMapToObjectPropertiesResult[1] | Should -Be 'Location' - } - } - - Context 'When a property map contains a wrong type' { - BeforeAll { - $propertyMapValue = @( - @{ - ParameterName = 'ComputerName' - PropertyName = 'cn' - }, - 'Location' - ) - } - - It 'Should throw the correct error' { - { - Convert-PropertyMapToObjectProperties $propertyMapValue - } | Should -Throw $localizedString.PropertyMapArrayIsWrongType - } - } - } - - Describe 'DscResource.Common\Test-DscPropertyState' -Tag 'TestDscPropertyState' { - Context 'When comparing tables' { - It 'Should return true for two identical tables' { - $mockValues = @{ - CurrentValue = 'Test' - DesiredValue = 'Test' - } - - Test-DscPropertyState -Values $mockValues | Should -Be $true - } - } - - Context 'When comparing strings' { - It 'Should return false when a value is different for [System.String]' { - $mockValues = @{ - CurrentValue = [System.String] 'something' - DesiredValue = [System.String] 'test' - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - - It 'Should return false when a String value is missing' { - $mockValues = @{ - CurrentValue = $null - DesiredValue = [System.String] 'Something' - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - } - - Context 'When comparing integers' { - It 'Should return false when a value is different for [System.Int32]' { - $mockValues = @{ - CurrentValue = [System.Int32] 1 - DesiredValue = [System.Int32] 2 - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - - It 'Should return false when a value is different for [Int16]' { - $mockValues = @{ - CurrentValue = [System.Int16] 1 - DesiredValue = [System.Int16] 2 - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - - It 'Should return false when a value is different for [UInt16]' { - $mockValues = @{ - CurrentValue = [System.UInt16] 1 - DesiredValue = [System.UInt16] 2 - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - - It 'Should return false when a Integer value is missing' { - $mockValues = @{ - CurrentValue = $null - DesiredValue = [System.Int32] 1 - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - } - - Context 'When comparing booleans' { - It 'Should return false when a value is different for [Boolean]' { - $mockValues = @{ - CurrentValue = [System.Boolean] $true - DesiredValue = [System.Boolean] $false - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - - It 'Should return false when a Boolean value is missing' { - $mockValues = @{ - CurrentValue = $null - DesiredValue = [System.Boolean] $true - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - } - - Context 'When comparing arrays' { - It 'Should return true when evaluating an array' { - $mockValues = @{ - CurrentValue = @('1','2') - DesiredValue = @('1','2') - } - - Test-DscPropertyState -Values $mockValues | Should -Be $true - } - - It 'Should return false when evaluating an array with wrong values' { - $mockValues = @{ - CurrentValue = @('CurrentValueA','CurrentValueB') - DesiredValue = @('DesiredValue1','DesiredValue2') - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - - It 'Should return false when evaluating an array, but the current value is $null' { - $mockValues = @{ - CurrentValue = $null - DesiredValue = @('1','2') - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - } - } - - Context -Name 'When passing invalid types for DesiredValue' { - It 'Should write a warning when DesiredValue contain an unsupported type' { - Mock -CommandName Write-Warning -Verifiable - - # This is a dummy type to test with a type that could never be a correct one. - class MockUnknownType - { - [ValidateNotNullOrEmpty()] - [System.String] - $Property1 - - [ValidateNotNullOrEmpty()] - [System.String] - $Property2 - - MockUnknownType() - { - } - } - - $mockValues = @{ - CurrentValue = New-Object -TypeName MockUnknownType - DesiredValue = New-Object -TypeName MockUnknownType - } - - Test-DscPropertyState -Values $mockValues | Should -Be $false - - Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope It - } - } - - Assert-VerifiableMock - } - - Describe "$($Global:DSCResourceName)\Compare-ResourcePropertyState" { - Context 'When one property is in desired state' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 1 - $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult.Expected | Should -Be 'DC01' - $compareTargetResourceStateResult.Actual | Should -Be 'DC01' - $compareTargetResourceStateResult.InDesiredState | Should -BeTrue - } - } - - Context 'When two properties are in desired state' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - Location = 'Sweden' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - Location = 'Sweden' - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 2 - $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01' - $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01' - $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue - $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location' - $compareTargetResourceStateResult[1].Expected | Should -Be 'Sweden' - $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden' - $compareTargetResourceStateResult[1].InDesiredState | Should -BeTrue - } - } - - Context 'When passing just one property and that property is not in desired state' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - } - - $mockDesiredValues = @{ - ComputerName = 'APP01' - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 1 - $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult.Expected | Should -Be 'APP01' - $compareTargetResourceStateResult.Actual | Should -Be 'DC01' - $compareTargetResourceStateResult.InDesiredState | Should -BeFalse - } - } - - Context 'When passing two properties and one property is not in desired state' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - Location = 'Sweden' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - Location = 'Europe' - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 2 - $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01' - $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01' - $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue - $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location' - $compareTargetResourceStateResult[1].Expected | Should -Be 'Europe' - $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden' - $compareTargetResourceStateResult[1].InDesiredState | Should -BeFalse - } - } - - Context 'When passing a common parameter set to desired value' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - Verbose = $true - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 1 - $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult.Expected | Should -Be 'DC01' - $compareTargetResourceStateResult.Actual | Should -Be 'DC01' - $compareTargetResourceStateResult.InDesiredState | Should -BeTrue - } - } - - Context 'When using parameter Properties to compare desired values' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - Location = 'Sweden' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - Location = 'Europe' - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - Properties = @( - 'ComputerName' - ) - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 1 - $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult.Expected | Should -Be 'DC01' - $compareTargetResourceStateResult.Actual | Should -Be 'DC01' - $compareTargetResourceStateResult.InDesiredState | Should -BeTrue - } - } - - Context 'When using parameter Properties and IgnoreProperties to compare desired values' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - Location = 'Sweden' - Ensure = 'Present' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - Location = 'Europe' - Ensure = 'Absent' - } - } - - It 'Should return the correct values' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - IgnoreProperties = @( - 'Ensure' - ) - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -HaveCount 2 - $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName' - $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01' - $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01' - $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue - $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location' - $compareTargetResourceStateResult[1].Expected | Should -Be 'Europe' - $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden' - $compareTargetResourceStateResult[1].InDesiredState | Should -BeFalse - } - } - - Context 'When using parameter Properties and IgnoreProperties to compare desired values' { - BeforeAll { - $mockCurrentValues = @{ - ComputerName = 'DC01' - Location = 'Sweden' - Ensure = 'Present' - } - - $mockDesiredValues = @{ - ComputerName = 'DC01' - Location = 'Europe' - Ensure = 'Absent' - } - } - - It 'Should return and empty array' { - $compareTargetResourceStateParameters = @{ - CurrentValues = $mockCurrentValues - DesiredValues = $mockDesiredValues - Properties = @( - 'ComputerName' - ) - IgnoreProperties = @( - 'ComputerName' - ) - } - - $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters - $compareTargetResourceStateResult | Should -BeNullOrEmpty - } - } - } - #region Function Assert-ADPSDrive - Describe "$($Global:DSCResourceName)\Assert-ADPSDrive" { - Mock -CommandName Assert-Module - - Context 'When the AD PS Drive does not exist and the New-PSDrive function is successful' { - Mock -CommandName Get-PSDrive -MockWith { $null } - Mock -CommandName New-PSDrive - - It 'Should not throw' { - { Assert-ADPSDrive } | Should -Not -Throw - } - - It 'Should have called Assert-Module' { - Assert-MockCalled -CommandName Assert-Module -Exactly -Times 1 -Scope Context - } - - It 'Should have called Get-PSDrive only once' { - Assert-MockCalled -CommandName Get-PSDrive -Exactly -Times 1 -Scope Context - } - - It 'Should have called New-PSDrive only once' { - Assert-MockCalled -CommandName New-PSDrive -Exactly -Times 1 -Scope Context - } - } - - Context 'When the AD PS Drive does not exist and the New-PSDrive function is not successful' { - Mock -CommandName Get-PSDrive -MockWith { $null } - Mock -CommandName New-PSDrive -MockWith { throw } - - It 'Should throw the correct error' { - { Assert-ADPSDrive } | Should -Throw $script:localizedString.CreatingNewADPSDriveError - } - - It 'Should call Assert-Module' { - Assert-MockCalled -CommandName Assert-Module -Exactly -Times 1 -Scope Context - } - - It 'Should call Get-PSDrive once' { - Assert-MockCalled -CommandName Get-PSDrive -Exactly -Times 1 -Scope Context - } - - It 'Should call New-PSDrive once' { - Assert-MockCalled -CommandName New-PSDrive -Exactly -Times 1 -Scope Context - } - } - - Context 'When the AD PS Drive already exists' { - Mock -CommandName Get-PSDrive -MockWith { New-MockObject -Type System.Management.Automation.PSDriveInfo } - Mock -CommandName New-PSDrive - - It 'Should not throw' { - { Assert-ADPSDrive } | Should -Not -Throw - } - - It 'Should call Assert-Module only once' { - Assert-MockCalled -CommandName Assert-Module -Exactly -Times 1 -Scope Context - } - - It 'Should call Get-PSDrive only once' { - Assert-MockCalled -CommandName Get-PSDrive -Exactly -Times 1 -Scope Context - } - - It 'Should not call New-PSDrive' { - Assert-MockCalled -CommandName New-PSDrive -Exactly -Times 0 -Scope Context - } - } - } - #endregion - } - #endregion -} -finally -{ - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion -} diff --git a/Tests/Unit/MSFT_xADDomain.Tests.ps1 b/Tests/Unit/MSFT_xADDomain.Tests.ps1 index 54c742abd..86d3b0671 100644 --- a/Tests/Unit/MSFT_xADDomain.Tests.ps1 +++ b/Tests/Unit/MSFT_xADDomain.Tests.ps1 @@ -1,52 +1,55 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() -$Global:DSCModuleName = 'xActiveDirectory' # Example xNetworking -$Global:DSCResourceName = 'MSFT_xADDomain' # Example MSFT_xFirewall +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADDomain' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion -function Invoke-TestSetup { +#endregion HEADER + +function Invoke-TestSetup +{ # If one type does not exist, it's assumed the other ones does not exist either. if (-not ('Microsoft.DirectoryServices.Deployment.Types.ForestMode' -as [Type])) { - Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'Microsoft.DirectoryServices.Deployment.Types.cs') + Add-Type -Path (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Unit\Stubs\Microsoft.DirectoryServices.Deployment.Types.cs') } # If one type does not exist, it's assumed the other ones does not exist either. if (-not ('Microsoft.ActiveDirectory.Management.ADForestMode' -as [Type])) { - Add-Type -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path (Join-Path -Path $script:moduleRoot -ChildPath 'Tests') -ChildPath 'Unit') -ChildPath 'Stubs') -ChildPath 'Microsoft.ActiveDirectory.Management.cs') + Add-Type -Path (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Unit\Stubs\Microsoft.ActiveDirectory.Management.cs') } } +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} + # Begin Testing try { Invoke-TestSetup - #region Pester Tests - - # The InModuleScope command allows you to perform white-box unit testing on the internal - # (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { - - #region Pester Test Initialization - + InModuleScope $script:dscResourceName { $correctDomainName = 'present.com'; $incorrectDomainName = 'incorrect.com'; $missingDomainName = 'missing.com'; @@ -62,15 +65,12 @@ try SafemodeAdministratorPassword = $testAdminCredential; } - #endregion - #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADDomain\Get-TargetResource' { Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ADDSDeployment' } It 'Calls "Assert-Module" to check "ADDSDeployment" module is installed' { - Mock -CommandName Get-ADDomain -MockWith { + Mock -CommandName Get-ADDomain -MockWith { [psobject]@{ Forest = $correctDomainName DomainMode = $mgmtDomainMode @@ -100,7 +100,7 @@ try It 'Calls "Get-ADDomain" without credentials if domain member' { Mock -CommandName Test-DomainMember -MockWith { $true; } - Mock -CommandName Get-ADDomain -ParameterFilter { $Credential -eq $null } -MockWith { + Mock -CommandName Get-ADDomain -ParameterFilter { $Credential -eq $null } -MockWith { [psobject]@{ Forest = $correctDomainName DomainMode = $mgmtDomainMode @@ -114,7 +114,7 @@ try It 'Calls "Get-ADForest" without credentials if domain member' { Mock -CommandName Test-DomainMember -MockWith { $true; } - Mock -CommandName Get-ADDomain -ParameterFilter { $Credential -eq $null } -MockWith { + Mock -CommandName Get-ADDomain -ParameterFilter { $Credential -eq $null } -MockWith { [psobject]@{ Forest = $correctDomainName DomainMode = $mgmtDomainMode @@ -153,19 +153,19 @@ try } It 'Returns the correct domain mode' { - Mock -CommandName Get-ADDomain -MockWith { + Mock -CommandName Get-ADDomain -MockWith { [psobject]@{ Forest = $correctDomainName DomainMode = $mgmtDomainMode } } - Mock -CommandName Get-ADForest -MockWith { [psobject]@{ForestMode = $mgmtForestMode} } + Mock -CommandName Get-ADForest -MockWith { [psobject]@{ForestMode = $mgmtForestMode} } (Get-TargetResource @testDefaultParams -DomainName $correctDomainName).DomainMode | Should Be $domainMode } It 'Returns the correct forest mode' { - Mock -CommandName Get-ADDomain -MockWith { + Mock -CommandName Get-ADDomain -MockWith { [psobject]@{ Forest = $correctDomainName DomainMode = $mgmtDomainMode @@ -179,8 +179,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADDomain\Test-TargetResource' { $correctDomainName = 'present.com'; $correctChildDomainName = 'present'; $correctDomainNetBIOSName = 'PRESENT'; @@ -257,16 +256,18 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - - function Install-ADDSForest { + Describe 'xADDomain\Set-TargetResource' { + function Install-ADDSForest + { param ( $DomainName, $SafeModeAdministratorPassword, $CreateDnsDelegation, $DatabasePath, $DnsDelegationCredential, $InstallDns, $LogPath, $NoRebootOnCompletion, $SysvolPath, $DomainNetbiosName, $ForestMode, $DomainMode ) } - function Install-ADDSDomain { + + function Install-ADDSDomain + { param ( $NewDomainName, $ParentDomainName, $SafeModeAdministratorPassword, $CreateDnsDelegation, $Credential, $DatabasePath, $DnsDelegationCredential, $DomainType, $InstallDns, $LogPath, @@ -505,8 +506,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - diff --git a/Tests/Unit/MSFT_xADDomainDefaultPasswordPolicy.Tests.ps1 b/Tests/Unit/MSFT_xADDomainDefaultPasswordPolicy.Tests.ps1 index 7112af1ca..471947270 100644 --- a/Tests/Unit/MSFT_xADDomainDefaultPasswordPolicy.Tests.ps1 +++ b/Tests/Unit/MSFT_xADDomainDefaultPasswordPolicy.Tests.ps1 @@ -1,38 +1,44 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() -$Global:DSCModuleName = 'xActiveDirectory' # Example xNetworking -$Global:DSCResourceName = 'MSFT_xADDomainDefaultPasswordPolicy' # Example MSFT_xFirewall +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADDomainDefaultPasswordPolicy' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion +#endregion HEADER -# Begin Testing -try +function Invoke-TestSetup { +} - #region Pester Tests - - # The InModuleScope command allows you to perform white-box unit testing on the internal - # (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} - #region Pester Test Initialization +# Begin Testing +try +{ + Invoke-TestSetup + InModuleScope $script:dscResourceName { $testDomainName = 'contoso.com'; $testDefaultParams = @{ DomainName = $testDomainName; @@ -53,11 +59,8 @@ try ReversibleEncryptionEnabled = $false; } - #endregion - #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADDomainDefaultPasswordPolicy\Get-TargetResource' { Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } It 'Calls "Assert-Module" to check "ActiveDirectory" module is installed' { @@ -112,8 +115,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADDomainDefaultPasswordPolicy\Test-TargetResource' { $testDomainName = 'contoso.com'; $testDefaultParams = @{ DomainName = $testDomainName; @@ -197,8 +199,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADDomainDefaultPasswordPolicy\Set-TargetResource' { $testDomainName = 'contoso.com'; $testDefaultParams = @{ DomainName = $testDomainName; @@ -283,8 +284,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - diff --git a/Tests/Unit/MSFT_xADForestProperties.Tests.ps1 b/Tests/Unit/MSFT_xADForestProperties.Tests.ps1 index 70af05601..0aa2abedf 100644 --- a/Tests/Unit/MSFT_xADForestProperties.Tests.ps1 +++ b/Tests/Unit/MSFT_xADForestProperties.Tests.ps1 @@ -1,23 +1,41 @@ -$script:DSCModuleName = 'xActiveDirectory' -$script:DSCResourceName = 'MSFT_xADForestProperties' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADForestProperties' +#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')) + & 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 ` + -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 { - InModuleScope $script:DSCResourceName { + Invoke-TestSetup + InModuleScope $script:dscResourceName { $forestName = 'contoso.com' $testCredential = [System.Management.Automation.PSCredential]::Empty @@ -175,5 +193,5 @@ try } finally { - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xADGroup.Tests.ps1 b/Tests/Unit/MSFT_xADGroup.Tests.ps1 index a8c1ed9ab..8428a6c49 100644 --- a/Tests/Unit/MSFT_xADGroup.Tests.ps1 +++ b/Tests/Unit/MSFT_xADGroup.Tests.ps1 @@ -1,35 +1,44 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() -$Global:DSCModuleName = 'xActiveDirectory' # Example xNetworking -$Global:DSCResourceName = 'MSFT_xADGroup' # Example MSFT_xFirewall +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADGroup' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion + +#endregion HEADER + +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} # Begin Testing try { - #region Pester Tests - - # The InModuleScope command allows you to perform white-box unit testing on the internal - # (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { + Invoke-TestSetup - #region Pester Test Initialization + InModuleScope $script:dscResourceName { $testPresentParams = @{ GroupName = 'TestGroup' GroupScope = 'Global'; @@ -44,8 +53,8 @@ try $testAbsentParams = $testPresentParams.Clone(); $testAbsentParams['Ensure'] = 'Absent'; - $testPresentParamsMultidomain = $testPresentParams.Clone() - $testPresentParamsMultidomain.MembershipAttribute = 'DistinguishedName' + $testPresentParamsMultiDomain = $testPresentParams.Clone() + $testPresentParamsMultiDomain.MembershipAttribute = 'DistinguishedName' $fakeADGroup = @{ Name = $testPresentParams.GroupName; @@ -88,8 +97,7 @@ try $testCredentials = New-Object System.Management.Automation.PSCredential 'DummyUser', (ConvertTo-SecureString 'DummyPassword' -AsPlainText -Force); #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADGroup\Get-TargetResource' { Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } It 'Calls "Assert-Module" to check AD module is installed' { @@ -155,12 +163,11 @@ try #end region #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADGroup\Test-TargetResource' { Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } - foreach ($attribute in @('SamAccountName','DistinguishedName','ObjectGUID','SID')) { - + foreach ($attribute in @('SamAccountName','DistinguishedName','ObjectGUID','SID')) + { It "Passes when group 'Members' match using '$attribute'" { Mock -CommandName Get-ADGroup { return $fakeADGroup; } Mock -CommandName Get-ADGroupMember { return @($fakeADUser1, $fakeADUser2); } @@ -324,8 +331,7 @@ try #end region #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADGroup\Set-TargetResource' { Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } It "Calls 'New-ADGroup' when 'Ensure' is 'Present' and the group does not exist" { @@ -344,7 +350,8 @@ try DisplayName = 'Test DisplayName'; } - foreach ($property in $testProperties.Keys) { + foreach ($property in $testProperties.Keys) + { It "Calls 'Set-ADGroup' when 'Ensure' is 'Present' and '$property' is specified" { Mock -CommandName Set-ADGroup Mock -CommandName Get-ADGroupMember @@ -422,7 +429,7 @@ try Mock -CommandName Get-ADDomainNameFromDistinguishedName -MockWith { return 'contoso.com' } Mock -CommandName Write-Verbose -ParameterFilter { $Message -and $Message -match 'Group membership objects are in .* different AD Domains.'} - Set-TargetResource @testPresentParamsMultidomain -Members @($fakeADUser1.distinguishedName, $fakeADUser2.distinguishedName); + Set-TargetResource @testPresentParamsMultiDomain -Members @($fakeADUser1.distinguishedName, $fakeADUser2.distinguishedName); Assert-MockCalled -CommandName Get-ADDomainNameFromDistinguishedName Assert-MockCalled -CommandName Add-ADCommonGroupMember -Scope It @@ -453,7 +460,7 @@ try } Mock -CommandName Write-Verbose -ParameterFilter { $Message -and $Message -match 'Group membership objects are in .* different AD Domains.'} - Set-TargetResource @testPresentParamsMultidomain -Members @($fakeADUser1.distinguishedName, $fakeADUser4.distinguishedName); + Set-TargetResource @testPresentParamsMultiDomain -Members @($fakeADUser1.distinguishedName, $fakeADUser4.distinguishedName); Assert-MockCalled -CommandName Get-ADDomainNameFromDistinguishedName Assert-MockCalled -CommandName Add-ADCommonGroupMember -Scope It @@ -691,10 +698,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion - - # TODO: Other Optional Cleanup Code Goes Here... + Invoke-TestCleanup } - diff --git a/Tests/Unit/MSFT_xADKDSKey.Tests.ps1 b/Tests/Unit/MSFT_xADKDSKey.Tests.ps1 index 266ec9eb1..16edf0c86 100644 --- a/Tests/Unit/MSFT_xADKDSKey.Tests.ps1 +++ b/Tests/Unit/MSFT_xADKDSKey.Tests.ps1 @@ -1,24 +1,26 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADKDSKey' + #region HEADER -$script:DSCModuleName = 'xActiveDirectory' -$script:DSCResourceName = 'MSFT_xADKDSKey' # 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'))) ) + (-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 ` +$TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` -DSCResourceName $script:dscResourceName ` -ResourceType 'Mof' ` -TestType Unit + #endregion HEADER function Invoke-TestSetup @@ -30,13 +32,12 @@ function Invoke-TestCleanup Restore-TestEnvironment -TestEnvironment $TestEnvironment } - # Begin Testing try { Invoke-TestSetup - InModuleScope $script:DSCResourceName { + InModuleScope $script:dscResourceName { # Need to do a deep copy of the Array of objects that compare returns function Copy-ArrayObjects { diff --git a/Tests/Unit/MSFT_xADManagedServiceAccount.Tests.ps1 b/Tests/Unit/MSFT_xADManagedServiceAccount.Tests.ps1 index 2943722bd..55d0595c1 100644 --- a/Tests/Unit/MSFT_xADManagedServiceAccount.Tests.ps1 +++ b/Tests/Unit/MSFT_xADManagedServiceAccount.Tests.ps1 @@ -1,29 +1,30 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADManagedServiceAccount' + #region HEADER -$script:DSCModuleName = 'xActiveDirectory' -$script:DSCResourceName = 'MSFT_xADManagedServiceAccount' # 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'))) ) + (-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 ` +$TestEnvironment = Initialize-TestEnvironment ` -DSCModuleName $script:dscModuleName ` -DSCResourceName $script:dscResourceName ` -ResourceType 'Mof' ` -TestType Unit + #endregion HEADER function Invoke-TestSetup { - } function Invoke-TestCleanup @@ -31,13 +32,12 @@ function Invoke-TestCleanup Restore-TestEnvironment -TestEnvironment $TestEnvironment } - # Begin Testing try { Invoke-TestSetup - InModuleScope $script:DSCResourceName { + InModuleScope $script:dscResourceName { # Need to do a deep copy of the Array of objects that compare returns function Copy-ArrayObjects { diff --git a/Tests/Unit/MSFT_xADObjectPermissionEntry.Tests.ps1 b/Tests/Unit/MSFT_xADObjectPermissionEntry.Tests.ps1 index 5cef998f8..63b313065 100644 --- a/Tests/Unit/MSFT_xADObjectPermissionEntry.Tests.ps1 +++ b/Tests/Unit/MSFT_xADObjectPermissionEntry.Tests.ps1 @@ -1,25 +1,32 @@ -$Global:DSCModuleName = 'xActiveDirectory' -$Global:DSCResourceName = 'MSFT_xADObjectPermissionEntry' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADObjectPermissionEntry' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion -function Invoke-TestSetup { } +#endregion HEADER -function Invoke-TestCleanup { +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ Restore-TestEnvironment -TestEnvironment $TestEnvironment } @@ -28,8 +35,7 @@ try { Invoke-TestSetup - InModuleScope $Global:DSCResourceName { - + InModuleScope $script:dscResourceName { #region Pester Test Initialization $testDefaultParameters = @{ Path = 'CN=PC01,CN=Computers,DC=contoso,DC=com' @@ -39,14 +45,17 @@ try ActiveDirectorySecurityInheritance = 'None' InheritedObjectType = '00000000-0000-0000-0000-000000000000' } + $testPresentParameters = @{ Ensure = 'Present' ActiveDirectoryRights = 'GenericAll' } + $testAbsentParameters = @{ Ensure = 'Absent' ActiveDirectoryRights = 'GenericAll' } + $mockGetAclPresent = { $mock = [PSCustomObject] @{ Path = 'AD:CN=PC01,CN=Computers,DC=contoso,DC=com' @@ -70,6 +79,7 @@ try $mock | Add-Member -MemberType 'ScriptMethod' -Name 'RemoveAccessRule' -Value {} return $mock } + $mockGetAclAbsent = { $mock = [PSCustomObject] @{ Path = 'AD:CN=PC,CN=Computers,DC=lab,DC=local' @@ -83,8 +93,7 @@ try #endregion #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADObjectPermissionEntry\Get-TargetResource' { Mock -CommandName 'Assert-ADPSDrive' -MockWith { } Context 'When the desired ace is present' { @@ -148,8 +157,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADObjectPermissionEntry\Test-TargetResource' { Mock -CommandName 'Assert-ADPSDrive' { } Context 'When the desired ace is present' { @@ -205,8 +213,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADObjectPermissionEntry\Set-TargetResource' { Mock -CommandName 'Assert-ADPSDrive' -MockWith { } Context 'When the desired ace is present' { diff --git a/Tests/Unit/MSFT_xADOrganizationalUnit.Tests.ps1 b/Tests/Unit/MSFT_xADOrganizationalUnit.Tests.ps1 index 12169e9e6..31486a75b 100644 --- a/Tests/Unit/MSFT_xADOrganizationalUnit.Tests.ps1 +++ b/Tests/Unit/MSFT_xADOrganizationalUnit.Tests.ps1 @@ -1,35 +1,44 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() -$Global:DSCModuleName = 'xActiveDirectory' # Example xNetworking -$Global:DSCResourceName = 'MSFT_xADOrganizationalUnit' # Example MSFT_xFirewall +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADOrganizationalUnit' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion -# Begin Testing -try +#endregion HEADER + +function Invoke-TestSetup { +} - #region Pester Tests +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} - # The InModuleScope command allows you to perform white-box unit testing on the internal - # (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { +# Begin Testing +try +{ + Invoke-TestSetup + InModuleScope $script:dscResourceName { function Get-ADOrganizationalUnit { param ($Name) } function Set-ADOrganizationalUnit { param ($Identity, $Credential) } function Remove-ADOrganizationalUnit { param ($Name, $Credential) } @@ -54,8 +63,7 @@ try } #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADOrganizationalUnit\Get-TargetResource' { It 'Returns a "System.Collections.Hashtable" object type' { Mock -CommandName Assert-Module Mock -CommandName Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu } @@ -117,8 +125,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADOrganizationalUnit\Test-TargetResource' { It 'Returns a "System.Boolean" object type' { Mock -CommandName Assert-Module Mock -CommandName Get-ADOrganizationalUnit -MockWith { return [PSCustomObject] $protectedFakeAdOu } @@ -186,8 +193,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADOrganizationalUnit\Set-TargetResource' { It 'Calls "New-ADOrganizationalUnit" when "Ensure" = "Present" and OU does not exist' { Mock -CommandName Assert-Module Mock -CommandName Get-ADOrganizationalUnit @@ -318,14 +324,10 @@ try } } #endregion - } #endregion } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - diff --git a/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 b/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 index ec4a538bd..13425c32c 100644 --- a/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 +++ b/Tests/Unit/MSFT_xADRecycleBin.Tests.ps1 @@ -1,23 +1,41 @@ -$script:DSCModuleName = 'xActiveDirectory' -$script:DSCResourceName = 'MSFT_xADRecycleBin' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADRecycleBin' +#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')) + & 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 ` + -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 { - InModuleScope $script:DSCResourceName { + Invoke-TestSetup + InModuleScope $script:dscResourceName { $forestFQDN = 'contoso.com' $forestFunctionality = 'Windows2016Forest' $configurationNamingContext = 'CN=Configuration,DC=contoso,DC=com' @@ -216,5 +234,5 @@ try } finally { - Restore-TestEnvironment -TestEnvironment $TestEnvironment + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xADReplicationSite.Tests.ps1 b/Tests/Unit/MSFT_xADReplicationSite.Tests.ps1 index da65b8ef2..48a85b2bc 100644 --- a/Tests/Unit/MSFT_xADReplicationSite.Tests.ps1 +++ b/Tests/Unit/MSFT_xADReplicationSite.Tests.ps1 @@ -1,30 +1,41 @@ -$Global:DSCModuleName = 'xActiveDirectory' -$Global:DSCResourceName = 'MSFT_xADReplicationSite' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADReplicationSite' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion + +#endregion HEADER + +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} # Begin Testing try { - #region Pester Tests - - # The InModuleScope command allows you to perform white-box unit testing on - # the internal (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { + Invoke-TestSetup + InModuleScope $script:dscResourceName { #region Pester Test Initialization $presentSiteName = 'DemoSite' $absentSiteName = 'MissingSite' @@ -65,8 +76,7 @@ try # #endregion #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADReplicationSite\Get-TargetResource' { It 'Should return a "System.Collections.Hashtable" object type' { # Arrange @@ -108,8 +118,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADReplicationSite\Test-TargetResource' { It 'Should return a "System.Boolean" object type' { # Arrange @@ -174,8 +183,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADReplicationSite\Set-TargetResource' { It 'Should add a new site' { # Arrange @@ -238,8 +246,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - diff --git a/Tests/Unit/MSFT_xADReplicationSiteLink.tests.ps1 b/Tests/Unit/MSFT_xADReplicationSiteLink.tests.ps1 index def86f586..eb1181377 100644 --- a/Tests/Unit/MSFT_xADReplicationSiteLink.tests.ps1 +++ b/Tests/Unit/MSFT_xADReplicationSiteLink.tests.ps1 @@ -1,25 +1,30 @@ -$script:DSCModuleName = 'xActiveDirectory' -$script:DSCResourceName = 'MSFT_xADReplicationSiteLink' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADReplicationSiteLink' #region HEADER -# Unit Test Template Version: 1.2.1 +# 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')))) +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')) + & 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 ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit #endregion HEADER +function Invoke-TestSetup +{ +} + function Invoke-TestCleanup { Restore-TestEnvironment -TestEnvironment $TestEnvironment @@ -28,7 +33,9 @@ function Invoke-TestCleanup # Begin Testing try { - InModuleScope $script:DSCResourceName { + Invoke-TestSetup + + InModuleScope $script:dscResourceName { $mockGetADReplicationSiteLinkReturn = @{ Name = 'HQSiteLink' Cost = 100 diff --git a/Tests/Unit/MSFT_xADReplicationSubnet.Tests.ps1 b/Tests/Unit/MSFT_xADReplicationSubnet.Tests.ps1 index e1d7ee421..cdb10f1b3 100644 --- a/Tests/Unit/MSFT_xADReplicationSubnet.Tests.ps1 +++ b/Tests/Unit/MSFT_xADReplicationSubnet.Tests.ps1 @@ -1,32 +1,43 @@ -$Global:DSCModuleName = 'xActiveDirectory' -$Global:DSCResourceName = 'MSFT_xADReplicationSubnet' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADReplicationSubnet' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion +#endregion HEADER + +function Invoke-TestSetup +{ +} + +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} # Begin Testing try { - #region Pester Tests - InModuleScope $Global:DSCResourceName { + Invoke-TestSetup + InModuleScope $script:dscResourceName { #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADReplicationSubnet\Get-TargetResource' { $testDefaultParameters = @{ Name = '10.0.0.0/8' Site = 'Default-First-Site-Name' @@ -97,7 +108,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { + Describe 'xADReplicationSubnet\Test-TargetResource' { $testDefaultParameters = @{ Name = '10.0.0.0/8' @@ -164,8 +175,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADReplicationSubnet\Set-TargetResource' { $testPresentParameters = @{ Ensure = 'Present' Name = '10.0.0.0/8' @@ -269,8 +279,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - diff --git a/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 b/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 index 4d1937d26..b0f81ea45 100644 --- a/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 +++ b/Tests/Unit/MSFT_xADServicePrincipalName.Tests.ps1 @@ -1,34 +1,43 @@ -$Global:DSCModuleName = 'xActiveDirectory' -$Global:DSCResourceName = 'MSFT_xADServicePrincipalName' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADServicePrincipalName' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -Write-Host $moduleRoot -ForegroundColor Green; -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion +#endregion HEADER -# Begin Testing -try +function Invoke-TestSetup { +} - #region Pester Tests +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} - InModuleScope $Global:DSCResourceName { +# Begin Testing +try +{ + Invoke-TestSetup + InModuleScope $script:dscResourceName { #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { - + Describe 'xADServicePrincipalName\Get-TargetResource' { $testDefaultParameters = @{ ServicePrincipalName = 'HOST/demo' } @@ -83,8 +92,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { - + Describe 'xADServicePrincipalName\Test-TargetResource' { $testDefaultParameters = @{ ServicePrincipalName = 'HOST/demo' Account = 'User' @@ -169,8 +177,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { - + Describe 'xADServicePrincipalName\Set-TargetResource' { $testPresentParams = @{ Ensure = 'Present' ServicePrincipalName = 'HOST/demo' @@ -248,7 +255,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } diff --git a/Tests/Unit/MSFT_xADUser.Tests.ps1 b/Tests/Unit/MSFT_xADUser.Tests.ps1 index 695ca9208..6953b0343 100644 --- a/Tests/Unit/MSFT_xADUser.Tests.ps1 +++ b/Tests/Unit/MSFT_xADUser.Tests.ps1 @@ -1,28 +1,41 @@ -$Global:DSCModuleName = 'xActiveDirectory' -$Global:DSCResourceName = 'MSFT_xADUser' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xADUser' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -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 { - #region Pester Tests + Invoke-TestSetup - InModuleScope $Global:DSCResourceName { + InModuleScope $script:dscResourceName { $testPresentParams = @{ DomainName = 'contoso.com' UserName = 'TestUser' @@ -54,8 +67,9 @@ try ) $testBooleanProperties = @('PasswordNeverExpires', 'CannotChangePassword', 'TrustedForDelegation', 'Enabled'); $testArrayProperties = @('ServicePrincipalNames') + #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { + Describe 'xADUser\Get-TargetResource' { It "Returns a 'System.Collections.Hashtable' object type" { Mock -CommandName Get-ADUser -MockWith { return [PSCustomObject] $fakeADUser } @@ -105,7 +119,7 @@ try #endregion #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { + Describe 'xADUser\Test-TargetResource' { It "Passes when user account does not exist and 'Ensure' is 'Absent'" { Mock -CommandName Get-TargetResource -MockWith { return $testAbsentParams } @@ -380,7 +394,7 @@ try #endregion #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { + Describe 'xADUser\Set-TargetResource' { It "Calls 'New-ADUser' when 'Ensure' is 'Present' and the account does not exist" { $newUserName = 'NewUser' $newAbsentParams = $testAbsentParams.Clone() @@ -618,7 +632,7 @@ try #endregion #region Function Assert-TargetResource - Describe "$($Global:DSCResourceName)\Assert-Parameters" { + Describe 'xADUser\Assert-Parameters' { It "Does not throw when 'PasswordNeverExpires' and 'CannotChangePassword' are specified" { { Assert-Parameters -PasswordNeverExpires $true -CannotChangePassword $true } | Should Not Throw } @@ -633,13 +647,8 @@ try } #endregion } - #endregion } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - - diff --git a/Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1 b/Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1 index 4c8119900..df501463e 100644 --- a/Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1 +++ b/Tests/Unit/MSFT_xWaitForADDomain.Tests.ps1 @@ -1,35 +1,44 @@ [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] param() -$Global:DSCModuleName = 'xActiveDirectory' -$Global:DSCResourceName = 'MSFT_xWaitForADDomain' +$script:dscModuleName = 'xActiveDirectory' +$script:dscResourceName = 'MSFT_xWaitForADDomain' #region HEADER -[String] $moduleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $Script:MyInvocation.MyCommand.Path)) -if ( (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests'))) -or ` - (-not (Test-Path -Path (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1'))) ) + +# 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 $moduleRoot -ChildPath '\DSCResource.Tests\')) + & git @('clone', 'https://github.com/PowerShell/DscResource.Tests.git', (Join-Path -Path $script:moduleRoot -ChildPath 'DscResource.Tests')) } -Import-Module (Join-Path -Path $moduleRoot -ChildPath 'DSCResource.Tests\TestHelper.psm1') -Force +Import-Module -Name (Join-Path -Path $script:moduleRoot -ChildPath (Join-Path -Path 'DSCResource.Tests' -ChildPath 'TestHelper.psm1')) -Force + $TestEnvironment = Initialize-TestEnvironment ` - -DSCModuleName $Global:DSCModuleName ` - -DSCResourceName $Global:DSCResourceName ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Mof' ` -TestType Unit -#endregion -# Begin Testing -try +#endregion HEADER + +function Invoke-TestSetup { +} - #region Pester Tests +function Invoke-TestCleanup +{ + Restore-TestEnvironment -TestEnvironment $TestEnvironment +} - # The InModuleScope command allows you to perform white-box unit testing on the internal - # (non-exported) code of a Script Module. - InModuleScope $Global:DSCResourceName { +# Begin Testing +try +{ + Invoke-TestSetup - #region Pester Test Initialization + InModuleScope $script:dscResourceName { $password = 'Password' | ConvertTo-SecureString -AsPlainText -Force $DomainUserCredential = New-Object pscredential('Username', $password) $domainName = 'example.com' @@ -49,11 +58,9 @@ try } $fakeDomainObject = @{Name = $domainName} - #endregion - #region Function Get-TargetResource - Describe "$($Global:DSCResourceName)\Get-TargetResource" { + Describe 'xWaitForADDomain\Get-TargetResource' { It 'Returns a "System.Collections.Hashtable" object type' { Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject} $targetResource = Get-TargetResource @testParams @@ -76,7 +83,7 @@ try #region Function Test-TargetResource - Describe "$($Global:DSCResourceName)\Test-TargetResource" { + Describe 'xWaitForADDomain\Test-TargetResource' { It 'Returns a "System.Boolean" object type' { Mock -CommandName Get-Domain -MockWith {return $fakeDomainObject} $targetResource = Test-TargetResource @testParams @@ -97,7 +104,7 @@ try #region Function Set-TargetResource - Describe "$($Global:DSCResourceName)\Set-TargetResource" { + Describe 'xWaitForADDomain\Set-TargetResource' { BeforeEach{ $global:DSCMachineStatus = $null } @@ -169,8 +176,5 @@ try } finally { - #region FOOTER - Restore-TestEnvironment -TestEnvironment $TestEnvironment - #endregion + Invoke-TestCleanup } - diff --git a/Tests/Unit/xActiveDirectory.Common.Tests.ps1 b/Tests/Unit/xActiveDirectory.Common.Tests.ps1 index b6ae6f135..376a85b74 100644 --- a/Tests/Unit/xActiveDirectory.Common.Tests.ps1 +++ b/Tests/Unit/xActiveDirectory.Common.Tests.ps1 @@ -4,9 +4,21 @@ $script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPat Import-Module -Name (Join-Path -Path $script:modulesFolderPath -ChildPath 'xActiveDirectory.Common.psm1') -Force +# If one type does not exist, it's assumed the other ones does not exist either. +if (-not ('Microsoft.DirectoryServices.Deployment.Types.ForestMode' -as [Type])) +{ + Add-Type -Path (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Unit\Stubs\Microsoft.DirectoryServices.Deployment.Types.cs') +} + +# If one type does not exist, it's assumed the other ones does not exist either. +if (-not ('Microsoft.ActiveDirectory.Management.ADForestMode' -as [Type])) +{ + Add-Type -Path (Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Unit\Stubs\Microsoft.ActiveDirectory.Management.cs') +} + InModuleScope 'xActiveDirectory.Common' { Describe 'xActiveDirectory.Common\Test-DscParameterState' -Tag TestDscParameterState { - Context -Name 'When passing values' -Fixture { + Context 'When passing values' { It 'Should return true for two identical tables' { $mockDesiredValues = @{ Example = 'test' } @@ -91,8 +103,15 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return true when only a specified value matches, but other non-listed values do not' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } - $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } + $mockCurrentValues = @{ + Example = 'test' + SecondExample = 'true' + } + + $mockDesiredValues = @{ + Example = 'test' + SecondExample = 'false' + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -104,8 +123,15 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return false when only specified values do not match, but other non-listed values do ' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = 'true' } - $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } + $mockCurrentValues = @{ + Example = 'test' + SecondExample = 'true' + } + + $mockDesiredValues = @{ + Example = 'test' + SecondExample = 'false' + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -118,7 +144,10 @@ InModuleScope 'xActiveDirectory.Common' { It 'Should return false when an empty hash table is used in the current values' { $mockCurrentValues = @{ } - $mockDesiredValues = @{ Example = 'test'; SecondExample = 'false' } + $mockDesiredValues = @{ + Example = 'test' + SecondExample = 'false' + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -129,7 +158,10 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return true when evaluating a table against a CimInstance' { - $mockCurrentValues = @{ Handle = '0'; ProcessId = '1000' } + $mockCurrentValues = @{ + Handle = '0' + ProcessId = '1000' + } $mockWin32ProcessProperties = @{ Handle = 0 @@ -155,7 +187,10 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return false when evaluating a table against a CimInstance and a value is wrong' { - $mockCurrentValues = @{ Handle = '1'; ProcessId = '1000' } + $mockCurrentValues = @{ + Handle = '1' + ProcessId = '1000' + } $mockWin32ProcessProperties = @{ Handle = 0 @@ -181,8 +216,15 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return true when evaluating a hash table containing an array' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = @('1', '2') } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + $mockCurrentValues = @{ + Example = 'test' + SecondExample = @('1', '2') + } + + $mockDesiredValues = @{ + Example = 'test' + SecondExample = @('1', '2') + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -193,8 +235,15 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return false when evaluating a hash table containing an array with wrong values' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = @('A', 'B') } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + $mockCurrentValues = @{ + Example = 'test' + SecondExample = @('A', 'B') + } + + $mockDesiredValues = @{ + Example = 'test' + SecondExample = @('1', '2') + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -205,8 +254,14 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return false when evaluating a hash table containing an array, but the CurrentValues are missing an array' { - $mockCurrentValues = @{ Example = 'test' } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + $mockCurrentValues = @{ + Example = 'test' + } + + $mockDesiredValues = @{ + Example = 'test' + SecondExample = @('1', '2') + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -217,8 +272,15 @@ InModuleScope 'xActiveDirectory.Common' { } It 'Should return false when evaluating a hash table containing an array, but the property i CurrentValues is $null' { - $mockCurrentValues = @{ Example = 'test'; SecondExample = $null } - $mockDesiredValues = @{ Example = 'test'; SecondExample = @('1', '2') } + $mockCurrentValues = @{ + Example = 'test' + SecondExample = $null + } + + $mockDesiredValues = @{ + Example = 'test' + SecondExample = @('1', '2') + } $testParameters = @{ CurrentValues = $mockCurrentValues @@ -229,7 +291,7 @@ InModuleScope 'xActiveDirectory.Common' { } } - Context -Name 'When passing invalid types for DesiredValues' -Fixture { + Context 'When passing invalid types for DesiredValues' { It 'Should throw the correct error when DesiredValues is of wrong type' { $mockCurrentValues = @{ Example = 'something' } $mockDesiredValues = 'NotHashTable' @@ -276,7 +338,7 @@ InModuleScope 'xActiveDirectory.Common' { } } - Context -Name 'When passing an CimInstance as DesiredValue and ValuesToCheck is $null' -Fixture { + Context 'When passing an CimInstance as DesiredValue and ValuesToCheck is $null' { It 'Should throw the correct error' { $mockCurrentValues = @{ Example = 'something' } @@ -512,5 +574,1544 @@ InModuleScope 'xActiveDirectory.Common' { } } } -} + Describe 'xActiveDirectory.Common\Resolve-DomainFQDN' { + It 'Returns "DomainName" when "ParentDomainName" not supplied' { + $testDomainName = 'contoso.com' + $testParentDomainName = $null + + $result = Resolve-DomainFQDN -DomainName $testDomainName -ParentDomainName $testParentDomainName + + $result | Should Be $testDomainName + } + + It 'Returns compound "DomainName.ParentDomainName" when "ParentDomainName" supplied' { + $testDomainName = 'subdomain' + $testParentDomainName = 'contoso.com' + + $result = Resolve-DomainFQDN -DomainName $testDomainName -ParentDomainName $testParentDomainName + + $result | Should Be ('{0}.{1}' -f $testDomainName, $testParentDomainName) + } + } + + Describe 'xActiveDirectory.Common\Test-DomainMember' { + It 'Returns "True" when domain member' { + Mock -CommandName Get-CimInstance -MockWith { + return @{ + Name = $env:COMPUTERNAME + PartOfDomain = $true + } + } + + Test-DomainMember | Should Be $true + } + + It 'Returns "False" when workgroup member' { + Mock -CommandName Get-CimInstance -MockWith { + return @{ + Name = $env:COMPUTERNAME + } + } + + Test-DomainMember | Should Be $false + } + } + + Describe 'xActiveDirectory.Common\Get-DomainName' { + It 'Returns expected domain name' { + Mock -CommandName Get-CimInstance -MockWith { + return @{ + Name = $env:COMPUTERNAME + Domain = 'contoso.com' + } + } + + Get-DomainName | Should Be 'contoso.com' + } + } + + Describe 'xActiveDirectory.Common\Assert-Module' { + BeforeAll { + $testModuleName = 'TestModule' + } + + Context 'When module is not installed' { + BeforeAll { + Mock -CommandName Get-Module + } + + It 'Should throw the correct error' { + { Assert-Module -ModuleName $testModuleName } | Should -Throw ($script:localizedData.RoleNotFoundError -f $testModuleName) + } + } + + Context 'When module is available' { + BeforeAll { + Mock -CommandName Import-Module + Mock -CommandName Get-Module -MockWith { + return @{ + Name = $testModuleName + } + } + } + + Context 'When module should not be imported' { + It 'Should not throw an error' { + { Assert-Module -ModuleName $testModuleName } | Should -Not -Throw + + Assert-MockCalled -CommandName Import-Module -Exactly -Times 0 -Scope It + } + } + + Context 'When module should be imported' { + It 'Should not throw an error' { + { Assert-Module -ModuleName $testModuleName -ImportModule } | Should -Not -Throw + + Assert-MockCalled -CommandName Import-Module -Exactly -Times 1 -Scope It + } + } + } + } + + Describe 'xActiveDirectory.Common\Get-ADObjectParentDN' { + It 'Returns CN object parent path' { + Get-ADObjectParentDN -DN 'CN=Administrator,CN=Users,DC=contoso,DC=com' | Should Be 'CN=Users,DC=contoso,DC=com' + } + + It 'Returns OU object parent path' { + Get-ADObjectParentDN -DN 'CN=Administrator,OU=Custom Organizational Unit,DC=contoso,DC=com' | Should Be 'OU=Custom Organizational Unit,DC=contoso,DC=com' + } + } + + Describe 'xActiveDirectory.Common\Remove-DuplicateMembers' { + It 'Removes one duplicate' { + $members = Remove-DuplicateMembers -Members 'User1','User2','USER1' + + $members.Count | Should Be 2 + $members -contains 'User1' | Should Be $true + $members -contains 'User2' | Should Be $true + } + + It 'Removes two duplicates' { + $members = Remove-DuplicateMembers -Members 'User1','User2','USER1','USER2' + + $members.Count | Should Be 2 + $members -contains 'User1' | Should Be $true + $members -contains 'User2' | Should Be $true + } + + It 'Removes double duplicates' { + $members = Remove-DuplicateMembers -Members 'User1','User2','USER1','user1' + + $members.Count | Should Be 2 + $members -contains 'User1' | Should Be $true + $members -contains 'User2' | Should Be $true + } + } + + Describe 'xActiveDirectory.Common\Test-Members' { + It 'Passes when nothing is passed' { + Test-Members -ExistingMembers $null | Should Be $true + } + + It 'Passes when there are existing members but members are required' { + $testExistingMembers = @('USER1', 'USER2') + + Test-Members -ExistingMembers $testExistingMembers | Should Be $true + } + + It 'Passes when existing members match required members' { + $testExistingMembers = @('USER1', 'USER2') + $testMembers = @('USER2', 'USER1') + + Test-Members -ExistingMembers $testExistingMembers -Members $testMembers | Should Be $true + } + + It 'Fails when there are no existing members and members are required' { + $testExistingMembers = @('USER1', 'USER2') + $testMembers = @('USER1', 'USER3') + + Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false + } + + It 'Fails when there are more existing members than the members required' { + $testExistingMembers = @('USER1', 'USER2', 'USER3') + $testMembers = @('USER1', 'USER3') + + Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false + } + + It 'Fails when there are more existing members than the members required' { + $testExistingMembers = @('USER1', 'USER2') + $testMembers = @('USER1', 'USER3', 'USER2') + + Test-Members -ExistingMembers $null -Members $testMembers | Should Be $false + } + + It 'Fails when existing members do not match required members' { + $testExistingMembers = @('USER1', 'USER2') + $testMembers = @('USER1', 'USER3') + + Test-Members -ExistingMembers $testExistingMembers -Members $testMembers | Should -BeFalse + } + + It 'Passes when existing members include required member' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToInclude = @('USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should -BeTrue + } + + It 'Passes when existing members include required members' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToInclude = @('USER2', 'USER1') + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should -BeTrue + } + + It 'Fails when existing members is missing a required member' { + $testExistingMembers = @('USER1') + $testMembersToInclude = @('USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should -BeFalse + } + + It 'Fails when existing members is missing a required member' { + $testExistingMembers = @('USER1', 'USER3') + $testMembersToInclude = @('USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should -BeFalse + } + + It 'Fails when existing members is missing a required members' { + $testExistingMembers = @('USER3') + $testMembersToInclude = @('USER1', 'USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should -BeFalse + } + + It 'Passes when existing member does not include excluded member' { + $testExistingMembers = @('USER1') + $testMembersToExclude = @('USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToExclude | Should -BeTrue + } + + It 'Passes when existing member does not include excluded members' { + $testExistingMembers = @('USER1') + $testMembersToExclude = @('USER2', 'USER3') + + Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToExclude | Should -BeTrue + } + + It 'Passes when existing members does not include excluded member' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToExclude = @('USER3') + + Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToExclude | Should -BeTrue + } + + It 'Should fail when an existing members is include as an excluded member' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToExclude = @('USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToExclude | Should -BeFalse + } + + It 'Should pass when MembersToExclude is set to $null' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToExclude = $null + + Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToExclude | Should -BeTrue + } + + It 'Should pass when MembersToInclude is set to $null' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToInclude = $null + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude | Should -BeTrue + } + + It 'Should fail when Members is set to $null' { + $testExistingMembers = @('USER1', 'USER2') + $testMembers = $null + + Test-Members -ExistingMembers $testExistingMembers -Members $testMembers -Verbose | Should -BeFalse + } + + It 'Should fail when multiple Members are the wrong members' { + $testExistingMembers = @('USER1', 'USER2') + $testMembers = @('USER3', 'USER4') + + Test-Members -ExistingMembers $testExistingMembers -Members $testMembers -Verbose | Should -BeFalse + } + + It 'Should fail when multiple MembersToInclude are not present in existing members' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToInclude = @('USER3', 'USER4') + + Test-Members -ExistingMembers $testExistingMembers -MembersToInclude $testMembersToInclude -Verbose | Should -BeFalse + } + + It 'Should fail when multiple MembersToExclude are present in existing members' { + $testExistingMembers = @('USER1', 'USER2') + $testMembersToExclude = @('USER1', 'USER2') + + Test-Members -ExistingMembers $testExistingMembers -MembersToExclude $testMembersToExclude -Verbose | Should -BeFalse + } + } + + Describe 'xActiveDirectory.Common\Assert-MemberParameters' { + It "Throws if 'Members' is specified but is empty" { + { Assert-MemberParameters -Members @() } | Should -Throw ($script:localizedData.MembersIsNullError -f 'Members', 'MembersToInclude', 'MembersToExclude') + } + + It "Throws if 'Members' and 'MembersToInclude' are specified" { + { Assert-MemberParameters -Members @('User1') -MembersToInclude @('User1') } | Should Throw 'parameters conflict' + } + + It "Throws if 'Members' and 'MembersToExclude' are specified" { + { Assert-MemberParameters -Members @('User1') -MembersToExclude @('User2') } | Should Throw 'parameters conflict' + } + + It "Throws if 'MembersToInclude' and 'MembersToExclude' contain the same member" { + { Assert-MemberParameters -MembersToExclude @('user1') -MembersToInclude @('USER1') } | Should Throw 'member must not be included in both' + } + + It "Throws if 'MembersToInclude' and 'MembersToExclude' are empty" { + { Assert-MemberParameters -MembersToExclude @() -MembersToInclude @() } | Should Throw 'At least one member must be specified' + } + } + + Describe 'xActiveDirectory.Common\ConvertTo-Timespan' { + It "Returns 'System.TimeSpan' object type" { + $testIntTimeSpan = 60 + + $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Minutes + + $result -is [System.TimeSpan] | Should Be $true + } + + It 'Creates TimeSpan from seconds' { + $testIntTimeSpan = 60 + + $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Seconds + + $result.TotalSeconds | Should Be $testIntTimeSpan + } + + It 'Creates TimeSpan from minutes' { + $testIntTimeSpan = 60 + + $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Minutes + + $result.TotalMinutes | Should Be $testIntTimeSpan + } + + It 'Creates TimeSpan from hours' { + $testIntTimeSpan = 60 + + $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Hours + + $result.TotalHours | Should Be $testIntTimeSpan + } + + It 'Creates TimeSpan from days' { + $testIntTimeSpan = 60 + + $result = ConvertTo-TimeSpan -TimeSpan $testIntTimeSpan -TimeSpanType Days + + $result.TotalDays | Should Be $testIntTimeSpan + } + } + + Describe 'xActiveDirectory.Common\ConvertFrom-Timespan' { + It "Returns 'System.UInt32' object type" { + $testIntTimeSpan = 60 + $testTimeSpan = New-TimeSpan -Seconds $testIntTimeSpan + + $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Seconds + + $result -is [System.UInt32] | Should Be $true + } + + It 'Converts TimeSpan to total seconds' { + $testIntTimeSpan = 60 + $testTimeSpan = New-TimeSpan -Seconds $testIntTimeSpan + + $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Seconds + + $result | Should Be $testTimeSpan.TotalSeconds + } + + It 'Converts TimeSpan to total minutes' { + $testIntTimeSpan = 60 + $testTimeSpan = New-TimeSpan -Minutes $testIntTimeSpan + + $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Minutes + + $result | Should Be $testTimeSpan.TotalMinutes + } + + It 'Converts TimeSpan to total hours' { + $testIntTimeSpan = 60 + $testTimeSpan = New-TimeSpan -Hours $testIntTimeSpan + + $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Hours + + $result | Should Be $testTimeSpan.TotalHours + } + + It 'Converts TimeSpan to total days' { + $testIntTimeSpan = 60 + $testTimeSpan = New-TimeSpan -Days $testIntTimeSpan + + $result = ConvertFrom-TimeSpan -TimeSpan $testTimeSpan -TimeSpanType Days + + $result | Should Be $testTimeSpan.TotalDays + } + } + + Describe 'xActiveDirectory.Common\Get-ADCommonParameters' { + It "Returns 'System.Collections.Hashtable' object type" { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity + + $result -is [System.Collections.Hashtable] | Should Be $true + } + + It "Returns 'Identity' key by default" { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity + + $result['Identity'] | Should Be $testIdentity + } + + It "Returns 'Name' key when 'UseNameParameter' is specified" { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity -UseNameParameter + + $result['Name'] | Should Be $testIdentity + } + + foreach ($identityParam in @('UserName','GroupName','ComputerName')) + { + It "Returns 'Identity' key when '$identityParam' alias is specified" { + $testIdentity = 'contoso.com' + $getADCommonParameters = @{ + $identityParam = $testIdentity + } + + $result = Get-ADCommonParameters @getADCommonParameters + + $result['Identity'] | Should Be $testIdentity + } + } + + It "Returns 'Identity' key by default when 'Identity' and 'CommonName' are specified" { + $testIdentity = 'contoso.com' + $testCommonName = 'Test Common Name' + + $result = Get-ADCommonParameters -Identity $testIdentity -CommonName $testCommonName + + $result['Identity'] | Should Be $testIdentity + } + + It "Returns 'Identity' key with 'CommonName' when 'Identity', 'CommonName' and 'PreferCommonName' are specified" { + $testIdentity = 'contoso.com' + $testCommonName = 'Test Common Name' + + $result = Get-ADCommonParameters -Identity $testIdentity -CommonName $testCommonName -PreferCommonName + + $result['Identity'] | Should Be $testCommonName + } + + It "Returns 'Identity' key with 'Identity' when 'Identity' and 'PreferCommonName' are specified" { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity -PreferCommonName + + $result['Identity'] | Should Be $testIdentity + } + + it "Returns 'Name' key when 'UseNameParameter' and 'PreferCommonName' are supplied" { + $testIdentity = 'contoso.com' + $testCommonName = 'Test Common Name' + + $result = Get-ADCommonParameters -Identity $testIdentity -UseNameParameter -CommonName $testCommonName -PreferCommonName + + $result['Name'] | Should Be $testCommonName + } + + It "Does not return 'Credential' key by default" { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity + + $result.ContainsKey('Credential') | Should Be $false + } + + It "Returns 'Credential' key when specified" { + $testIdentity = 'contoso.com' + $testCredential = [System.Management.Automation.PSCredential]::Empty + + $result = Get-ADCommonParameters -Identity $testIdentity -Credential $testCredential + + $result['Credential'] | Should Be $testCredential + } + + It "Does not return 'Server' key by default" { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity + + $result.ContainsKey('Server') | Should Be $false + } + + It "Returns 'Server' key when specified" { + $testIdentity = 'contoso.com' + $testServer = 'testserver.contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity -Server $testServer + + $result['Server'] | Should Be $testServer + } + + It "Converts 'DomainAdministratorCredential' parameter to 'Credential' key" { + $testIdentity = 'contoso.com' + $testCredential = [System.Management.Automation.PSCredential]::Empty + + $result = Get-ADCommonParameters -Identity $testIdentity -DomainAdministratorCredential $testCredential + + $result['Credential'] | Should Be $testCredential + } + + It "Converts 'DomainController' parameter to 'Server' key" { + $testIdentity = 'contoso.com' + $testServer = 'testserver.contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity -DomainController $testServer + + $result['Server'] | Should Be $testServer + } + + It 'Accepts remaining arguments' { + $testIdentity = 'contoso.com' + + $result = Get-ADCommonParameters -Identity $testIdentity -UnexpectedParameter 42 + + $result['Identity'] | Should Be $testIdentity + } + } + + Describe 'xActiveDirectory.Common\ConvertTo-DeploymentForestMode' { + It 'Converts an Microsoft.ActiveDirectory.Management.ForestMode to Microsoft.DirectoryServices.Deployment.Types.ForestMode' { + ConvertTo-DeploymentForestMode -Mode Windows2012Forest | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.ForestMode] + } + + It 'Converts an Microsoft.ActiveDirectory.Management.ForestMode to the correct Microsoft.DirectoryServices.Deployment.Types.ForestMode' { + ConvertTo-DeploymentForestMode -Mode Windows2012Forest | Should Be ([Microsoft.DirectoryServices.Deployment.Types.ForestMode]::Win2012) + } + + It 'Converts valid integer to Microsoft.DirectoryServices.Deployment.Types.ForestMode' { + ConvertTo-DeploymentForestMode -ModeId 5 | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.ForestMode] + } + + It 'Converts a valid integer to the correct Microsoft.DirectoryServices.Deployment.Types.ForestMode' { + ConvertTo-DeploymentForestMode -ModeId 5 | Should Be ([Microsoft.DirectoryServices.Deployment.Types.ForestMode]::Win2012) + } + + It 'Throws an exception when an invalid forest mode is selected' { + { ConvertTo-DeploymentForestMode -Mode Nonexistant } | Should Throw + } + + It 'Throws no exception when a null value is passed' { + { ConvertTo-DeploymentForestMode -Mode $null } | Should Not Throw + } + + It 'Throws no exception when an invalid mode id is selected' { + { ConvertTo-DeploymentForestMode -ModeId 666 } | Should Not Throw + } + + It 'Returns $null when a null value is passed' { + ConvertTo-DeploymentForestMode -Mode $null | Should Be $null + } + + It 'Returns $null when an invalid mode id is selected' { + ConvertTo-DeploymentForestMode -ModeId 666 | Should Be $null + } + } + + Describe 'xActiveDirectory.Common\ConvertTo-DeploymentDomainMode' { + It 'Converts an Microsoft.ActiveDirectory.Management.DomainMode to Microsoft.DirectoryServices.Deployment.Types.DomainMode' { + ConvertTo-DeploymentDomainMode -Mode Windows2012Domain | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.DomainMode] + } + + It 'Converts an Microsoft.ActiveDirectory.Management.DomainMode to the correct Microsoft.DirectoryServices.Deployment.Types.DomainMode' { + ConvertTo-DeploymentDomainMode -Mode Windows2012Domain | Should Be ([Microsoft.DirectoryServices.Deployment.Types.DomainMode]::Win2012) + } + + It 'Converts valid integer to Microsoft.DirectoryServices.Deployment.Types.DomainMode' { + ConvertTo-DeploymentDomainMode -ModeId 5 | Should BeOfType [Microsoft.DirectoryServices.Deployment.Types.DomainMode] + } + + It 'Converts a valid integer to the correct Microsoft.DirectoryServices.Deployment.Types.DomainMode' { + ConvertTo-DeploymentDomainMode -ModeId 5 | Should Be ([Microsoft.DirectoryServices.Deployment.Types.DomainMode]::Win2012) + } + + It 'Throws an exception when an invalid domain mode is selected' { + { ConvertTo-DeploymentDomainMode -Mode Nonexistant } | Should Throw + } + + It 'Throws no exception when a null value is passed' { + { ConvertTo-DeploymentDomainMode -Mode $null } | Should Not Throw + } + + It 'Throws no exception when an invalid mode id is selected' { + { ConvertTo-DeploymentDomainMode -ModeId 666 } | Should Not Throw + } + + It 'Returns $null when a null value is passed' { + ConvertTo-DeploymentDomainMode -Mode $null | Should Be $null + } + + It 'Returns $null when an invalid mode id is selected' { + ConvertTo-DeploymentDomainMode -ModeId 666 | Should Be $null + } + } + + Describe 'xActiveDirectory.Common\Restore-ADCommonObject' { + $getAdObjectReturnValue = @( + [PSCustomObject] @{ + Deleted = $true + DistinguishedName = 'CN=a375347\0ADEL:f0e3f4fe-212b-43e7-83dd-c8f3b47ebb9c,CN=Deleted Objects,DC=contoso,DC=com' + Name = 'a375347' + ObjectClass = 'user' + ObjectGUID = 'f0e3f4fe-212b-43e7-83dd-c8f3b47ebb9c' + # Make this one day older. + whenChanged = (Get-Date).AddDays(-1) + }, + [PSCustomObject] @{ + Deleted = $true + DistinguishedName = 'CN=a375347\0ADEL:d3c8b8c1-c42b-4533-af7d-3aa73ecd2216,CN=Deleted Objects,DC=contoso,DC=com' + Name = 'a375347' + ObjectClass = 'user' + ObjectGUID = 'd3c8b8c1-c42b-4533-af7d-3aa73ecd2216' + whenChanged = Get-Date + } + ) + + $restoreAdObjectReturnValue = [PSCustomObject]@{ + DistinguishedName = 'CN=a375347,CN=Accounts,DC=contoso,DC=com' + Name = 'a375347' + ObjectClass = 'user' + ObjectGUID = 'd3c8b8c1-c42b-4533-af7d-3aa73ecd2216' + } + + function Restore-ADObject + { + } + + $getAdCommonParameterReturnValue = @{Identity = 'something'} + $restoreIdentity = 'SomeObjectName' + $restoreObjectClass = 'user' + $restoreObjectWrongClass = 'wrong' + + Context 'When there are objects in the recycle bin' { + Mock -CommandName Get-ADObject -MockWith { return $getAdObjectReturnValue } -Verifiable + Mock -CommandName Get-ADCommonParameters -MockWith { return $getAdCommonParameterReturnValue } + Mock -CommandName Restore-ADObject -Verifiable + + It 'Should not throw when called with the correct parameters' { + {Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass} | Should -Not -Throw + } + + It 'Should return the correct restored object' { + Mock -CommandName Restore-ADObject -MockWith { return $restoreAdObjectReturnValue} + (Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass).ObjectClass | Should -Be 'user' + } + + It 'Should throw the correct error when invalid parameters are used' { + {Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectWrongClass} | Should -Throw "Cannot validate argument on parameter 'ObjectClass'" + } + + It 'Should call Get-ADObject as well as Restore-ADObject' { + Assert-VerifiableMock + } + + It 'Should throw an InvalidOperationException when object parent does not exist' { + Mock -CommandName Restore-ADObject -MockWith { throw (New-Object -TypeName Microsoft.ActiveDirectory.Management.ADException)} + + {Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass} | Should -Throw -ExceptionType ([System.InvalidOperationException]) + } + } + + Context 'When there are no objects in the recycle bin' { + Mock -CommandName Get-ADObject + Mock -CommandName Get-ADCommonParameters -MockWith { return $getAdCommonParameterReturnValue} + Mock -CommandName Restore-ADObject + + It 'Should return $null' { + Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass | Should -Be $null + } + + It 'Should not call Restore-ADObject' { + Restore-ADCommonObject -Identity $restoreIdentity -ObjectClass $restoreObjectClass + Assert-MockCalled -CommandName Restore-ADObject -Exactly -Times 0 -Scope It + } + } + } + + Describe 'xActiveDirectory.Common\Get-ADDomainNameFromDistinguishedName' { + $validDistinguishedNames = @( + @{ + DN = 'CN=group1,OU=Group,OU=Wacken,DC=contoso,DC=com' + Domain = 'contoso.com' + } + @{ + DN = 'CN=group1,OU=Group,OU=Wacken,DC=sub,DC=contoso,DC=com' + Domain = 'sub.contoso.com' + } + @{ + DN = 'CN=group1,OU=Group,OU=Wacken,DC=child,DC=sub,DC=contoso,DC=com' + Domain = 'child.sub.contoso.com' + } + ) + + $invalidDistinguishedNames = @( + 'Group1' + 'contoso\group1' + 'user1@contoso.com' + ) + + Context 'The distinguished name is valid' { + foreach ($name in $validDistinguishedNames) + { + It "Should match domain $($name.Domain)" { + Get-ADDomainNameFromDistinguishedName -DistinguishedName $name.Dn | Should -Be $name.Domain + } + } + } + + Context 'The distinguished name is invalid' { + foreach ($name in $invalidDistinguishedNames) + { + It "Should return `$null for $name" { + Get-ADDomainNameFromDistinguishedName -DistinguishedName $name | Should -Be $null + } + } + } + } + + Describe 'xActiveDirectory.Common\Add-ADCommonGroupMember' { + Mock -CommandName Assert-Module -ParameterFilter { $ModuleName -eq 'ActiveDirectory' } + + $memberData = @( + [pscustomobject]@{ + Name = 'CN=Account1,DC=contoso,DC=com' + Domain = 'contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Group1,DC=contoso,DC=com' + Domain = 'contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Computer1,DC=contoso,DC=com' + Domain = 'contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Account1,DC=a,DC=contoso,DC=com' + Domain = 'a.contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Group1,DC=a,DC=contoso,DC=com' + Domain = 'a.contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Computer1,DC=a,DC=contoso,DC=com' + Domain = 'a.contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Account1,DC=b,DC=contoso,DC=com' + Domain = 'b.contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Group1,DC=b,DC=contoso,DC=com' + Domain = 'b.contoso.com' + } + [pscustomobject]@{ + Name = 'CN=Computer1,DC=b,DC=contoso,DC=com' + Domain = 'b.contoso.com' + } + ) + + $invalidMemberData = @( + 'contoso.com\group1' + 'user1@contoso.com' + 'computer1.contoso.com' + ) + + $fakeParameters = @{ + Identity = 'SomeGroup' + } + + Context 'When all members are in the same domain' { + Mock -CommandName Add-ADGroupMember + $groupCount = 0 + foreach ($domainGroup in ($memberData | Group-Object -Property Domain)) + { + $groupCount ++ + It 'Should not throw an error when calling Add-ADCommonGroupMember' { + Add-ADCommonGroupMember -Members $domainGroup.Group.Name -Parameters $fakeParameters + } + } + + It "Should have called Add-ADGroupMember $groupCount times" { + Assert-MockCalled -CommandName Add-ADGroupMember -Exactly -Times $groupCount + } + } + + Context 'When members are in different domains' { + Mock -CommandName Add-ADGroupMember + Mock -CommandName Get-ADObject -MockWith { + param ( + [Parameter()] + [string] + $Identity, + + [Parameter()] + [string] + $Server, + + [Parameter()] + [string[]] + $Properties + ) + + $objectClass = switch ($Identity) + { + {$Identity -match 'Group'} { 'group' } + {$Identity -match 'Account'} { 'user' } + {$Identity -match 'Computer'} { 'computer' } + } + + return ([PSCustomObject]@{ + objectClass = $objectClass + }) + } + # Mocks should return something that is used with Add-ADGroupMember + Mock -CommandName Get-ADComputer -MockWith { return 'placeholder' } + Mock -CommandName Get-ADGroup -MockWith { return 'placeholder' } + Mock -CommandName Get-ADUser -MockWith { return 'placeholder' } + + It 'Should not throw an error' { + {Add-ADCommonGroupMember -Members $memberData.Name -Parameters $fakeParameters -MembersInMultipleDomains} | Should -Not -Throw + } + + It 'Should have called all mocked cmdlets' { + Assert-MockCalled -CommandName Get-ADComputer -Exactly -Times $memberData.Where( {$_.Name -like '*Computer*'}).Count + Assert-MockCalled -CommandName Get-ADUser -Exactly -Times $memberData.Where( {$_.Name -like '*Account*'}).Count + Assert-MockCalled -CommandName Get-ADGroup -Exactly -Times $memberData.Where( {$_.Name -like '*Group*'}).Count + Assert-MockCalled -CommandName Add-ADGroupMember -Exactly -Times $memberData.Count + } + } + + Context 'When the domain name cannot be determined' { + It 'Should throw an InvalidArgumentException' { + {Add-ADCommonGroupMember -Members $invalidMemberData -Parameters $fakeParameters -MembersInMultipleDomains} | Should -Throw -ExceptionType ([System.ArgumentException]) + } + } + } + + Describe 'xActiveDirectory.Common\Get-DomainControllerObject' { + Context 'When domain name cannot be reached' { + BeforeAll { + Mock -CommandName Get-ADDomainController -MockWith { + throw New-Object -TypeName 'Microsoft.ActiveDirectory.Management.ADServerDownException' + } + } + + It 'Should throw the correct error' { + { Get-DomainControllerObject -DomainName 'contoso.com' -Verbose } | Should -Throw $localizedString.FailedEvaluatingDomainController + + Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It + } + } + + Context 'When current node is not a domain controller' { + BeforeAll { + Mock -CommandName Get-ADDomainController + Mock -CommandName Test-IsDomainController -MockWith { + return $false + } + } + + It 'Should return $null' { + $getDomainControllerObjectResult = Get-DomainControllerObject -DomainName 'contoso.com' -Verbose + $getDomainControllerObjectResult | Should -BeNullOrEmpty + + Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It + } + } + + Context 'When current node is not a domain controller, but operating system information says it should be' { + BeforeAll { + Mock -CommandName Get-ADDomainController + Mock -CommandName Test-IsDomainController -MockWith { + return $true + } + } + + It 'Should throw the correct error' { + { Get-DomainControllerObject -DomainName 'contoso.com' -Verbose } | Should -Throw $script:localizedData.WasExpectingDomainController + + Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It + } + } + + Context 'When current node is a domain controller' { + BeforeAll { + Mock -CommandName Get-ADDomainController -MockWith { + return @{ + Site = 'MySite' + Domain = 'contoso.com' + IsGlobalCatalog = $true + } + } + } + + It 'Should return the correct values for each property' { + $getDomainControllerObjectResult = Get-DomainControllerObject -DomainName 'contoso.com' -Verbose + + $getDomainControllerObjectResult.Site | Should -Be 'MySite' + $getDomainControllerObjectResult.Domain | Should -Be 'contoso.com' + $getDomainControllerObjectResult.IsGlobalCatalog | Should -BeTrue + + Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It + } + } + + Context 'When current node is a domain controller, and using specific credential' { + BeforeAll { + Mock -CommandName Get-ADDomainController -MockWith { + return @{ + Site = 'MySite' + Domain = 'contoso.com' + IsGlobalCatalog = $true + } + } + + $mockAdministratorUser = 'admin@contoso.com' + $mockAdministratorPassword = 'P@ssw0rd-12P@ssw0rd-12' | ConvertTo-SecureString -AsPlainText -Force + $mockAdministratorCredential = New-Object -TypeName 'System.Management.Automation.PSCredential' -ArgumentList @($mockAdministratorUser, $mockAdministratorPassword) + } + + It 'Should return the correct values for each property' { + $getDomainControllerObjectResult = Get-DomainControllerObject -DomainName 'contoso.com' -Credential $mockAdministratorCredential -Verbose + + $getDomainControllerObjectResult.Site | Should -Be 'MySite' + $getDomainControllerObjectResult.Domain | Should -Be 'contoso.com' + $getDomainControllerObjectResult.IsGlobalCatalog | Should -BeTrue + + Assert-MockCalled -CommandName Get-ADDomainController -ParameterFilter { + $PSBoundParameters.ContainsKey('Credential') -eq $true + } -Exactly -Times 1 -Scope It + } + } + } + + Describe 'xActiveDirectory.Common\Test-IsDomainController' { + Context 'When operating system information says the node is a domain controller' { + BeforeAll { + Mock -CommandName Get-CimInstance -MockWith { + return @{ + ProductType = 2 + } + } + } + + It 'Should return $true' { + $testIsDomainControllerResult = Test-IsDomainController + $testIsDomainControllerResult | Should -BeTrue + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It + } + } + + Context 'When operating system information says the node is not a domain controller' { + BeforeAll { + Mock -CommandName Get-CimInstance -MockWith { + return @{ + ProductType = 3 + } + } + } + + It 'Should return $false' { + $testIsDomainControllerResult = Test-IsDomainController + $testIsDomainControllerResult | Should -BeFalse + + Assert-MockCalled -CommandName Get-CimInstance -Exactly -Times 1 -Scope It + } + } + } + + Describe 'xActiveDirectory.Common\Convert-PropertyMapToObjectProperties' { + Context 'When a property map should be converted to object properties' { + BeforeAll { + $propertyMapValue = @( + @{ + ParameterName = 'ComputerName' + PropertyName = 'cn' + }, + @{ + ParameterName = 'Location' + } + ) + } + + It 'Should return the correct values' { + $convertPropertyMapToObjectPropertiesResult = Convert-PropertyMapToObjectProperties $propertyMapValue + $convertPropertyMapToObjectPropertiesResult | Should -HaveCount 2 + $convertPropertyMapToObjectPropertiesResult[0] | Should -Be 'cn' + $convertPropertyMapToObjectPropertiesResult[1] | Should -Be 'Location' + } + } + + Context 'When a property map contains a wrong type' { + BeforeAll { + $propertyMapValue = @( + @{ + ParameterName = 'ComputerName' + PropertyName = 'cn' + }, + 'Location' + ) + } + + It 'Should throw the correct error' { + { + Convert-PropertyMapToObjectProperties $propertyMapValue + } | Should -Throw $localizedString.PropertyMapArrayIsWrongType + } + } + } + + Describe 'DscResource.Common\Test-DscPropertyState' -Tag 'TestDscPropertyState' { + Context 'When comparing tables' { + It 'Should return true for two identical tables' { + $mockValues = @{ + CurrentValue = 'Test' + DesiredValue = 'Test' + } + + Test-DscPropertyState -Values $mockValues | Should -Be $true + } + } + + Context 'When comparing strings' { + It 'Should return false when a value is different for [System.String]' { + $mockValues = @{ + CurrentValue = [System.String] 'something' + DesiredValue = [System.String] 'test' + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + + It 'Should return false when a String value is missing' { + $mockValues = @{ + CurrentValue = $null + DesiredValue = [System.String] 'Something' + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + } + + Context 'When comparing integers' { + It 'Should return false when a value is different for [System.Int32]' { + $mockValues = @{ + CurrentValue = [System.Int32] 1 + DesiredValue = [System.Int32] 2 + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + + It 'Should return false when a value is different for [Int16]' { + $mockValues = @{ + CurrentValue = [System.Int16] 1 + DesiredValue = [System.Int16] 2 + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + + It 'Should return false when a value is different for [UInt16]' { + $mockValues = @{ + CurrentValue = [System.UInt16] 1 + DesiredValue = [System.UInt16] 2 + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + + It 'Should return false when a Integer value is missing' { + $mockValues = @{ + CurrentValue = $null + DesiredValue = [System.Int32] 1 + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + } + + Context 'When comparing booleans' { + It 'Should return false when a value is different for [Boolean]' { + $mockValues = @{ + CurrentValue = [System.Boolean] $true + DesiredValue = [System.Boolean] $false + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + + It 'Should return false when a Boolean value is missing' { + $mockValues = @{ + CurrentValue = $null + DesiredValue = [System.Boolean] $true + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + } + + Context 'When comparing arrays' { + It 'Should return true when evaluating an array' { + $mockValues = @{ + CurrentValue = @('1','2') + DesiredValue = @('1','2') + } + + Test-DscPropertyState -Values $mockValues | Should -Be $true + } + + It 'Should return false when evaluating an array with wrong values' { + $mockValues = @{ + CurrentValue = @('CurrentValueA','CurrentValueB') + DesiredValue = @('DesiredValue1','DesiredValue2') + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + + It 'Should return false when evaluating an array, but the current value is $null' { + $mockValues = @{ + CurrentValue = $null + DesiredValue = @('1','2') + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + } + } + + Context -Name 'When passing invalid types for DesiredValue' { + It 'Should write a warning when DesiredValue contain an unsupported type' { + Mock -CommandName Write-Warning -Verifiable + + # This is a dummy type to test with a type that could never be a correct one. + class MockUnknownType + { + [ValidateNotNullOrEmpty()] + [System.String] + $Property1 + + [ValidateNotNullOrEmpty()] + [System.String] + $Property2 + + MockUnknownType() + { + } + } + + $mockValues = @{ + CurrentValue = New-Object -TypeName MockUnknownType + DesiredValue = New-Object -TypeName MockUnknownType + } + + Test-DscPropertyState -Values $mockValues | Should -Be $false + + Assert-MockCalled -CommandName Write-Warning -Exactly -Times 1 -Scope It + } + } + + Assert-VerifiableMock + } + + Describe 'xActiveDirectory.Common\Compare-ResourcePropertyState' { + Context 'When one property is in desired state' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult.Expected | Should -Be 'DC01' + $compareTargetResourceStateResult.Actual | Should -Be 'DC01' + $compareTargetResourceStateResult.InDesiredState | Should -BeTrue + } + } + + Context 'When two properties are in desired state' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + Location = 'Sweden' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + Location = 'Sweden' + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 2 + $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01' + $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01' + $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue + $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location' + $compareTargetResourceStateResult[1].Expected | Should -Be 'Sweden' + $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden' + $compareTargetResourceStateResult[1].InDesiredState | Should -BeTrue + } + } + + Context 'When passing just one property and that property is not in desired state' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + } + + $mockDesiredValues = @{ + ComputerName = 'APP01' + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult.Expected | Should -Be 'APP01' + $compareTargetResourceStateResult.Actual | Should -Be 'DC01' + $compareTargetResourceStateResult.InDesiredState | Should -BeFalse + } + } + + Context 'When passing two properties and one property is not in desired state' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + Location = 'Sweden' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + Location = 'Europe' + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 2 + $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01' + $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01' + $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue + $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location' + $compareTargetResourceStateResult[1].Expected | Should -Be 'Europe' + $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden' + $compareTargetResourceStateResult[1].InDesiredState | Should -BeFalse + } + } + + Context 'When passing a common parameter set to desired value' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + Verbose = $true + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult.Expected | Should -Be 'DC01' + $compareTargetResourceStateResult.Actual | Should -Be 'DC01' + $compareTargetResourceStateResult.InDesiredState | Should -BeTrue + } + } + + Context 'When using parameter Properties to compare desired values' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + Location = 'Sweden' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + Location = 'Europe' + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + Properties = @( + 'ComputerName' + ) + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 1 + $compareTargetResourceStateResult.ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult.Expected | Should -Be 'DC01' + $compareTargetResourceStateResult.Actual | Should -Be 'DC01' + $compareTargetResourceStateResult.InDesiredState | Should -BeTrue + } + } + + Context 'When using parameter Properties and IgnoreProperties to compare desired values' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + Location = 'Sweden' + Ensure = 'Present' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + Location = 'Europe' + Ensure = 'Absent' + } + } + + It 'Should return the correct values' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + IgnoreProperties = @( + 'Ensure' + ) + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -HaveCount 2 + $compareTargetResourceStateResult[0].ParameterName | Should -Be 'ComputerName' + $compareTargetResourceStateResult[0].Expected | Should -Be 'DC01' + $compareTargetResourceStateResult[0].Actual | Should -Be 'DC01' + $compareTargetResourceStateResult[0].InDesiredState | Should -BeTrue + $compareTargetResourceStateResult[1].ParameterName | Should -Be 'Location' + $compareTargetResourceStateResult[1].Expected | Should -Be 'Europe' + $compareTargetResourceStateResult[1].Actual | Should -Be 'Sweden' + $compareTargetResourceStateResult[1].InDesiredState | Should -BeFalse + } + } + + Context 'When using parameter Properties and IgnoreProperties to compare desired values' { + BeforeAll { + $mockCurrentValues = @{ + ComputerName = 'DC01' + Location = 'Sweden' + Ensure = 'Present' + } + + $mockDesiredValues = @{ + ComputerName = 'DC01' + Location = 'Europe' + Ensure = 'Absent' + } + } + + It 'Should return and empty array' { + $compareTargetResourceStateParameters = @{ + CurrentValues = $mockCurrentValues + DesiredValues = $mockDesiredValues + Properties = @( + 'ComputerName' + ) + IgnoreProperties = @( + 'ComputerName' + ) + } + + $compareTargetResourceStateResult = Compare-ResourcePropertyState @compareTargetResourceStateParameters + $compareTargetResourceStateResult | Should -BeNullOrEmpty + } + } + } + + Describe 'xActiveDirectory.Common\Assert-ADPSDrive' { + Mock -CommandName Assert-Module + + Context 'When the AD PS Drive does not exist and the New-PSDrive function is successful' { + Mock -CommandName Get-PSDrive -MockWith { $null } + Mock -CommandName New-PSDrive + + It 'Should not throw' { + { Assert-ADPSDrive } | Should -Not -Throw + } + + It 'Should have called Assert-Module' { + Assert-MockCalled -CommandName Assert-Module -Exactly -Times 1 -Scope Context + } + + It 'Should have called Get-PSDrive only once' { + Assert-MockCalled -CommandName Get-PSDrive -Exactly -Times 1 -Scope Context + } + + It 'Should have called New-PSDrive only once' { + Assert-MockCalled -CommandName New-PSDrive -Exactly -Times 1 -Scope Context + } + } + + Context 'When the AD PS Drive does not exist and the New-PSDrive function is not successful' { + Mock -CommandName Get-PSDrive -MockWith { $null } + Mock -CommandName New-PSDrive -MockWith { throw } + + It 'Should throw the correct error' { + { Assert-ADPSDrive } | Should -Throw $script:localizedString.CreatingNewADPSDriveError + } + + It 'Should call Assert-Module' { + Assert-MockCalled -CommandName Assert-Module -Exactly -Times 1 -Scope Context + } + + It 'Should call Get-PSDrive once' { + Assert-MockCalled -CommandName Get-PSDrive -Exactly -Times 1 -Scope Context + } + + It 'Should call New-PSDrive once' { + Assert-MockCalled -CommandName New-PSDrive -Exactly -Times 1 -Scope Context + } + } + + Context 'When the AD PS Drive already exists' { + Mock -CommandName Get-PSDrive -MockWith { New-MockObject -Type System.Management.Automation.PSDriveInfo } + Mock -CommandName New-PSDrive + + It 'Should not throw' { + { Assert-ADPSDrive } | Should -Not -Throw + } + + It 'Should call Assert-Module only once' { + Assert-MockCalled -CommandName Assert-Module -Exactly -Times 1 -Scope Context + } + + It 'Should call Get-PSDrive only once' { + Assert-MockCalled -CommandName Get-PSDrive -Exactly -Times 1 -Scope Context + } + + It 'Should not call New-PSDrive' { + Assert-MockCalled -CommandName New-PSDrive -Exactly -Times 0 -Scope Context + } + } + + } + + Describe 'xActiveDirectory.Common\Test-ADReplicationSite' { + BeforeAll { + function Get-ADDomainController + { + } + + function Get-ADReplicationSite + { + } + + Mock -CommandName Get-ADDomainController -MockWith { + return $env:COMPUTERNAME + } + } + + Context 'When a replication site does not exist' { + BeforeAll { + Mock -CommandName Get-ADReplicationSite -MockWith { + throw New-Object -TypeName 'Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException' + } + } + + It 'Should return $false' { + $testADReplicationSiteResult = Test-ADReplicationSite -SiteName 'TestSite' -DomainName 'contoso.com' + $testADReplicationSiteResult | Should -BeFalse + + Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ADReplicationSite -Exactly -Times 1 -Scope It + } + } + + Context 'When a replication site exist' { + BeforeAll { + Mock -CommandName Get-ADReplicationSite -MockWith { + return 'site object' + } + } + + It 'Should return $true' { + $testADReplicationSiteResult = Test-ADReplicationSite -SiteName 'TestSite' -DomainName 'contoso.com' + $testADReplicationSiteResult | Should -BeTrue + + Assert-MockCalled -CommandName Get-ADDomainController -Exactly -Times 1 -Scope It + Assert-MockCalled -CommandName Get-ADReplicationSite -Exactly -Times 1 -Scope It + } + } + } +}