EmmExDAGModule.psm1

<#PSScriptInfo
.VERSION 1.0.0
.AUTHOR Faris Malaeb
.PROJECTURI https://www.powershellcenter.com/
.DESCRIPTION
 This Powershell module will Place your Exchange Server DAG in maintenance Mode
 Also you can remove Exchange DAG from Maintenance Mode.
 Available Commands
    Start-EMMDAGEnabled: Set your Exchange Server to be in Maintenance Mode.
    Stop-EMMDAGEnabled: Remove Exchange from maintenanace Mode
    Set-EMMHubTransport: State Stop and Drain the HubTransport Service
    Start-EMMRedirectMessage: Redirect Messages in the Queue to another server FQDN
    Set-EMMClusterConfig: Disable or Enable Cluster Node
    Set-EMMDBActivationMoveNow: Set Exchange MailboxServer to Block mode so it wont accept mailbox activation request
    Test-EMMReadiness: Test the environment for readiness to go in maintenance Mode
    
 
#>
 

Function Check-ScriptReadiness{
param(
$ServerName,
$AltServer
)
        if (((Test-NetConnection -Port 80 -ComputerName $ServerName).TcpTestSucceeded -like $true) -and (Test-NetConnection -Port 80 -ComputerName $AltServer).TcpTestSucceeded -like $true){
        $isadmin=[bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")
        switch ($isadmin)
        {
            $true {return 1}
            $false {return 0}
           
        }
       
       }
       Else{
        Write-Warning -Message $Error[0]
        break
       }


}

Function Start-EMMDAGEnabled {
   
    Param(
        [parameter(mandatory=$false,ValueFromPipeline=$true,Position=0)]$ServerForMaintenance,
        [parameter(mandatory=$false)][ValidatePattern("(?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)")][string]$ReplacementServerFQDN

    )

        Begin{
        Write-Host "Preparing a basic check for Script Execution..."
            $ErrorActionPreference="Stop"
            $ReadyToExecute=Check-ScriptReadiness -ServerName $ServerForMaintenance -AltServer $ReplacementServerFQDN
            if ($ReadyToExecute -eq 0){Write-Host "Please Make sure that you execute Powershell as Admin" -ForegroundColor Red
                return
            }
        [hashtable]$ExMainProgress=[ordered]@{}

        }

        Process{
            Write-Host "Preparing $($ServerForMaintenance) to be placed in Maintinance Mode" -ForegroundColor Yellow
            $Step1=Set-EMMHubTransportState -Servername $ServerForMaintenance -Status Draining
            $step2=Start-EMMRedirectMessage -SourceServer $ServerForMaintenance -ToServer $ReplacementServerFQDN -TimeoutinSeconds 5
            $step3=Set-EMMClusterConfig -ClusterNode $ServerForMaintenance -PauseOrResume PauseThisNode
            $Step4=Set-EMMDBActivationMoveNow -ServerName $ServerForMaintenance -TargetServerNameForManualMove $ReplacementServerFQDN -BlockMode -TimeoutBeforeManualMove 120
            Set-ServerComponentState $ServerForMaintenance -Component ServerWideOffline -State Inactive -Requester Maintenance
            $step5=get-ServerComponentState $ServerForMaintenance -Component ServerWideOffline
            Write-Host "All Commands are completed, and below are the result...`n"-ForegroundColor Yellow
            $ExMainProgress.Add("HubTransportDraining",$Step1)
            $ExMainProgress.Add("Queue Redirection",$step2)
            $ExMainProgress.Add("ClusterNode",$step3)
            $ExMainProgress.Add("DBMove",$step4)
            $ExMainProgress.Add("ServerWide",$step5.State)

        }
        
        End{
       return $ExMainProgress 
        }
}
Export-ModuleMember Start-EmmDAGEnabled



Function Stop-EMMDAGEnabled {
   
    Param(
        [parameter(mandatory=$false,ValueFromPipeline=$true,Position=0)]$ServerInMaintenance
        
    )

        Begin{
            $ErrorActionPreference="Stop"
            [hashtable]$ExOutMainProgress=[ordered]@{}
        }

        Process{
            Write-Host "Preparing $($ServerInMaintenance) for Activation..." -ForegroundColor Yellow
            Set-ServerComponentState $ServerInMaintenance -Component ServerWideOffline -State active -Requester Maintenance
            $outstep1=Set-EMMClusterConfig -ClusterNode $ServerInMaintenance -PauseOrResume ResumeThisNode
            $outStep2=Set-EMMDBActivationMoveNow -ServerName $ServerInMaintenance -UnrestrictedOrIntrasite Unrestricted 
            $outStep3=Set-EMMHubTransportState -Servername $ServerInMaintenance -Status Active
              Write-Host "All should be done, below are the result, Make sure that there is no failure or other issues" -ForegroundColor Yellow
              Write-Host "-------- Result for Activating Server " -NoNewline ;Write-Host "$($ServerInMaintenance) " -ForegroundColor Yellow -NoNewline ;Write-Host " -----------"
              $ExOutMainProgress.Add("ServerWide",(Get-ServerComponentState $ServerInMaintenance -Component ServerWideOffline).State)
              $ExOutMainProgress.Add("ClusterNode",$outstep1)
              $ExOutMainProgress.Add("DB Server Activation",$outStep2)
              $ExOutMainProgress.Add("HubTransport",$outStep3)
          
            
        }
        
        End{
       return $ExOutMainProgress
        }
}
Export-ModuleMember Stop-EMMDAGEnabled


Function Set-EMMHubTransportState {
[CmdletBinding()]
Param(
[parameter(mandatory=$true,ValueFromPipeline=$true,Position=0)]$Servername,
[validateset("Draining","Active")]$Status
)

  Process{
  Write-Host "Configuring Hub Tranport to be " -NoNewline; Write-Host "$($Status)" -ForegroundColor Green -NoNewline ; Write-Host " For " -NoNewline; Write-Host "$($Servername)" -ForegroundColor Green

    Try
    {    

    if ($Status -like "Draining"){

       if ((Get-ExchangeServer | Get-ServerComponentState -Component Hubtransport | where {($_.State -like "Active")  -and  ($_.Serverfqdn -notlike "*$Servername*")}).state.count -eq 0){
            Write-warning "Ops, There is no more servers with a HubTransport state set to Active State in the environment, Please make sure to have atleast one"
            break
            }

       Set-ServerComponentState -identity $servername -Component HubTransport -State Draining -Requester Maintenance
       sleep -Seconds 1
       $Srvcomstate=(Get-ServerComponentState $servername -Component HubTransport).state
       return $Srvcomstate
       }
    Else{
       Set-ServerComponentState -identity $servername -Component HubTransport -State Active -Requester Maintenance
       sleep -Seconds 1
       $Srvcomstate=(Get-ServerComponentState $servername -Component HubTransport).state
       return $Srvcomstate
       }
    }
    catch {
        Write-Warning -Message $Error[0]
        break
    }

    }

    End{
       Write-Host "Configs are completed, Now $($Servername) is set to be :" -NoNewline; write-host (Get-ServerComponentState $servername -Component HubTransport).state -ForegroundColor Green

    }
    

    
}
Export-ModuleMember Set-EMMHubTransportState


Function Start-EMMRedirectMessage{
param(
[parameter(mandatory=$True,ValueFromPipeline=$true,Position=0)]$SourceServer,
[parameter(mandatory=$False)]$TimeoutinSeconds=120,
[parameter(mandatory=$True)][ValidatePattern("(?=^.{1,254}$)(^(?:(?!\d+\.|-)[a-zA-Z0-9_\-]{1,63}(?<!-)\.?)+(?:[a-zA-Z]{2,})$)")][string]$ToServer
)

Process{
    try {
    $counter=0
        Write-Host "Redirecting queued messages from $($SourceServer) to $($ToServer) process has started..."
        Write-Host "Transfaring the message queue, Please wait..$($TimeoutinSeconds) Seconds remain for timeout..."
        Redirect-Message -Server $SourceServer -Target $ToServer -Confirm:$False -ErrorAction Stop
        
    Do{

        sleep -Seconds 1
        $counter++
            if ($Counter -ge $TimeoutinSeconds){
                Write-Host "Process is Timeout"
                Write-Host "Currently there are $((Get-Queue | select Messagecount | Measure-Object -Sum -Property MessageCount).sum) "-NoNewline
                Write-Host "in the queue, the number should be Zero"
                Write-Host "Do you want to continue placing the server in maintenance Mode or you want to abort the process"
                $YesNo=Read-Host "Press Y to continue or any other key to abort the process"
                    if ($YesNo -like "Y"){return "Queue Transfer is not completed, But the user accepted it"}
                    else{
                    Write-Host "Queue Trasnfare fail to complete, Maybe a slow connection, or very heavy queue pending"
                    Write-Host "You can run the command" -NoNewline
                    Write-host " Start-ExtMainRedirectMessage -SourceServer $($SourceServer) -ToServer $($toserver) -Counter 120" -ForegroundColor Yellow
                    Break
                        }
                }
    }
    while (
    (Get-Queue | select Messagecount | Measure-Object -Sum -Property MessageCount).sum -ne 0
    )
    }
    Catch [Microsoft.Exchange.Data.Common.LocalizedException]{
    Write-Warning "It seems that the server is not reachable or not exist.. please confirm.`n" 
    Write-Host $_.exception.message -ForegroundColor Red
    break
    }
    Catch{
    Write-Warning -Message $Error[0]
    Write-Host ""
    $FailedYesNo=Read-Host "Press Y to continue placing the server in maintenance mode, or any other key to abort the process"
                   if ($FailedYesNo -like "Y"){return "Operation Failed, But the user accepted it"}
                    else{
                        Break
                        }
            
    }
}
End{
    Return "Queue Transfer is completed"
    }
}

Export-ModuleMember Start-EMMRedirectMessage


Function Set-EMMClusterConfig {
Param(
[parameter(mandatory=$true,ValueFromPipeline=$true,Position=0)]$ClusterNode,
[parameter(mandatory=$true)][validateset("PauseThisNode","ResumeThisNode")]$PauseOrResume
)

    Process{
        Write-Host "Starting Cluster Management for "-NoNewline ; Write-Host  $($ClusterNode) -ForegroundColor Yellow
    try{
          
          Write-Host "Checking Cluster Readiness and resilience" -ForegroundColor Yellow
          $Status=Invoke-Command -ComputerName $ClusterNode -ScriptBlock {(get-ClusterNode)}
          Write-Host "The number of Up Nodes are $(($Status | where {$_.state -like 'up'}).State.count)" -ForegroundColor  Yellow

        if ($PauseOrResume -like "PauseThisNode"){
                
         
            if (($Status | where {($_.state -like 'up') -and ($_.name -notlike $ClusterNode)}).count -eq 0){
                Write-Host "WARNING: The numbe of available cluster is not enough, Please stop and resume one node at least" -ForegroundColor Red
                $Status
                break
                }

            if (($Status | where{$_.name -like $ClusterNode}).State -Like "Paused"){
                Write-Host "Node already disabled...Nothing to do in this step"
                return "Node is Already Paused"
            }

             $clsstate=Invoke-Command -ComputerName $ClusterNode -ScriptBlock {Suspend-ClusterNode -name $args[0]} -ArgumentList $ClusterNode
                Sleep -Seconds 2
                return $clsstate.State
               }
               ## Resume Cluster node
         if ($PauseOrResume -like "ResumeThisNode"){
          if (($Status | where{$_.name -like $ClusterNode}).State -Like "Up"){
                Write-Host "Node already Up...Nothing to do in this step"
                return "Node is Already Up"
            }
                $clsresumestate=Invoke-Command -ComputerName $ClusterNode -ScriptBlock {resume-ClusterNode -name $args[0]} -ArgumentList $ClusterNode
                Sleep -Seconds 2
                return $clsresumestate.State
             }
                

    }
    Catch {
    Write-Warning -Message $Error[0]
    Write-Host "Failed to prepare the cluster, process cannot continue without this step... Aborting"
    break
    }
}
End{
Write-Host "Cluster Management is completed..."
    }
}
Export-ModuleMember Set-EMMClusterConfig 

Function Set-EMMDBActivationMoveNow{
Param(
[parameter(mandatory=$true,
            ValueFromPipeline=$true,
            ParameterSetName="ActiveOff",
            Position=0)]
[parameter(Mandatory=$true,
           ParameterSetName="ActiveOn",
           ValueFromPipeline=$true,
            Position=0)] $ServerName,
[parameter(mandatory=$false,ParameterSetName="ActiveOn")]$TargetServerNameForManualMove,
[parameter(mandatory=$false,ParameterSetName="ActiveOn")][switch]$BlockMode,
[parameter(mandatory=$false,ParameterSetName="ActiveOff")][validateset("IntrasiteOnly","Unrestricted")]$UnrestrictedOrIntrasite,
[parameter(mandatory=$false,ParameterSetName="ActiveOn")]$TimeoutBeforeManualMove=240

)

begin{
$FinalResult=""
}
Process{
    Try{
        ##Validation first
        $DBSetting=Get-MailboxServer
        if (($DBSetting | where {($_.DatabaseCopyAutoActivationPolicy -notlike "Blocked") -and ($_.name -notlike "*$ServerName*")}).count -eq 0){
            Write-Warning "There is no available server with a Activation Policy set to Unrestricted or IntrasiteOnly" 
            Write-Warning "Please ensure that there is at least one server available to handle the load..."
            $DBSetting
            break
            }
            if ($BlockMode){
                Set-MailboxServer $ServerName -DatabaseCopyActivationDisabledAndMoveNow $true
                sleep 1
                $DatabaseCopyPolicy=Get-MailboxServer $ServerName | Select name,DatabaseCopyActivationDisabledAndMoveNow,DatabaseCopyAutoActivationPolicy
                Write-Host "Please note that Activation policy as it might be needed later on if you are running this command without using the Start-EMMDAGEnabled command" 
                write-host $DatabaseCopyPolicy.DatabaseCopyAutoActivationPolicy -ForegroundColor DarkRed -BackgroundColor Yellow
                Set-MailboxServer $ServerName -DatabaseCopyAutoActivationPolicy Blocked
                if ((Get-MailboxDatabaseCopyStatus -Server $ServerName | Where{$_.Status -eq "Mounted"}).count -eq 0){
                Write-Host "No Active Database on this server found... The New DatabaseCopyAutoActivationPolicy is: " -NoNewline 
                Write-Host (Get-MailboxServer $ServerName | Select name,DatabaseCopyAutoActivationPolicy ).DatabaseCopyAutoActivationPolicy -ForegroundColor Green 
                return "No Active Database, Server is ready"
                }
                try{
                        Write-Host "Waiting for Database migration to complete, Timeout for this process is $($TimeoutBeforeManualMove) Seconds"
                        $i=0
                        Do{
                            Write-Host "." -NoNewline
                            $i++
                            sleep 1
                                if ($i -ge $TimeoutBeforeManualMove){
                                    Write-Host "The Number of database on this node are: "-NoNewline 
                                    $DBOnServer=Get-MailboxDatabaseCopyStatus -Server $ServerName | Where{$_.Status -eq "Mounted"}
                                    Write-host "Manual migration will start and move all DBs from $($ServerName) to $($TargetServerNameForManualMove)"
                                    Write-Host "The Value should be Zero"
                                        foreach ($singleDB in $DBOnServer){
                                        Write-Host $singleDB -ForegroundColor Green ##Delete
                                            $DBOnRemoteServerQL=Get-MailboxDatabaseCopyStatus -Server $TargetServerNameForManualMove | where {$_.databasename -like $singleDB.DatabaseName}
                                            Write-Host "Database Name: $($DBOnRemoteServerQL.DatabaseName)"
                                            Write-Host "CopyQueue: $($DBOnRemoteServerQL.CopyQueueLength)"
                                            Write-Host "ReplayQueue: $($DBOnRemoteServerQL.ReplayQueueLength)`n"
                                                if (($DBOnRemoteServerQL.CopyQueueLength) -or ($DBOnRemoteServerQL.ReplayQueueLength) -gt 0){
                                                    Write-Host "There are some pending Logs waiting for replay, I will wait till the process is finished"
                                                        do{
                                                            Write-Host "." -NoNewline
                                                            sleep 1
                                                          }
                                                          While (
                                                          ((($DBOnRemoteServerQL.CopyQueueLength) -and ($DBOnRemoteServerQL.ReplayQueueLength)) -ne 0)
                                                          )


                                                }
                                                Else{
                                                    Write-Host "Activating the database, please wait"
                                                    Move-ActiveMailboxDatabase -Identity $singleDB.DatabaseName -ActivateOnServer $TargetServerNameForManualMove  -Confirm:$false -SkipAllChecks ##DELETE
                                                    sleep -Seconds 5
                                                }
                                            }                                  


                                    }

                        }
                        while(
                            (Get-MailboxDatabaseCopyStatus -Server $ServerName | Where{$_.Status -eq "Mounted"}).count -ne 0
                        )
                        $DBMountedOnThisServer= (Get-MailboxDatabaseCopyStatus -Server $ServerName | Where{$_.Status -eq "Mounted"}).count
                        return $DBMountedOnThisServer
                    }

                    Catch [Microsoft.Exchange.Cluster.Replay.AmDbActionWrapperException]{
                    Write-Host "It seems that there still more logs to be shipped, Please check the error below and try to re-run the commands after sometime" -ForegroundColor Yellow
                    Write-Host "Or the database has been already activated on the remote server."
                    Write-Host "Set-EMMDBActivationMoveNow -ServerName $($ServerName) -TargetServerName $($TargetServerNameForManualMove) -Blocked -timeout 200"
                    Write-Host $_.exception.message
                    return "Require review, Please Run Get-MailboxDatabaseCopyStatus"
                    }
                    catch{
                    Write-Warning $Error[0]
                    break
                    }
      
            }
            Else{
            Write-Host "Leaving Block Mode"
            try{

                Set-MailboxServer $ServerName -DatabaseCopyAutoActivationPolicy $UnrestrictedOrIntrasite
                Set-MailboxServer $ServerName -DatabaseCopyActivationDisabledAndMoveNow $false
                sleep 1
                $validation= Get-MailboxServer $ServerName | Select DatabaseCopyAutoActivationPolicy
                $FinalResult=$validation.DatabaseCopyAutoActivationPolicy
                return $FinalResult
                }
                catch{
                Write-Host $Error[0]
                break
                }

            }


    }
    Catch{
    Write-Host $Error[0]
    break

    }
}
    End{
        Write-Host "Activation configuration are completed..."
    }
}

Export-ModuleMember Set-EMMDBActivationMoveNow

Function Test-EMMReadiness{
param(
[parameter(mandatory=$True,ValueFromPipeline=$true,Position=0)]$SourceServer
)
   
   Process{
   Write-Host "This process will check the server readiness"
   Write-Host "There will be no move or any change to the environement, just a check"

    try{

       Write-Host "Testing Exchange Ports reachability ..."
        Get-ExchangeServer | Test-NetConnection -Port 80 | select Computername,TcpTestSucceeded,RemotePort
        Get-ExchangeServer | Test-NetConnection -Port 443 | select Computername,TcpTestSucceeded,RemotePort

       if ((Get-ExchangeServer | Get-ServerComponentState -Component Hubtransport | where {($_.State -like "Active")  -and  ($_.Serverfqdn -notlike "*$SourceServer*")}).state.count -eq 0){
            Write-warning "You Dont have any additional Node with a Hubtransport State set to Active"
            Get-ExchangeServer | Get-ServerComponentState -Component Hubtransport
            }
            Else{
              (get-exchangeserver).foreach{ Write-Host "The Current Status for HubTransport Exchange Componenet is " -NoNewline ; Write-Host (Get-ServerComponentState -Identity $_ -Component Hubtransport).state -ForegroundColor Green}
            }

       if ((Get-ExchangeServer | Get-ServerComponentState -Component ServerWideOffline | where {($_.State -like "Active")  -and  ($_.Serverfqdn -notlike "*$SourceServer*")}).state.count -eq 0){
            Write-warning "You Dont have any additional Node with a ServerWideOffline State set to Active"
            Get-ExchangeServer | Get-ServerComponentState -Component Hubtransport
            }
            Else{
            (get-exchangeserver).foreach{ Write-Host "The Current Status for ServerWideOffline Exchange Componenet is " -NoNewline ; Write-Host (Get-ServerComponentState -Identity $_ -Component ServerWideOffline).state -ForegroundColor Green}
            }

          $Status=Invoke-Command -ComputerName $SourceServer -ScriptBlock {(get-ClusterNode)}
          if (($Status | where {($_.state -like 'up') -and ($_.name -notlike $SourceServer)}).count -eq 0){
                Write-Host "WARNING: The numbe of available cluster is not enough, Please stop and resume one node at least" -ForegroundColor Red
                $Status
                }
                Else{
                Write-Host "The Cluster nodes seems to be available and good" -ForegroundColor Green
                }

                Write-Host "Checking Exchange Servers for Activation policy"
        $DBSetting=Get-MailboxServer
        if (($DBSetting | where {($_.DatabaseCopyAutoActivationPolicy -notlike "Blocked") -and ($_.name -notlike $SourceServer)}).count -eq 0){
            Write-Warning "There is no available server with a Activation Policy set to Unrestricted or IntrasiteOnly" 
            Write-Warning "Please ensure that there is at least one server available to handle the load..."
            $DBSetting
            }
            Else{
            Write-Host "Other servers are available :)" -ForegroundColor Green
            $DBSetting
            }

        Write-Host "Checking Service health:`n"
        
       (get-exchangeserver).foreach{ Write-Host "Number of Failed Service ... " -NoNewline ; Write-Host ((Test-ServiceHealth -Server $_).ServicesNotRunning).count -ForegroundColor Green ;(Test-ServiceHealth -Server $_ | where {$_.ServicesNotRunning.count -gt 0})}
       
        Write-Host "Checking Log size, make sure that there is no log queue or copy queue"
        (get-ExchangeServer).foreach{ Get-MailboxDatabaseCopyStatus $_.databasename | ft Name,Status,ContentIndexState,CopyQueueLength,ReplayQueueLength}

        Write-Host "Testing Replication Health"
        get-exchangeserver | Test-ReplicationHealth | ft -AutoSize

        }
        catch  {
        
        $Error[0]

        }
    }
    End{
    Write-Host "Process is completed.."
    }

}
Export-ModuleMember Test-EMMReadiness

Write-Host "Welcome to EMM (Exchange Maintenance Module)" -ForegroundColor Green
Write-Host "Please Give me a moment to load Exchange Snapin...." -ForegroundColor Green
Write-Host "One more tip: Run this Module using RunAsAdministrator " -ForegroundColor Green
try{
    if ((Get-PSSnapin).Name -notcontains 'microsoft.exchange.management.powershell.snapin'){
        Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction SilentlyContinue
       }
 }
catch{
Write-Warning "Ops, something went wrong, are you sure you have Exchange Powershell Snapin installed ?!`n"
$_.exception.message
}