Det siste året har jeg vært hos to forskjellige kunder med nøyaktige samme utfordring. Deres sentrale IAM løsning klarte ikke å provisjonene mailboks til brukeren basert på informasjon som ligger i regelsettet. Dette førte til at kundene valgte en løsning hvor alle nye mailbokser ble opprettet i dedikert database for deretter å manuelt bli flyttet til riktig database og server.

Den innebygde funksjonen i Exchange fungerer ikke optimalt når man har en sentral IAM løsning og Exchange servere på flere kontinenter. Exchange Automatic mailbox distribution vil automatisk plassere mailboksen i en database som tilhører samme site/lokasjon hvor provisjoneringen utføres. Når IAM løsningen, som står på datasenteret i Norge, utfører New-Mailbox-kommandoen, vil den alltid velge en database på datasenteret i Norge. Dette er selvsagt ikke optimalt når brukeren befinner seg på et annet kontinent og det er plassert ut lokale Exchange servere.

Løsningen ble i begge tilfellene å lage et Powershell script som tar en parameter –Site. IAM-systemet vet hvilken lokasjon/site brukeren tilhører og sender dette inn til scriptet. Scriptet finner også ut hvilken database på den aktuelle lokasjonen som har færrest antall mailbokser og velger denne databasen. På den måten blir mailboksene jevnt fordelt i databasene. Scriptet kan kjøres fra en ordinær Poweshell sesjon. Den leter gjennom Active Directory og finner en Exchange server i samme AD-Site og starter en sesjon mot denne.

Modulen må endres slik at det lages en kobling mellom site-koden og databaseprefiksen. I dette eksempelet begynner alle databaser i lokasjon NO med prefix NODB.

 Switch ($Site.ToUpper()){
 
 "NO" {$prefix="NODB*"}
 "US" {$prefix="USDB*"}
 "UK" {$prefix="UKDB*"}
 
 default {$prefix="*"}
 }

Databaser hvor det ikke ønskes å provisjonere brukere legges inn i $excludeDB

 
 #Do not count users in these databases
 $excludeDB = @("ServiceDB","PFDB1")

Powershell-modulen kan lastes ned her: SICRA-Exchange2013.psm1

Eksempel #1

exch_db1

 

Eksempel #2

exch_db2


 


Function Get-MailboxDatabaseWithLeastNumberOfMailboxes {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$false)][String]$Site,
[switch]$showSummary,
[switch]$returnDN
)   

<# 
.SYNOPSIS Connects to the first available Exchange server in the currrent AD site 
and returns the mailboxdatabase of the with least number of mailboxes in the 
specified site.

.DESCRIPTION This function reads the backlink propery 'homeMDBBL' 
of every mailbox database in the datacenter specified by site and returns the 
database with the least number of mailboxes connected to it. System mailboxes 
are excluded. 

.PARAMETER site Tells the script in witch datacenter to check for mailboxdatabases. 

.PARAMETER showSummary Write information about the usercount in every database 
to the console. 

.PARAMETER returnDN Return a string with th distinguishedName (DN) og the databse 
rather than the database object itself. 

.EXAMPLE Get-MailboxDatabaseWithLeastNumberOfMailboxes -Site US 
Returns the database with least number of mailboxes in the US datacenter. 

.EXAMPLE Get-MailboxDatabaseWithLeastNumberOfMailboxes -Site US -showSummary 
Returns the database with least number of mailboxes in the US datacenter, 
and write the number of users in every database to the console 

.EXAMPLE Get-MailboxDatabaseWithLeastNumberOfMailboxes -Site US -returnDN 
Returns the database with least number of mailboxes in the US datacenter, 
and write return a string with th distinguisedName (DN) og the databse 
rather than the database object itself.

.NOTES This function is written by Håkon Jensen, Sicra AS 
#>

   

    #Determine database prefix based on site parameter if specified

    Switch ($Site.ToUpper()){
    
        "NO" {$prefix="NODB*"}
        "US" {$prefix="USDB*"}
        "UK" {$prefix="UKDB*"}
    
        default {$prefix="*"}
    }

    
    
    #Do not count users in these databsaes
    $excludeDB = @("ServiceDB","PFDB1")



Function Connect-Exchange2013 {

    $session = Get-PSSession | Where-Object {$_.ConfigurationName -eq "Microsoft.Exchange" -and $_.State -eq "Opened" -and $_.Availability -eq "Available"}
    
    if ($session -eq $null) {

        Write-Verbose "Creating new remoting session."

        #Get Exchange Servers in AD Site
        $servers = Get-ExchangeServerInSite

        #Try Servers in Collection
        foreach ($server in $servers)
        {
            Write-Verbose "Connecting to $($server.NetBIOSName) in site $($server.Site)"
            Try 
            {
                $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI "http://$($server.NetBIOSName)/powershell" -WarningAction SilentlyContinue
                Import-PSSession $session
                Write-Verbose "Conncted to $($server.NetBIOSName)"
                Return $true
            }
            Catch 
            {
                Write-Warning "Could not connect to $($server.NetBIOSName)"
            }
        }

    } else {

        #Reusing exising connection
        Write-Verbose "Using existing session to $($session.ComputerName)"
        Return $true

    }

    #Unable to connect to Exchange Servers In Site
    Return $false
}

