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 } |