Exchange Server 2016 introduced the PowerShell cmdlet Get-MailboxServerRedundancy. This cmdlet helps you plan and prepare for Exchange Server maintenance by querying the current maintenance readiness of the database availability group (DAG).
Interestingly, there is no PowerShell help available for this vital cmdlet. Microsoft Docs or Exchange Management Shell's Get-Help provide any valuable information.
When querying a DAG about the server redundancy status, the cmdlet's default output provides you with the essential information.
The default output contains information about:
This example shows the Get-MailboxServerRedundancy output of a six server DAG, before activating maintenance mode for server LOCEXS06.
Get-MailboxServerRedundancy -DatabaseAvailabilityGroup EXDAG01 Identity IsServerFound IsInMainten RepairUrgency SafeForMaintenance HealthInfoLastUpdateTime InAD ance -------- ------------- ----------- ------------- ------------------ ------------------------ LOCEXS01 True False Prohibited False 17.02.2020 09:10:11 LOCEXS02 True False Normal True 17.02.2020 09:10:11 LOCEXS03 True False Normal True 17.02.2020 09:10:11 LOCEXS06 True False Normal True 17.02.2020 09:10:11 LOCEXS05 True False Normal True 17.02.2020 09:10:11 LOCEXS04 True False Prohibited False 17.02.2020 09:10:11
As Exchange Administrator, you are most interested in the information displayed in columns RepairUrgency and SafeForMaintenance.
As you can see in this screenshot, no server is in maintenance mode. Servers S01 and S04 have a RepairUrgency state of Prohibited, and a SafeForMaintenance state of False. The latter tells us that we cannot activate maintenance mode for servers safely without risking mailbox database redundancy.
What is the reason for this? Let's have a look.
You can use the same cmdlet to query detailed information for each member server of the DAG. The default output for a single server does not provide any additional information on the server status.
Get-MailboxServerRedundancy -DatabaseAvailabilityGroup EXDAG01 -Identity LOCEXS01 Identity IsServerFound IsInMainten RepairUrgency SafeForMaintenance HealthInfoLastUpdateTime InAD ance -------- ------------- ----------- ------------- ------------------ ------------------------ LOCEXS01 True False Prohibited False 17.02.2020 09:11:11
Because we cannot activate maintenance mode for server LOCEXS01 safely, we are interested in identifying which redundancy state is responsible.
You can find this information by displaying the detailed server information.
Use the Format-List, or short FL, cmdlet to display the Get-MailboxServerRedundancy cmdlet output as a formatted list.
Get-MailboxServerRedundancy -DatabaseAvailabilityGroup EXDAG01 -Identity LOCEXS01 | FL RunspaceId : 70d82f8d-e6ca-4bfc-863f-11300a9784ff Identity : LOCEXS01 IsServerFoundInAD : True IsInMaintenance : False RepairUrgency : Prohibited SafeForMaintenance : False ServerContactedFqdn : LOCEXS04.VARUNAGROUP.DE HealthInfoCreateTime : 15.06.2018 15:16:19 HealthInfoLastUpdateTime : 17.02.2020 09:11:11 ServerFoundInAD : CurrentState: Active; LastActiveTransition: 15.06.2018 15:22:16; LastInactiveTransition: InMaintenance : CurrentState: Inactive; LastActiveTransition: 17.01.2020 09:07:02; LastInactiveTransition: 17.01.2020 10:42:02 AutoActivationPolicyBlocked : CurrentState: Inactive; LastActiveTransition: 09.01.2020 10:14:50; LastInactiveTransition: 09.01.2020 11:00:51 ActivationDisabledAndMoveNow : CurrentState: Inactive; LastActiveTransition: ; LastInactiveTransition: 15.06.2018 15:22:16 HighAvailabilityComponentStateOffline : CurrentState: Inactive; LastActiveTransition: 17.01.2020 09:07:02; LastInactiveTransition: 17.01.2020 10:42:02 CriticalForMaintainingAvailability : CurrentState: Inactive; LastActiveTransition: 31.01.2020 16:52:49; LastInactiveTransition: 31.01.2020 16:56:49 CriticalForMaintainingRedundancy : CurrentState: Active; LastActiveTransition: 29.01.2020 11:43:06; LastInactiveTransition: 29.01.2020 11:42:06 PotentiallyCriticalForMaintainingRedundancy : CurrentState: Active; LastActiveTransition: 01.02.2020 05:49:37; LastInactiveTransition: CriticalForRestoringAvailability : CurrentState: Inactive; LastActiveTransition: 06.05.2019 09:16:36; LastInactiveTransition: 06.05.2019 09:20:36 CriticalForRestoringRedundancy : CurrentState: Inactive; LastActiveTransition: 29.01.2020 11:42:06; LastInactiveTransition: 29.01.2020 11:43:06 HighForRestoringAvailability : CurrentState: Inactive; LastActiveTransition: 29.01.2020 11:42:06; LastInactiveTransition: 29.01.2020 11:43:06 HighForRestoringRedundancy : CurrentState: Inactive; LastActiveTransition: 10.02.2020 09:05:02; LastInactiveTransition: 10.02.2020 09:06:02 IsSafeForMaintenance : CurrentState: Inactive; LastActiveTransition: 03.11.2019 09:42:35; LastInactiveTransition: 12.11.2019 06:29:58 IsValid : True ObjectState : Unchanged
The lines 24-27 show the information we want to know. Both, the CriticalForMaintainingRedundancy and PotentiallyCriticalForMaintainingRedundancy parameters have a CurrentState value of Active. The Primary Activation Manager (PAM) considers the server availability critical to provide redundant availability of the database copies hosted by this server.
Each of state-parameter shows three pieces of information:
But there is still the bothering question of why are two of the six servers not safe for activating maintenance?
The reason is simple. The mailbox databases mounted by the member servers of the DAG have a different number of database copies. This configuration is due to data storage capacity constraints.
The mailbox databases storing primary user mailboxes use four database copies per database. Those copies are evenly distributed across all six mailbox servers. Mailbox database storing online archive mailboxes use three copies per database. This database copy layout allows for safely activating server maintenance for one server at a time without risk to database redundancy.
The servers LOCEXS01 and LOCEXS04 hold mailbox databases with just two copies per configured database. Placing one of those two servers into maintenance mode reduces the database availability for these mailbox databases to one. Therefore, PAM informs us that database redundancy is at risk when activating maintenance for those two servers.
This example shows the member server redundancy state while LOCEXS06 is in maintenance. The reason for monthly maintenance for installing Windows updates.
Maintenance was activated using the StartDagServerMaintenance.ps1 PowerShell script.
Get-MailboxServerRedundancy -DatabaseAvailabilityGroup indag01 Identity IsServerFound IsInMainten RepairUrgency SafeForMaintenance HealthInfoLastUpdateTime InAD ance -------- ------------- ----------- ------------- ------------------ ------------------------ LOCEXS01 True False Prohibited False 17.02.2020 11:04:12 LOCEXS02 True False Normal True 17.02.2020 11:04:12 LOCEXS03 True False Prohibited False 17.02.2020 11:04:12 LOCEXS06 True True High True 17.02.2020 11:04:12 LOCEXS05 True False Prohibited False 17.02.2020 11:04:12 LOCEXS04 True False Prohibited False 17.02.2020 11:04:12
Having a single server in maintenance has a significant impact on all other servers in the DAG. The servers LOCEXS03 and LOCEXS05 are not safe for maintenance as well. Activating maintenance for those two servers would affect the database redundancy for the databases hosted by those two servers.
After completing all maintenance tasks, e.g., installing Windows Updates or a new Exchange Server Cumulative Update, you end server maintenance using the PowerShell script StopDagServerMaintenance.ps1.
We query the server redundancy state again.
Get-MailboxServerRedundancy -DatabaseAvailabilityGroup indag01 Identity IsServerFound IsInMainten RepairUrgency SafeForMaintenance HealthInfoLastUpdateTime InAD ance -------- ------------- ----------- ------------- ------------------ ------------------------ LOCEXS01 True False Prohibited False 17.02.2020 11:23:12 LOCEXS02 True False Normal True 17.02.2020 11:23:12 LOCEXS03 True False Normal True 17.02.2020 11:23:12 LOCEXS06 True False High True 17.02.2020 11:23:12 LOCEXS05 True False Normal True 17.02.2020 11:23:12 LOCEXS04 True False Prohibited False 17.02.2020 11:23:12
Server LOCEXS06 is not in maintenance, but the RepairUrgency state is High. The local Exchange Server replication engine is still busy replicating and processing log files, and updating the search indices. When CopyQueueLength and ReplayQueueLength are back to 0, and ContentIndexStates are back to Healthy, the RepairUrgency switches to Normal.
You receive an error message when activating maintenance for an Exchange Server not safe for maintenance using StartDagServerMaintenance.ps1 -serverName [SERVER] In this case, you must use:
.\StartDagServerMaintenance.ps1 -serverName SERVERNAME -overrideMinimumTwoCopies:$true
Enjoy Exchange Server!
There are three different ways to configure new Exchange user mailboxes after these have been created.
The Exchange cmdlet extension is controlled by a scripting agent configuration file and a organizational setting to enable/disable the scripting agent.
A scripting agent configuration file sample (ScriptingAgentConfig.xml.sample) is located in
The sample needs to be renamed to ScriptingAgentConfig.xml, to be picked up the PowerShell engine.
As always, a slight reminder: Test any modification in a test environment first, before you use the extension in a production environment.
After succesfull testing and deployment, you need to enable the scripting agent using
Enable-CmdletExtensionAgent "Scripting Agent"
Even thought that you can extend mostly any Exchange cmdlet, this example covers the extension of the New-Mailbox and Enable-Mailbox cmdlets in a multi domain and multi AD site environment.
This extension disables the following CAS mailbox settings, after a new mailbox has been created:
What does the example do?
<?xml version="1.0" encoding="utf-8" ?> <Configuration version="1.0"> <Feature Name="MailboxProvisioning" Cmdlets="New-Mailbox,Enable-Mailbox"> <ApiCall Name="OnComplete"> If ($succeeded) { if (!($provisioningHandler.UserSpecifiedParameters.Archive -eq $true)) { # delay execution for 10 seconds, adjust as needed Start-Sleep -s 10 # validate parameters to use a not null parameter if ($provisioningHandler.UserSpecifiedParameters["Identity"] -ne $null) { $user = $provisioningHandler.UserSpecifiedParameters["Identity"].ToString() } elseif ($provisioningHandler.UserSpecifiedParameters["Name"] -ne $null) { $user = $provisioningHandler.UserSpecifiedParameters["Name"].ToString() } else { $user = $provisioningHandler.UserSpecifiedParameters["Alias"].ToString() } # view entire forest in a multi domain environment Set-AdServerSettings -ViewEntireForest:$true # fetch domain controllers in AD site} $server = Get-ExchangeServer $env:computername $DCs = Get-DomainController | ?{$_.adsite -eq $server.site} $CasMailbox = $null foreach($d in $DCs) { while($CasMailbox -eq $null) { # find a valid domain controller having the updated user object $CasMailbox = Get-CASMailbox $user -DomainController $d.dnshostname -ErrorAction SilentlyContinue # fetch DCs FQDN $WriteDC = $d.DnsHostName break } } try { # set CAS features as needed Set-CasMailbox $user -ActiveSyncEnabled:$false -ImapEnabled:$false -PopEnabled:$false -MapiHttpEnabled:$false -DomainController $WriteDC -ErrorAction SilentlyContinue } catch {} } } </ApiCall> </Feature> </Configuration>
After adding the PowerShell code to the ScriptingAgentConfig.xml file, the file needs to be distributed across all Exchange servers. For distribution of the scripting agent configuration file I personally recommend Paul Cunningham's PowerShell script.
Be aware of the fact, that the scripting agent Xml is being validated using a strict schema validation. The scripting agent Xml is case sensitive, as noted here.
The Microsoft 365 Virtual Marathon took place on May 27-28 2020.
The recording of my session "Exchange Hybrid - What, Why, and How" is available on YouTube.
Browse all recordings of the Microsoft 365 Virtual Marathon here: https://www.youtube.com/channel/UCrtmT6Ir1MIs0ZES7sKMmqA
Enjoy!
When you plan to implement an Exchange Hybrid Configuration between your on-premises Exchange Organization and Exchange online you have to choose between two variants and five operating modes. It is not as complicated as it sounds.
I have written a blog post about the different options available.
The post is published in ENow's ESE blog.
Enjoy.
On October 26th ENow hosted the webinar "Deploy Exchange 2016 On-prem and Hybrid Webinar".
"Support for Office 365 is part of the Exchange 2016 DNA. Yet, traditional on-premises deployment still make up a large number of Exchange deployments worldwide. In this session, we will cover the various deployment options and take a peek into what it takes to install, configure and manage an Exchange 2016 on-premises or Exchange 2016 hybrid deployment."
I came across an interesting issue when setting up a new Exchange 2013 server in an Exchange organization having the cmdlet extension agent enabled.
As mentioned in my last post Exchange setup checks for the existence of the ScriptingAgentConfig.xml file when agent extenstion is enabled in the Exchange organization. It turned out that this ist not only true when you install an Exchange update using /mode:update, but as well when installing a new Exchange server using /mode:install.
The following error occurs when Exchange Management Tools are provisioned.
Configuring Microsoft Exchange Server Preparing Setup COMPLETED Stopping Services COMPLETED Copying Exchange Files COMPLETED Language Files COMPLETED Restoring Services COMPLETED Language Configuration COMPLETED Exchange Management Tools FAILED The following error was generated when "$error.Clear(); Set-WERRegistryMarkers; " was run: "Microsoft.Exchange.Provisioning.ProvisioningBrokerException: Provisioning layer initialization failed: '"Scripting Agent initialization failed: "File is not found: 'C:\Program File s\Microsoft\Exchange Server\V15\Bin\CmdletExtensionAgents\ScriptingAgentConfig.xml'.""' ---> Microso ft.Exchange.Provisioning.ProvisioningException: "Scripting Agent initialization failed: "File is not found: 'C:\Program Files\Microsoft\Exchange Server\V15\Bin\CmdletExtensionAgents\ScriptingAgentConf ig.xml'."" ---> System.IO.FileNotFoundException: "File is not found: 'C:\Program Files\Microsoft\Exc hange Server\V15\Bin\CmdletExtensionAgents\ScriptingAgentConfig.xml'." at Microsoft.Exchange.ProvisioningAgent.ScriptingAgentConfiguration.Initialize(String xmlConfigPa th) at Microsoft.Exchange.ProvisioningAgent.ScriptingAgentConfiguration..ctor(String xmlConfigPath) --- End of inner exception stack trace --- at Microsoft.Exchange.ProvisioningAgent.ScriptingAgentConfiguration..ctor(String xmlConfigPath) at Microsoft.Exchange.ProvisioningAgent.ScriptingAgentClassFactory.get_Configuration() at Microsoft.Exchange.ProvisioningAgent.ScriptingAgentClassFactory.GetSupportedCmdlets() at Microsoft.Exchange.Provisioning.ProvisioningBroker.BuildHandlerLookupTable(CmdletExtensionAgen t[] enabledAgents, Exception& ex) --- End of inner exception stack trace --- at Microsoft.Exchange.Provisioning.ProvisioningLayer.GetProvisioningHandlersImpl(Task task) at Microsoft.Exchange.Provisioning.ProvisioningLayer.GetProvisioningHandlers(Task task) at Microsoft.Exchange.Configuration.Tasks.Task.<BeginProcessing>b__4() at Microsoft.Exchange.Configuration.Tasks.Task.InvokeNonRetryableFunc(Action func, Boolean termin atePipelineIfFailed)".
As expected a fresh Exchange install contains the sample file only. The following screenshot shows the Exchange Management Shell and the releated folder in the background.
The only solution currently known to me is to disable the cmdlet extension agent until the setup of the new Exchange server has finished.
Disable-CmdletExtensionAgent "Scripting Agent"
Having the cmdlet extension agent disabled the setup finishes without any issues. Don't forget to copy the cmdlet extension Xml file to the newly built server and to enable the cmdlet extension agent again.
You need assistance with your Exchange Server setup? You have questions about your Exchange Server infrastructure and going hybrid? You are interested in what Exchange Server 2016 has to offer for your environment?
Contact me at thomas@mcsmemail.de Follow at https://twitter.com/stensitzki
This blog post is about creating an Twitter Bot to tweet messages using Azure Automation. The steps and the script itself are based on Trevor Sullivan's TechNet Gallery post. His post assumes that you are familiar with some Azure Automation steps. So I am going to describe the required steps in more detail.
You'll need the following components to setup your personal Twitter Bot.
First you'll need to create a Twitter application to reference your Azure Automation bot. The authentication information of your Twitter application will be needed in step 2.
The information required are
You need to create a new Twitter application by accesssing the following link: https://apps.twitter.com/
Ensure that you've added your mobile phone number to your Twitter account before creating a new Twitter application. This is a requirement for creating Twitter applications.
Log on to Twitter using the Twitter account you want your Twitter Bot to post as.You'll see something similar as this:
Just click Create New App.
Enter the information as needed. The application name must be a globally unique name. So it might be tricky to find a suitable application name. Click Create your Twitter application to finally create the application.
Select Keys and Access Tokens and copy the Consumer Key (API Key) and the Consumer Secret (API Secret) value into a text editor of your choice.
Further down on the same web page you'll find the Your Access Token section.
Click Create my access token.
After you've created the access token, copy the Access Token and the Access Token Secret to your text editor document. You'll need all four values in just a moment.
The Azure automation component will require application credentials for posting Tweets on your behalf. These required credentials are stored in a JSON file. Yo do not need top worry about the JSON data format.
The PoshTwit PowerShell module helps you to create the required JSON file.
The simpliest way to get the PoshTwit module is by installing the module directly from the PowerShell Gallery using an Administrative PowerShell session.
Install-Module PoshTwit
If you cannot use the Install-Module cmdlet, use the link provided in the Links section.
Remember that this step is only needed to create the JSON file containing the required credential information for Azure Automation.
After you've successfully installed the PoshTwit module, call the Set-PoshTwitToken cmdlet using all four Twitter app credential information to create the authentication JSON file.
Set-PoshTwitToken -ConsumerKey [YourConsumerKey] -ConsumerSecret [YourConsumerSecret] -AccessToken [YourAccessToken] -AccessSecret [AccessSecret]
The JSON file wil be created in the PowerShell module installtion folder. Which is by default:
C:\Program Files\WindowsPowerShell\Modules\PoshTwit\0.1.6
The version number might differ depending on the version you've installed.
Open the token.json file and copy the content to your text editor. The content of the token.json file will be the password for the Azure Automation credential object. The content will look like this:
{"ConsumerKey":"9FX***********","ConsumerSecret":"4kIxa***********","AccessToken":"24540854***********","AccessSecret":"OSYP***********"}
You should see your Twitter application authentication information. You will nedd to copy & paste this string including the curly brackets as account credentials at a later step.
Log on to the Azure Portal and create a new Azure Automation account. The Azure Automation will host your automation runbooks, variables and other settings. You can have multiple Azure Automation accounts. This is especially usefull when you want to delegate access and control of Automation accounts to different members of staff.
Click '+', enter Auto as search text and select Automation.
Click Create on the next blade.
Configure your new Azure Automation account by using a unique name, select the appropriate Azure subscription, create a new Resource Group or use an existing, select the Azure location, leave Yes as the default option for creating an Azure Run As account, select the checkbox to pin the Azure Automation account to your Azure dashboard, and click Create.
After you've been redirected to the Azure Dashboard wait for the Azure Automation Account to be created. If you are not redirected to the Azure Automation blade automatically, select the Automation Account tile on the Azure dashboard.
Select Process Automation - Runbooks. You'll notice two tutorials and two tutorial scripts which are automatically provisioned for you.
Click Add a runbook to create the Twitter Bot runbook.
The next step requires that you've download the Tweet-PowerShellTips.ps1 script. If you haven't, download it now.
Select Import an exisiting runbook and browse for the downloaded PowerShell script on the next blade. After selecting the PowerShell file the fiel will be uploaded and validated automatically. The Runbook type and Runbook name properties will be set automatically for you. Just enter a short description for your runbook. Click Create.
The uploaded PowerShell script utilizes the PoshTwit PowerShell module. This PowerShell module needs to be available within the Azure Automation account as well. Additonal PowerShell modules are configured within the Shared Resources section of your Azure Automation account. The PoshTwit module is added from the PowerShell Gallery.
Select Modules Gallery, enter PoshTwit as search text and press Enter, click the PoshTwit search result tile.
Click Import to import the PowerShell module to thre shared ressources of your Azure Automation account. Click OK on the following blade. Close the PoshTwit module blade.
Now you'll add the required Twitter application credentials to the Shared Resources of the Azure Automation account.
Select Shared Resources - Credentials and click Add a credential.
Use TwitterCredential as Name and User name. The variable is accessed by the PowerShell script using the cmdlet
Get-AutomationPSCredential -Name TwitterCredential
Now copy and paste the full JSON file content as Password and Password confirmation. Click Create to save the new credential information.
Select the new runbook from the list of runbooks to edit the runbook properties.
Click Edit to edit the PowerShell code to adjust the tweets to match your needs (at least). YOu are able to edit the PowerShell code directly from the browser window.
Ensure to click Save, after you've edited the PowerShell code.
Each time you've edited an Azure Runbook, you need to publish the new version of the runbook. Click Publish and confirm the publishing with Yes.
You can test your runbook using the Test pane. The script itself will not write any output to the output windows, as the script does not use any Write-Output cmdlets.
You can add the following PowerShell code to the script to output the Tweet Id and Tweet text.
Write-Output "Publish Tweet $($TweetId) | $($TweetList[$TweetId])"
As a last step you need to create a schedule to post random tweets automatically. Automation schedules are created as shared resources again.
Select Shared Resources - Schedules and click Add a schedule.
Configure a schedule name, the start date, the schedule time zone, and the recurring interval. Click Create.
Select your runbook to link the just created schedule.
Select Schedules and click Add a schedule.
Click Link a schedule to your runbook, select the schedule and click OK.
The runbook schedules overview will show an information when the next run will be initiated.
That's it. Your Azure Automation Twitter Bot is up and running.
Now you can simply edit the runbook, add new tweets to the string array, save the changes and publish the changed runbook for production use. As long as the changes are not published, Azure Automation will use the last published version of the runbook.
Enjoy Azure.
This is the source code of the updated PowerShell script.
# Array of tweets # Ensure that the length of each tweet does not exceed 140 characters # Ensure to have at least 2 entries $TweetList = @( 'Find more #PowerShell #scripts at http://scripts.granikos.eu ', 'More #Office365 and #MSFTExchange tips at http://JustCantGetEnough.granikos.eu ' ) # Get a tweet text by random $TweetId = Get-Random -Minimum 0 -Maximum ($TweetList.Count - 1); # Fetch automation credentials $TwitterCredential = Get-AutomationPSCredential -Name TwitterCredential; $TwitterCredential = ConvertFrom-Json -InputObject $TwitterCredential.GetNetworkCredential().Password; # Provision the tweet $Tweet = @{ ConsumerKey = $TwitterCredential.ConsumerKey; ConsumerSecret = $TwitterCredential.ConsumerSecret; AccessToken = $TwitterCredential.AccessToken; AccessSecret = $TwitterCredential.AccessSecret; Tweet = $TweetList[$TweetId]; }; # Publish the tweet Publish-Tweet @Tweet;