Function Get-ExchangeServerInSite {
    $serversInSite = @()
    [int] $exRoleMBX = 2
    $ADSite = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]
    $siteDN = $ADSite::GetComputerSite().GetDirectoryEntry().distinguishedName
    $configNC=([ADSI]"LDAP://RootDse").configurationNamingContext
    $searcher = new-object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$configNC")
    $objectClass = "objectClass=msExchExchangeServer"
    $version = "versionNumber>=1937801568"
    $site = "msExchServerSite=$siteDN"
    $searcher.Filter = "(&($objectClass)($version)($site))"
    $result = $searcher.PropertiesToLoad.Add("name")
    $result = $searcher.PropertiesToLoad.Add("msexchcurrentserverroles")
    $servers =  $searcher.FindAll()
    foreach($server in $servers)
    {
        If ($server.Properties.msexchcurrentserverroles[0] -band $exRoleMBX) 
        {
            $item = New-Object System.Object
            $item | Add-Member -MemberType NoteProperty -Name "NetBIOSName" -Value $server.Properties.name[0]
            $item | Add-Member -MemberType NoteProperty -Name "Site" -Value $siteDN
            $serversInSite += $item
        }
    }
    return $serversInSite
}

Function Get-NumberOfUsersInDatabase{
        [CmdletBinding()]
        Param([Parameter(Mandatory)][String]$DistinguishedName)
        

        
        #System mailboxes should not be counted
        [string] $exchangeSystemObjectOU = "CN=Microsoft Exchange System Objects"

       
        #Create Counter for valid mailboxes
        [int] $count = 0


        
        #Create ADSI object representing the mailbox database
        [ADSI] $objDB = “GC://$($DistinguishedName -replace ‘/’,’\/’)”
        if ( ($objDB.objectClass -ne $null) -and ($objDB.objectClass.Contains(“msExchPrivateMDB”)))
        {
            #Go though each mailbox and exclude system mailboxes
            Write-Verbose "Processing database $($objDB.cn)"            
            ForEach ($mailbox in $objDB.homeMDBBL){
                If ($mailbox.ToString().ToUpper().Contains($exchangeSystemObjectOU.ToUpper()) -eq $true) {
                    Write-Debug "EXCLUDE $mailbox"
                } else {
                    $count += 1
                    Write-Debug "INCLUDE$($count) $mailbox"
                }
            }
            #Return number of mailboxes in database, excluding system mailboxes
            Write-Debug "Database $($objDB.cn) contains $count mailboxes"
            Return $count
        }
    }


    #Connect to Exchange Servers In Current Site
    $result = Connect-Exchange2013

    if ($result -eq $false) 
    {
        #Connection to Exchaneg is not OK Stop
        Write-Error "Could not connect to Exchange 2013"
        Exit -1
    }
 
  
    
    #Get databases in users location
    $databases = Get-MailboxDatabase -Identity $prefix
    If ($databases.Count -lt 1) {
        Write-Error "Unable to get databases"
        Return $null
    }




    
    #Create object array of available databases
    $availableDatabases = @()
    
    ForEach ($database in $databases){

        #Write Progress Status
        $status = "Processing database $($database.Name)"
        $currentDB += 1
        Write-Progress -Activity "Counting users" -Status $status  -PercentComplete ($currentDB / $databases.Count * 100)

        #Skip databases in the exlusion list
        if ($excludeDB -contains $database -eq $false) {

                #Get Database DN
                $dn = $database.DistinguishedName

                #Get Database Name
                $name = $database.Name

                #Get Database UserCount
                $userCount = Get-NumberOfUsersInDatabase -DistinguishedName $dn

                #Add database DN and UserCount to array of available databases
                $item = New-Object System.Object
                $item | Add-Member -MemberType NoteProperty -Name "DistinguishedName" -Value $dn
                $item | Add-Member -MemberType NoteProperty -Name "DatabaseName" -Value $name
                $item | Add-Member -MemberType NoteProperty -Name "UserCount" -Value $userCount
                $availableDatabases += $item
            }
  
    }
    
    
    
    #Sort available databases so that the database with lowest number of mailboxes are stored in position [0]
    $availableDatabases = $availableDatabases | Sort-Object -Property UserCount

    if ($showSummary)
    {
        Write-Output "Summary databsae usercount"
        Write-Output ($availableDatabases | Format-Table DatabaseName,UserCount -AutoSize | Out-String)
    }



    #Return database object with lowest number of mailboxes
    If ($availableDatabases.Count -gt 0){ 

        #Return the database with lowest mailbox count (stored in position [0]

        #Return database object as default. Return DN as String if specified.
        if ($returnDN)
        {
            [string] $returnDB = $availableDatabases[0].DistinguishedName
        }
        else
        {
            $returnDB = Get-MailboxDatabase $availableDatabases[0].DatabaseName
        }
        
        Return $returnDB
    
    } else {
    
        Write-Error "No available database found."
        Return $null
    
    }

}


Export-ModuleMember -Function Get-MailboxDatabaseWithLeastNumberOfMailboxes