DSCResources/MSFT_xExchWaitForADPrep/MSFT_xExchWaitForADPrep.psm1
<#
.SYNOPSIS Retrieves the current DSC configuration for this resource. .PARAMETER Identity Not actually used. Enter anything, as long as it's not null. .PARAMETER Credential Credentials used to perform Active Directory lookups against the Schema, Configuration, and Domain naming contexts. .PARAMETER SchemaVersion Specifies that the Active Directory schema should have been prepared using Exchange 'setup /PrepareSchema', and should be at the specified version. .PARAMETER OrganizationVersion Specifies that the Exchange Organization should have been prepared using Exchange 'setup /PrepareAD', and should be at the specified version. .PARAMETER DomainVersion Specifies that the domains containing the target Exchange servers were prepared using setup /PrepareAD, /PrepareDomain, or /PrepareAllDomains, and should be at the specified version. .PARAMETER ExchangeDomains The FQDN's of domains that should be checked for DomainVersion in addition to the domain that this Exchange server belongs to. .PARAMETER RetryIntervalSec How many seconds to wait between retries when checking whether AD has been prepped. Defaults to 60. .PARAMETER RetryCount How many retry attempts should be made to see if AD has been prepped before an exception is thrown. Defaults to 30. #> function Get-TargetResource { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter(Mandatory = $true)] [System.String] $Identity, [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.Int32] $SchemaVersion, [Parameter()] [System.Int32] $OrganizationVersion, [Parameter()] [System.Int32] $DomainVersion, [Parameter()] [System.String[]] $ExchangeDomains, [Parameter()] [System.UInt32] $RetryIntervalSec = 60, [Parameter()] [System.UInt32] $RetryCount = 30 ) Write-FunctionEntry -Verbose:$VerbosePreference $adRootDSE = Get-ADRootDSEInternal -Credential $Credential if ($null -eq $adRootDSE) { throw 'Unable to retrieve ADRootDSE' } $currentSchemaVersion = Get-SchemaVersion -ADRootDSE $adRootDSE -Credential $Credential $currentOrganizationVersion = Get-OrganizationVersion -ADRootDSE $adRootDSE -Credential $Credential $currentDomainVersions = Get-DomainsVersion -Credential $Credential -ExchangeDomains $ExchangeDomains $returnValue = @{ SchemaVersion = [System.Int32] $currentSchemaVersion OrganizationVersion = [System.Int32] $currentOrganizationVersion DomainVersionHashtable = [System.Collections.Hashtable] $currentDomainVersions DomainVersion = [System.Int32] ($currentDomainVersions.Values | Sort-Object | Select-Object -First 1) ExchangeDomains = [System.String[]] (Get-StringFromHashtable -Hashtable $currentDomainVersions).Split(';') } $returnValue } <# .SYNOPSIS Sets the DSC configuration for this resource. .PARAMETER Identity Not actually used. Enter anything, as long as it's not null. .PARAMETER Credential Credentials used to perform Active Directory lookups against the Schema, Configuration, and Domain naming contexts. .PARAMETER SchemaVersion Specifies that the Active Directory schema should have been prepared using Exchange 'setup /PrepareSchema', and should be at the specified version. .PARAMETER OrganizationVersion Specifies that the Exchange Organization should have been prepared using Exchange 'setup /PrepareAD', and should be at the specified version. .PARAMETER DomainVersion Specifies that the domains containing the target Exchange servers were prepared using setup /PrepareAD, /PrepareDomain, or /PrepareAllDomains, and should be at the specified version. .PARAMETER ExchangeDomains The FQDN's of domains that should be checked for DomainVersion in addition to the domain that this Exchange server belongs to. .PARAMETER RetryIntervalSec How many seconds to wait between retries when checking whether AD has been prepped. Defaults to 60. .PARAMETER RetryCount How many retry attempts should be made to see if AD has been prepped before an exception is thrown. Defaults to 30. #> function Set-TargetResource { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.String] $Identity, [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.Int32] $SchemaVersion, [Parameter()] [System.Int32] $OrganizationVersion, [Parameter()] [System.Int32] $DomainVersion, [Parameter()] [System.String[]] $ExchangeDomains, [Parameter()] [System.UInt32] $RetryIntervalSec = 60, [Parameter()] [System.UInt32] $RetryCount = 30 ) Write-FunctionEntry -Verbose:$VerbosePreference $testResults = Wait-ForTrueTestTargetResource @PSBoundParameters if (!$testResults) { throw 'AD has still not been prepped after the maximum amount of retries.' } } <# .SYNOPSIS Tests whether the desired configuration for this resource has been applied. .PARAMETER Identity Not actually used. Enter anything, as long as it's not null. .PARAMETER Credential Credentials used to perform Active Directory lookups against the Schema, Configuration, and Domain naming contexts. .PARAMETER SchemaVersion Specifies that the Active Directory schema should have been prepared using Exchange 'setup /PrepareSchema', and should be at the specified version. .PARAMETER OrganizationVersion Specifies that the Exchange Organization should have been prepared using Exchange 'setup /PrepareAD', and should be at the specified version. .PARAMETER DomainVersion Specifies that the domains containing the target Exchange servers were prepared using setup /PrepareAD, /PrepareDomain, or /PrepareAllDomains, and should be at the specified version. .PARAMETER ExchangeDomains The FQDN's of domains that should be checked for DomainVersion in addition to the domain that this Exchange server belongs to. .PARAMETER RetryIntervalSec How many seconds to wait between retries when checking whether AD has been prepped. Defaults to 60. .PARAMETER RetryCount How many retry attempts should be made to see if AD has been prepped before an exception is thrown. Defaults to 30. #> function Test-TargetResource { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSDSCUseVerboseMessageInDSCResource", "")] [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.String] $Identity, [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.Int32] $SchemaVersion, [Parameter()] [System.Int32] $OrganizationVersion, [Parameter()] [System.Int32] $DomainVersion, [Parameter()] [System.String[]] $ExchangeDomains, [Parameter()] [System.UInt32] $RetryIntervalSec = 60, [Parameter()] [System.UInt32] $RetryCount = 30 ) Write-FunctionEntry -Verbose:$VerbosePreference $adStatus = Get-TargetResource @PSBoundParameters $testResults = $true if ($PSBoundParameters.ContainsKey('SchemaVersion')) { if ($SchemaVersion -gt $adStatus.SchemaVersion) { Write-InvalidSettingVerbose -SettingName 'SchemaVersion' ` -ExpectedValue $SchemaVersion ` -ActualValue $adStatus.SchemaVersion ` -Verbose:$VerbosePreference $testResults = $false } } if ($PSBoundParameters.ContainsKey('OrganizationVersion')) { if ($OrganizationVersion -gt $adStatus.OrganizationVersion) { Write-InvalidSettingVerbose -SettingName 'OrganizationVersion' ` -ExpectedValue $OrganizationVersion ` -ActualValue $adStatus.OrganizationVersion ` -Verbose:$VerbosePreference $testResults = $false } } if ($PSBoundParameters.ContainsKey('DomainVersion')) { [System.String[]] $targetDomains = Get-EachExchangeDomainFQDN -ExchangeDomains $ExchangeDomains # Compare the desired DomainVersion with the actual version of each domain foreach ($domain in $targetDomains) { if ($DomainVersion -gt $adStatus.DomainVersionHashtable[$domain]) { Write-InvalidSettingVerbose -SettingName "DomainVersion: $domain" ` -ExpectedValue $DomainVersion ` -ActualValue $adStatus.DomainVersionHashtable[$domain] ` -Verbose:$VerbosePreference $testResults = $false } } } return $testResults } <# .SYNOPSIS Executes Get-ADRootDSE and returns the results. .PARAMETER Credential The credentials to use when running Get-ADRootDSE. #> function Get-ADRootDSEInternal { param ( [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential ) $cmdletParams = @{ ErrorAction = 'SilentlyContinue' } if ($null -ne $Credential) { $cmdletParams.Add('Credential', $Credential) } return (Get-ADRootDSE @cmdletParams) } <# .SYNOPSIS Executes Get-ADObject with the specified parameters and returns the results. .PARAMETER Credential The credentials to use when running Get-ADObject. .PARAMETER Searching Whether Get-ADObject should search within the specified DN, or access it directly. .PARAMETER DistinguishedName The distinguishedName of the ADObject to access. .PARAMETER Properties The properties that Get-ADObject should retrieve. .PARAMETER Filter The filter to pass to Get-ADObject. .PARAMETER SearchScope The search scope to pass to Get-ADObject. #> function Get-ADObjectInternal { param ( [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.Boolean] $Searching = $false, [Parameter()] [System.String] $DistinguishedName, [Parameter()] [System.String[]] $Properties, [Parameter()] [System.String] $Filter, [Parameter()] [System.String] $SearchScope ) if ($Searching -eq $false) { $getAdObjParams = @{'Identity' = $DistinguishedName} } else { $getAdObjParams = @{'SearchBase' = $DistinguishedName} if ([System.String]::IsNullOrEmpty($Filter) -eq $false) { $getAdObjParams.Add('Filter', $Filter) } if ([System.String]::IsNullOrEmpty($SearchScope) -eq $false) { $getAdObjParams.Add('SearchScope', $SearchScope) } } if ($null -ne $Credential) { $getAdObjParams.Add('Credential', $Credential) } if ($Properties.Count -gt 0) { $getAdObjParams.Add('Properties', $Properties) } # ErrorAction SilentlyContinue doesn't seem to work with Get-ADObject. Doing in Try/Catch instead try { $object = Get-ADObject @getAdObjParams } catch { Write-Warning "Failed to find object at '$DistinguishedName' using Get-ADObject." } return $object } <# .SYNOPSIS Gets the Exchange Schema version in Int32 form. Returns null if the Schema Version cannot be retrieved. .PARAMETER ADRootDSE The ADRootDSE object to use when determining the Exchange Schema version. .PARAMETER Credential The Credentials to use when trying to determine the Exchange Schema version. #> function Get-SchemaVersion { [CmdletBinding()] [OutputType([Nullable[System.Int32]])] param ( [Parameter(Mandatory=$true)] [System.Object] $ADRootDSE, [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential ) $currentSchemaVersion = $null # Check for existence of schema object $schemaObj = Get-ADObjectInternal -Credential $Credential -DistinguishedName "CN=ms-Exch-Schema-Version-Pt,$($ADRootDSE.schemaNamingContext)" -Properties 'rangeUpper' if ($null -ne $schemaObj) { $currentSchemaVersion = $schemaObj.rangeUpper } else { Write-Warning "Unable to find schema object 'CN=ms-Exch-Schema-Version-Pt,$($ADRootDSE.schemaNamingContext)'. This is either because Exchange /PrepareSchema has not been run, or because the configured account does not have permissions to access this object." } return $currentSchemaVersion } <# .SYNOPSIS Gets the Exchange Organization version in Int32 form. Returns null if the Organization Version cannot be retrieved. .PARAMETER ADRootDSE The ADRootDSE object to use when determining the Exchange Organization version. .PARAMETER Credential The Credentials to use when trying to determine the Exchange Organization version. #> function Get-OrganizationVersion { [CmdletBinding()] [OutputType([Nullable[System.Int32]])] param ( [Parameter(Mandatory=$true)] [System.Object] $ADRootDSE, [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential ) $currentOrganizationVersion = $null $exchangeContainer = Get-ADObjectInternal -Credential $Credential -DistinguishedName "CN=Microsoft Exchange,CN=Services,$($ADRootDSE.configurationNamingContext)" -Properties 'rangeUpper' if ($null -ne $exchangeContainer) { $orgContainer = Get-ADObjectInternal -Credential $Credential -Searching $true -DistinguishedName "CN=Microsoft Exchange,CN=Services,$($ADRootDSE.configurationNamingContext)" -Properties 'objectVersion' -Filter "objectClass -like 'msExchOrganizationContainer'" -SearchScope 'OneLevel' if ($null -ne $orgContainer) { $currentOrganizationVersion = $orgContainer.objectVersion } else { Write-Warning "Unable to find any objects of class msExchOrganizationContainer under 'CN=Microsoft Exchange,CN=Services,$($ADRootDSE.configurationNamingContext)'. This is either because Exchange /PrepareAD has not been run, or because the configured account does not have permissions to access this object." } } else { Write-Warning "Unable to find Exchange Configuration Container at 'CN=Microsoft Exchange,CN=Services,$($ADRootDSE.configurationNamingContext)'. This is either because Exchange /PrepareAD has not been run, or because the configured account does not have permissions to access this object." } return $currentOrganizationVersion } <# .SYNOPSIS Gets a Hashtable containing the Exchange Domain version of each configured domain. Always returns at least the current Exchange Server's domain. Will also add an explicitly configured Exchange Domains if they are not already in the list. .PARAMETER Credential The Credentials to use when trying to determine the Exchange Domain versions. .PARAMETER ExchangeDomains A list of Exchange Domains to check for version in addition to the current server's domain. #> function Get-DomainsVersion { [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.String[]] $ExchangeDomains ) [System.Collections.Hashtable] $currentDomainVersions = @{} [System.String[]] $targetDomains = Get-EachExchangeDomainFQDN -ExchangeDomains $ExchangeDomains foreach ($domain in $targetDomains) { $domainDn = Get-DomainDNFromFQDN -Fqdn $domain $mesoContainer = Get-ADObjectInternal -Credential $Credential -DistinguishedName "CN=Microsoft Exchange System Objects,$($domainDn)" -Properties 'objectVersion' $mesoVersion = $null if ($null -ne $mesoContainer) { $mesoVersion = $mesoContainer.objectVersion } else { Write-Warning "Unable to find object with DN 'CN=Microsoft Exchange System Objects,$($domainDn)'. This is either because Exchange /PrepareDomain has not been run for this domain, or because the configured account does not have permissions to access this object." } $currentDomainVersions.Add($domain, $mesoVersion) } return $currentDomainVersions } <# .SYNOPSIS Gets a list of Exchange domains to check the domain version for. Always returns at least the current Exchange Server's domain. Will also add an explicitly configured Exchange Domains if they are not already in the list. .PARAMETER ExchangeDomains A list of Exchange Domains to check for version in addition to the current server's domain. #> function Get-EachExchangeDomainFQDN { [CmdletBinding()] [OutputType([System.String[]])] param ( [Parameter()] [System.String[]] $ExchangeDomains ) [System.String[]] $targetDomains = @() # Get this server's domain and add it to the list [System.String] $machineDomain = (Get-CimInstance -ClassName Win32_ComputerSystem).Domain.ToLower() $targetDomains += $machineDomain # Add any additional explicitly configured, non-duplicate domain to the list if ($null -ne $ExchangeDomains) { foreach ($domain in $ExchangeDomains) { $domainLower = $domain.ToLower() if ($targetDomains.Contains($domainLower) -eq $false) { $targetDomains += $domainLower } } } return $targetDomains } <# .SYNOPSIS Waits for a specified amount of time until Test-TargetResource returns $true. .PARAMETER Identity Not actually used. Enter anything, as long as it's not null. .PARAMETER Credential Credentials used to perform Active Directory lookups against the Schema, Configuration, and Domain naming contexts. .PARAMETER SchemaVersion Specifies that the Active Directory schema should have been prepared using Exchange 'setup /PrepareSchema', and should be at the specified version. .PARAMETER OrganizationVersion Specifies that the Exchange Organization should have been prepared using Exchange 'setup /PrepareAD', and should be at the specified version. .PARAMETER DomainVersion Specifies that the domains containing the target Exchange servers were prepared using setup /PrepareAD, /PrepareDomain, or /PrepareAllDomains, and should be at the specified version. .PARAMETER ExchangeDomains The FQDN's of domains that should be checked for DomainVersion in addition to the domain that this Exchange server belongs to. .PARAMETER RetryIntervalSec How many seconds to wait between retries when checking whether AD has been prepped. Defaults to 60. .PARAMETER RetryCount How many retry attempts should be made to see if AD has been prepped before an exception is thrown. Defaults to 30. #> function Wait-ForTrueTestTargetResource { [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(Mandatory = $true)] [System.String] $Identity, [Parameter()] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, [Parameter()] [System.Int32] $SchemaVersion, [Parameter()] [System.Int32] $OrganizationVersion, [Parameter()] [System.Int32] $DomainVersion, [Parameter()] [System.String[]] $ExchangeDomains, [Parameter()] [System.UInt32] $RetryIntervalSec = 60, [Parameter()] [System.UInt32] $RetryCount = 30 ) $testResults = $false for ($i = 0; $i -lt $RetryCount; $i++) { $testResults = Test-TargetResource @PSBoundParameters if ($testResults -eq $false) { Write-Verbose -Message "AD has still not been fully prepped as of $([DateTime]::Now). Sleeping for $($RetryIntervalSec) seconds." Start-Sleep -Seconds $RetryIntervalSec } else { break } } return $testResults } Export-ModuleMember -Function *-TargetResource |