Blog and Scripts

PowerShell HTML Weekly AD and Mailbox Report

As an Exchange admin, one of the hardest things to keep an eye on is AD. Not in the sense of your replication or DC health, but that everything is where it is supposed to be. Some of the most trivial calls end up being related to a distribution group, shared mailbox, or room mailbox not being in the proper OU, so the relevant team cannot manage them. Unfortunately there isn’t a tool out there (that I know of) that will help you keep an eye on this sort of thing without pounding you with notifications. For that reason, I created a script that I run weekly as a scheduled task. This script greets me every Monday morning, and shows me the following:

  • Mailboxes on Litigation Hold
  • Mailboxes without a Retention Policy
  • All the existing Journaling Rules
  • The user objects in AD that have been inactive for over 60 days
  • Shared Mailboxes in the wrong OU
  • Room Mailboxes in the wrong OU
  • Distribution Groups in the wrong OU
  • Shared Mailboxes with enabled user objects
  • Room Mailboxes with enabled user objects

And here’s what the report looks like in an Outlook 2016 client:

Now I don’t expect this script to work for every environment. The place I developed the script for has a pretty basic AD hierarchy, with singular specific root OUs for each type of mailbox and distribution groups. If, for example, you keep shared mailboxes in multiple OUs you will have to make some changes to the filters in each relevant cmdlet. The good news is the framework is already here.

Hat tip to Karsten Schneider at ilikesharepoint.de for providing me the starting point on this script.

AD Management Tools [Windows 10]

Stepping away from Exchange for a minute, I was recently reminded that some people don’t have an Admin server, but still find themselves needing to manage an Active Directory environment. If that is the case, you’ll need to install the Remote Server Administration Tools on your PC. If you’re running Windows 7 you can download the installer here. For the AD Management tools Windows 10, you can download the tools here if you have an older build. For newer builds select “Manage optional features” in Settings and click “Add a feature” to see the list of available RSAT tools. From there you can pick the tools you would like to install.

Exchange 2010 Health Check Script

A few years ago when I was leading my first Exchange team, one of the very first lessons I learned was that everyone has a different idea for how to validate a change. When I started my IT career, I was taught after any change you reboot, and when the system comes back up you check the event logs, services, and any relevant application logs to make sure everything is running as expected. However, as I found out after delegating a patching change to a subordinate, everyone didn’t learn that same lesson. Hence, the Exchange 2010 Health Check script.

Start PowerShell to run the Exchange 2010 Health Check script
Server restart – friend or foe?

The Change

During that change window, three out of four mailbox servers didn’t start their Mail Submission service. This was on a weekend of course, and IIRC no one really noticed until Sunday evening. Once I got the call though, I knew exactly what the issue was. I remoted in to the network as soon as I could, started the Mail Submission services, and dreaded going to work the next morning.

The Aftermath

That night I thought about the two questions that were going to come my way the next morning- how did this happen, and how do we prevent it in the future? The first question is relatively easy. Sometimes, for some reason, Exchange services just don’t want to start after a reboot. This is something of a cop-out of course, but I’ve seen this service hang at reboot in most of the environments I’ve worked on. The other question of how we prevent this in the future, was going to take a bit of work.

The Solution

Training is of course the simple answer, but that isn’t the answer that I would want to hear. Especially after an outage of that magnitude.

The solution I came up with was the following Exchange 2010 Health Check Script. This script was to be run against any Exchange 2010 server post-change, and would gather the following server stats and perform the relevant tests:

  • All Servers
    • Status of Services set to startup automatically
    • All of the Error and Warning logs in the last 100 Application log entries
    • All of the Error and Warning logs in the last 100 System log entries
  • For CASes
    • All active connections to all services (based off this function)
    • Test POP connectivity
    • Test IMAP connectivity
    • Test OWA connectivity
    • Test EWS connectivity
  • For Hub Transports
    • Test SMTP connectivity
    • Report on messages in queue
  • For Mailbox Servers
    • Test DAG health
    • Report on the mailbox database copy statuses
    • Test MAPI connectivity
    • Test mailflow

In situations where we had multi-role servers, namely CAS/HTs, the script would run the tests and generate reports for each role. And once run, the admin will be prompted to email the Messaging team’s distribution group so everyone can have eyes on the results.

#################################################################
# Exchange 2010 Health Check Script                             #
# This script generates an Exchange Server health check report  #
# And can email it to a specified recipient			#
#								#
# Created by Eric Kukkuck 3/3/2015				#
# https://MSExchangeHelp.com					#
#################################################################

$OrgName = "MyCo"
$TranscriptPath = "D:\ABZ\Reports\E2010HealthCheck_" + (Get-Date -f yyyy-MM-dd_HH-mm) + ".txt"

$POPEnabled = "$False"
$IMAPEnabled = "$True"

$SMTPServer = "smtp.domain.intranet"
$SMTPSender = "postmaster@domain.net"
$SMTPRecipient = "MessagingTeamDG@domain.net"

start-transcript -Path $TranscriptPath

    Write-Host ""
write-host "Welcome to the $OrgName Exchange Server Health Check script. This script is to be run upon completion of any changes made to an Exchange sever and the results emailed to $SMTPRecipient." -foregroundcolor "green"

    write-host ""
Write-Host "Enter server name to validate:" -ForegroundColor "Yellow"

    write-host ""
$Server = Read-Host

    write-host ""
$ServerRole = get-ExchangeServer $Server | Select ServerRole

function Get-CASActiveUsers {
  [CmdletBinding()]
    param(
    [Parameter(Position=1, ParameterSetName="Value", Mandatory=$true)]
    [String[]]$ComputerName,
    [Parameter(Position=0, ParameterSetName="Pipeline", ValueFromPipelineByPropertyName=$true, Mandatory=$true)]
    [String]$Name
  )

  process {
    switch($PsCmdlet.ParameterSetName) {
      "Value" {$servers = $ComputerName}
      "Pipeline" {$servers = $Name}
    }
    $servers | %{
      $RPC = Get-Counter "\MSExchange RpcClientAccess\User Count" -ComputerName $_
      $OWA = Get-Counter "\MSExchange OWA\Current Unique Users" -ComputerName $_
      $AS = Get-Counter “\MSExchange ActiveSync\Current Requests” -ComputerName $_
      $AB = Get-Counter "\MSExchangeAB\NSPI Connections Current" -ComputerName $_
      $IMAP =  If($IMAPEnabled -eq 'True'){get-counter "\MSExchangeimap4(1)\current connections" -ComputerName $_}Else{'Disabled'}
      $POP = If($POPEnabled -eq 'True'){Get-Counter "\MSExchangePOP3(_Total)\Connections Current" -ComputerName $_}Else{'Disabled'}
      $EWS = Get-Counter "\W3SVC_W3WP(*msexchangeservicesapppool)\Active Requests" -ComputerName $_
      New-Object PSObject -Property @{
        Server = $_
        "RPC Client Access" = $RPC.CounterSamples[0].CookedValue
        "Outlook Web App" = $OWA.CounterSamples[0].CookedValue
	"ActiveSync" = $AS.CounterSamples[0].CookedValue
	"Address Book" = $AB.CounterSamples[0].CookedValue
	"IMAP" = If($IMAP -eq'Disabled'){'Disabled'}Else{$IMAP.CounterSamples[0].CookedValue}
	"POP" = If ($POP -eq 'Disabled'){'Disabled'}Else {$POP.CounterSamples[0].CookedValue}
	"EWS" = $EWS.CounterSamples[0].CookedValue
      } | Select-Object Server,"RPC Client Access","Outlook Web App",ActiveSync,IMAP,POP,"Address Book",EWS | FT -Auto
    }
  }
}

if ($ServerRole -like '@{ServerRole=ClientAccess}'){write-host 'Dedicated CAS Server Detected...' -foregroundcolor "magenta"
write-host ""
write-host 'Service Status (Displays Services that are configured for Automatic startup but are in a stopped state):' -foregroundcolor "green"
Get-WmiObject Win32_Service -computername $Server |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
# process them; in this example we just show them:
Format-Table -AutoSize @(
    'DisplayName'
    @{ Expression = 'State'; Width = 9 }
    @{ Expression = 'StartMode'; Width = 9 }
    'StartName'
)
write-host "All 'Error' & 'Warning' Application log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName application | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host "All 'Error' & 'Warning' System log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName System | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host 'Active Client Connections:' -foregroundcolor "green"

get-casactiveusers -computername $Server

write-host 'IMAP Connectivity Validation:' -foregroundcolor "green"

If($IMAPEnabled -eq 'False'){Write-Host ""}

If($IMAPEnabled -eq 'True'){Test-ImapConnectivity -ClientAccessServer $Server | fl Result,scenariodescription}Else{'IMAP Service disabled, continuing to next test...'}

If($IMAPEnabled -eq 'False'){Write-Host ""}

write-host 'POP Connectivity Validation:' -foregroundcolor "green"

If($POPEnabled -eq 'False'){Write-Host ""}

If($POPEnabled -eq 'True'){Test-PopConnectivity -ClientAccessServer $Server | fl Result,ScenarioDescription}Else{'POP Service disabled, continuing to next test...'}

If($POPEnabled -eq 'False'){Write-Host ""}

write-host 'Outlook Web Services Connectivity Validation:' -foregroundcolor "green"

Test-OutlookWebServices -ClientAccessServer $Server | ft Type,Message -auto -wrap

write-host 'OWA Validation:' -foregroundcolor "green"

Test-OwaConnectivity -ClientAccessServer $Server | fl Result,URL,AuthenticationMethod,ScenarioDescription

write-host 'Web Services Validation:' -foregroundcolor "green"

Test-WebServicesConnectivity -ClientAccessServer $Server | ft Result,Scenario,ScenarioDescription -auto

}

if ($ServerRole -like '@{ServerRole=HubTransport}'){write-host 'Dedicated Hub Transport Server Detected...' -foregroundcolor "magenta"
write-host ""
write-host 'Service Status (Displays Services that are configured for Automatic startup but are in a stopped state):' -foregroundcolor "green"
Get-WmiObject Win32_Service -computername $Server |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
# process them; in this example we just show them:
Format-Table -AutoSize @(
    'DisplayName'
    @{ Expression = 'State'; Width = 9 }
    @{ Expression = 'StartMode'; Width = 9 }
    'StartName'
)
write-host "All 'Error' & 'Warning' Application log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName application | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host "All 'Error' & 'Warning' System log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName System | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host 'SMTP Connectivity Validation:' -foregroundcolor "green"

Test-SmtpConnectivity $Server | FT

write-host 'Transport Queue Status:' -foregroundcolor "green"

get-queue -server $Server | FT

}

if ($ServerRole -like '@{ServerRole=ClientAccess, HubTransport}'){write-host 'CAS\HT Multirole Server Detected...' -foregroundcolor "magenta"
write-host ""
write-host 'Service Status (Displays Services that are configured for Automatic startup but are in a stopped state):' -foregroundcolor "green"
Get-WmiObject Win32_Service -computername $Server |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
# process them; in this example we just show them:
Format-Table -AutoSize @(
    'DisplayName'
    @{ Expression = 'State'; Width = 9 }
    @{ Expression = 'StartMode'; Width = 9 }
    'StartName'
)
write-host "All 'Error' & 'Warning' Application log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName application | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host "All 'Error' & 'Warning' System log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName System | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host 'Active Client Connections:' -foregroundcolor "green"

get-casactiveusers -computername $Server

write-host 'IMAP Connectivity Validation:' -foregroundcolor "green"

If($IMAPEnabled -eq 'False'){Write-Host ""}

If($IMAPEnabled -eq 'True'){Test-ImapConnectivity -ClientAccessServer $Server | fl Result,scenariodescription}Else{'IMAP Service disabled, continuing to next test...'}

If($IMAPEnabled -eq 'False'){Write-Host ""}

write-host 'POP Connectivity Validation:' -foregroundcolor "green"

If($POPEnabled -eq 'False'){Write-Host ""}

If($POPEnabled -eq 'True'){Test-PopConnectivity -ClientAccessServer $Server | fl Result,ScenarioDescription}Else{'POP Service disabled, continuing to next test...'}

If($POPEnabled -eq 'False'){Write-Host ""}

write-host 'Outlook Web Services Connectivity Validation:' -foregroundcolor "green"

Test-OutlookWebServices -ClientAccessServer $Server | ft Type,Message -auto -wrap

write-host 'OWA Validation:' -foregroundcolor "green"

Test-OwaConnectivity -ClientAccessServer $Server | fl Result,URL,AuthenticationMethod,ScenarioDescription

write-host 'Web Services Validation:' -foregroundcolor "green"

Test-WebServicesConnectivity -ClientAccessServer $Server | ft Result,Scenario,ScenarioDescription -auto

write-host 'SMTP Connectivity Validation:' -foregroundcolor "green"

Test-SmtpConnectivity $Server | FT

write-host 'Transport Queue Status:' -foregroundcolor "green"

get-queue -server $Server | FT

}

if ($ServerRole -like '@{ServerRole=Mailbox, ClientAccess, HubTransport}'){write-host 'CAS\HT\MB Multirole Server Detected...' -foregroundcolor "magenta"
write-host ""
write-host 'Service Status (Displays Services that are configured for Automatic startup but are in a stopped state):' -foregroundcolor "green"
Get-WmiObject Win32_Service -computername $Server |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
# process them; in this example we just show them:
Format-Table -AutoSize @(
    'DisplayName'
    @{ Expression = 'State'; Width = 9 }
    @{ Expression = 'StartMode'; Width = 9 }
    'StartName'
)
write-host "All 'Error' & 'Warning' Application log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName application | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host "All 'Error' & 'Warning' System log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName System | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host 'Active Client Connections:' -foregroundcolor "green"

get-casactiveusers -computername $Server

write-host 'IMAP Connectivity Validation:' -foregroundcolor "green"

If($IMAPEnabled -eq 'False'){Write-Host ""}

If($IMAPEnabled -eq 'True'){Test-ImapConnectivity -ClientAccessServer $Server | fl Result,scenariodescription}Else{'IMAP Service disabled, continuing to next test...'}

If($IMAPEnabled -eq 'False'){Write-Host ""}

write-host 'POP Connectivity Validation:' -foregroundcolor "green"

If($POPEnabled -eq 'False'){Write-Host ""}

If($POPEnabled -eq 'True'){Test-PopConnectivity -ClientAccessServer $Server | fl Result,ScenarioDescription}Else{'POP Service disabled, continuing to next test...'}

If($POPEnabled -eq 'False'){Write-Host ""}

write-host 'Outlook Web Services Connectivity Validation:' -foregroundcolor "green"

Test-OutlookWebServices -ClientAccessServer $Server | ft Type,Message -auto -wrap

write-host 'OWA Validation:' -foregroundcolor "green"

Test-OwaConnectivity -ClientAccessServer $Server | fl Result,URL,AuthenticationMethod,ScenarioDescription

write-host 'Web Services Validation:' -foregroundcolor "green"

Test-WebServicesConnectivity -ClientAccessServer $Server | ft Result,Scenario,ScenarioDescription -auto

write-host 'SMTP Connectivity Validation:' -foregroundcolor "green"

Test-SmtpConnectivity $Server | FT

write-host 'Transport Queue Status:' -foregroundcolor "green"

get-queue -server $Server | FT

write-host 'DAG Node Status:' -foregroundcolor "green"

test-replicationhealth $Server | ft

write-host 'Database Status:' -foregroundcolor "green"

get-mailboxdatabasecopystatus -server $Server |select Identity,Status,ActiveDatabaseCopy,ContentIndexState,CopyQueueLength,ReplayQueueLength | sort Identity | ft

write-host 'MAPI Connectivity Validation:' -foregroundcolor "green"

Test-MAPIConnectivity -Server $Server | FT

write-host 'Mailflow Validation:' -foregroundcolor "green"

Test-mailflow -Identity $Server | FT TestMailflowResult,MessageLatencyTime

}

if ($ServerRole -like '@{ServerRole=Mailbox}'){write-host 'Dedicated Mailbox Server Detected...' -foregroundcolor "magenta"
write-host ""
write-host 'Service Status (Displays Services that are configured for Automatic startup but are in a stopped state):' -foregroundcolor "green"
Get-WmiObject Win32_Service -computername $Server |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
# process them; in this example we just show them:
Format-Table -AutoSize @(
    'DisplayName'
    @{ Expression = 'State'; Width = 9 }
    @{ Expression = 'StartMode'; Width = 9 }
    'StartName'
)
write-host "All 'Error' & 'Warning' Application log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName application | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host "All 'Error' & 'Warning' System log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName System | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host 'DAG Node Status:' -foregroundcolor "green"

test-replicationhealth $Server | ft

write-host 'Database Status:' -foregroundcolor "green"

get-mailboxdatabasecopystatus -server $Server |select Identity,Status,ActiveDatabaseCopy,ContentIndexState,CopyQueueLength,ReplayQueueLength | sort Identity | ft

write-host 'MAPI Connectivity Validation:' -foregroundcolor "green"

Test-MAPIConnectivity -Server $Server | FT

write-host 'Mailflow Validation:' -foregroundcolor "green"

Test-mailflow -Identity $Server | FT TestMailflowResult,MessageLatencyTime

}

if ($ServerRole -like '*EDGE*'){write-host 'Edge Server Detected...' -foregroundcolor "magenta"
write-host ""
write-host 'Service Status (Displays Services that are configured for Automatic startup but are in a stopped state):' -foregroundcolor "green"
Get-WmiObject Win32_Service -computername $Server |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
# process them; in this example we just show them:
Format-Table -AutoSize @(
    'DisplayName'
    @{ Expression = 'State'; Width = 9 }
    @{ Expression = 'StartMode'; Width = 9 }
    'StartName'
)
write-host "All 'Error' & 'Warning' Application log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName application | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host "All 'Error' & 'Warning' System log entries in the most recent 100 entries:" -foregroundcolor "green"

Get-EventLog -Newest 100 -ComputerName $Server -LogName System | where {($_.Entrytype -like 'error') -or ($_.EntryType -like 'warning')} | ft TimeGenerated,EntryType,Source,Message

write-host 'Transport Queue Status:' -foregroundcolor "green"

get-queue -server $Server | FT

}

stop-transcript

#$Body = Get-Content -Path C:\ESHC.txt | Out-String

    write-host ""
write-host "Would you like to send this report to the $SMTPRecipient address?" -foregroundcolor "yellow"
    write-host ""
    write-host "Y - Yes" -foregroundcolor "yellow"
    write-host "N - No" -foregroundcolor "red"
    write-host "" 
    write-host -nonewline "Type your choice and press Enter:" -foregroundcolor "yellow"
    $SendMail = read-host
    write-host ""
    $ok = @("Y","N","X") -contains $SendMail
    if ( -not $ok) { write-host "Invalid selection" }

if($SendMAil -eq 'Y'){send-mailmessage -From:$SMTPSender -To:$SMTPRecipient -SMTPServer:$SMTPServer -Subject:$Server' - Post-change Report' -attach $TranscriptPath -Body:"Server report"
write-host 'Report complete, returning to the command prompt...' -foregroundcolor "yellow"}
if($SendMail -eq 'N'){write-host 'Report complete, returning to the command prompt...' -foregroundcolor "yellow"}
    write-host ""

If for some reason the text above doesn’t work, you can download the file here (rename .txt to .ps1)

Exchange Management Tools (Exchange 2010)

Out of the box, Exchange 2010 comes with two primary tools for managing your environment – the Exchange Management Console (EMC) and the Exchange Management Shell (EMS). With just these two tools a good admin can do almost anything management asks of them 😉

The Exchange 2010 Management Console (EMC)
The Exchange Management Console (EMC). Thick management tool installs 4 life!

The EMC is the GUI tool, which is great to get a quick idea of how a server is configured, how many Transport Rules you have, or do anything that only has to be done a handful of times.

The EMS on the other hand, is great for when you need to pull information from dozens, hundreds, or thousands of items. Imaging if someone asked you who has the largest mailbox in the environment. You could click through thousands of mailboxes in the EMC, or just run a quick cmdlet in the EMS and let PowerShell do the heavy lifting. The EMS also allows you to run more complex scripts that will make your life a lot easier.

The Exchange Management Shell (EMS)
The Exchange Management Shell (EMS). I know consoles can be intimidating but in the right hands, the EMS can get you anything you want.

With these two Exchange management tools, any Exchange admin can rule the world (of Exchange)!

Exchange 2010 Deleted Mailbox Not Showing in Disconnected Mailbox View (Clean-MailboxDatabase)

Earlier today I ran into an issue where an Exchange 2010 deleted mailbox wasn’t showing in the Disconnected Mailbox view of the EMC as expected.

No disconnected mailbox in this screenshot...
Exchange 2010 EMC, where’s my missing mailbox?

Knowing that sometimes the EMC doesn’t quite reflect current reality, I decided to run the following in the EMS to see what I could find:

Get-MailboxDatabase | get-mailboxstatistics | where {$_.disconnectreason -ne $null} | ft database,displayname,disc*

The results of the above PowerShell command were basically the same – a mailbox I had just disabled was nowhere to be found! After a few more minutes in the EMS, I was able to identify that the mailbox did still exist within the database (whew!), it just hadn’t fully be flagged as disconnected. Knowing that things like this usually occur because a maintenance task hadn’t been completed, I hit the trusty Google machine and found Peter Schmidt’s post on this exact issue. Running Clean-MailboxDatabase against the relevant DB made my “missing” mailbox appear as disconnected and within two minutes we were back up in running.

Or so we thought. Emails sent to the recently-attached mailbox were promptly responded to with NDRs, the content of such read something like this:

#554-5.2.1 mailbox disabled 554 5.2.1 STOREDRV.Deliver.Exception:AccountDisabledException.MapiExceptionMailboxDisabled; Failed to process message due to a permanent exception with message Cannot open mailbox

Fortunately the fix for this issue was to re-run Clean-MailboxDatabase again to freshen things up.

Exchange CAS IIS Log Maintenance

One of the first things a young Exchange admin will learn is that there is no built in IIS log maintenance capability for clearing out old log files baked in to Exchange. Hopefully this lesson comes when SCOM starts throwing drive space alerts, and not when the C: drive fills up and craters the server :/. To mitigate this risk, most admins create a simple script for regularly performing IIS log maintenance by deleting CAS log files older than a certain date. Here are a couple of scripts you can use, depending on your environment.

Properly scheduled IIS log maintenance will help keep your C: drive clean (what was happening on 6/14!?)
15 items so the script is working as expected. Based on file size you may want to shorten the retention window to 7 days in your environment.

For info on how to create the Scheduled Task to run this script daily, look here

If you just have one CAS (or one server with the Client Access Service in the case of Exchange 2016), this one-liner will get the job done:

#################################################################
# Exchange CAS IIS Log Maintenance                              #
# This script deletes IIS log files older than 14 days from the #
# local Exchange CAS server					#
#								#
# Created by Eric Kukkuck 3/3/2015				#
#################################################################

Get-ChildItem –Path  “C:\inetpub\logs\LogFiles\W3SVC1” | Where-Object {$_.CreationTime –lt (Get-Date).AddDays(-14)} | Remove-Item

If you have multiple CASes and an Admin server, this script will pull a list of CAS servers from a text file and clear the old log files remotely. It will also remove log files from the first two additional log file directories, if you happen to have three directories in play (as I did at one point, the default website, a secondary website for basic auth ActiveSync devices, and a third website for the AV solution)

#################################################################
# Exchange CAS IIS Log Maintenance                              #
# This script deletes IIS log files older than 14 days from all #
# the Exchange CAS servers listed in the        		#
# D:\ABZ\Scripts\ExchangeCASServers.txt file			#
#								#
# Created by Eric Kukkuck 3/3/2015				#
#################################################################

$servers = get-content "D:\ABZ\Scripts\ExchangeCASServers.txt"

foreach ($server in $servers)
{
	Get-ChildItem –Path  “\\$server\c$\inetpub\logs\LogFiles\W3SVC1”,"\\$server\c$\inetpub\logs\LogFiles\W3SVC2","\\$server\c$\inetpub\logs\LogFiles\W3SVC3",“\\$server\c$\inetpub\logs\LogFiles\W3SVC4” | Where-Object {$_.CreationTime –lt (Get-Date).AddDays(-14)} | Remove-Item
}

-Eric

IIS Log Parser Script for Finding Two Items Per Line

Troubleshooting Exchange connectivity issues can often be a chore, especially if you’re trying to go line by line in the IIS logs. The close formatting in those text files makes for a blurry and mind-numbing experience, not to mention how easy it is to miss what you’re actually looking for. To make that experience easier (and to offload the hard work to PowerShell) I’ve put together the following IIS log parser script* that will allow you to search IIS logs remotely on multiple servers for lines that contain two different items and export that data to a CSV. For example, if your helpdesk reports to you that some people are unable to access their mailbox from their ActiveSync device, you may want to start your investigation by searching the IIS logs on multiple CASes for lines that contain both “ActiveSync” and “401”. This script will do just that!

*I’d like to give credit to who created the original script I edited to make this one, but I cannot seem to find it online 🙁

-Eric

#########################################################
# Search Multiple IIS Logs for Multiple Items Per Line 	#
# Created By Eric Kukkuck   04/16/2014			#
#########################################################

# Edit the variables below to meet your needs #

$Path = "\\SERVER1\c$\inetpub\logs\LogFiles\W3SVC1","\\SERVER2\c$\inetpub\logs\LogFiles\W3SVC1"
$PathArray = @()
$ResultsLog = "C:\Temp\IISSearchResults.csv"
$Variable1 = "ActiveSync"
$Variable2 = "401"

# The meat and potatoes #

if (Test-Path $ResultsLog -PathType Leaf) { 
write-host "Delete the current log file and try again " 
exit
}
# This code snippet gets all the files in $Path that end in “.log”.
Get-ChildItem $Path -Recurse -Filter “*.log” |
Where-Object { $_.Attributes -ne “Directory”} |
ForEach-Object {gc $_.FullName | % { if($_ -match "($Variable1.*$Variable2)") {
$_ | add-content -path $ResultsLog }
    }
}

O365, On-prem Exchange, and SPF Records

One of the first things you should update before integrating Exchange Online or EOP into your mail flow is your organization’s SPF record. If you plan to use EOP as your perimeter gateway you may think that the O365 IPs are all you need, and you can get away with an SPF record that looks something like this:

v=spf1 include:spf.protection.outlook.com -all

Unfortunately, that isn’t the case. When Exchange Online shuffles emails around internally between tenants, any message from your on-prem environment will still need to be validated against your SPF record. What’s even more interesting, is if you have mailboxes in Exchange Online, Exchange on-prem, and decide to put a perimeter gateway like Proofpoint in front of EOP, then you get to have three sets of IPS (Exchange Online’s, your on-prem environment’s, and your Proofpoint gateway’s) in your SPF record!

-Eric

Get CAS Active User Counts (Exchange 2010)

A lot of times when troubleshooting a potential Exchange CAS server issue or performing maintenance on an Exchange 2010 client access server, you’ll find you need to identify how many active connections exist on your CAS. The script found on Technet will pull your RPC, OWA, and EAS connections, but I prefer a more complete view so I’ve added Address Book, POP, IMAP, and EWS connections to fill out the script.

One thing to note about running this script – it creates a function to be called later, so you need to save it as a .ps1 file and execute it by entering “. .\filename.ps1” at the prompt. Once the function has been added to the shell you call it by entering “Get-CASActiveUsers -ComputerName casname“.

-Eric

function Get-CASActiveUsers {
  [CmdletBinding()]
    param(
    [Parameter(Position=0, ParameterSetName="Value", Mandatory=$true)]
    [String[]]$ComputerName,
    [Parameter(Position=0, ParameterSetName="Pipeline", ValueFromPipelineByPropertyName=$true, Mandatory=$true)]
    [String]$Name
  )

  process {
    switch($PsCmdlet.ParameterSetName) {
      "Value" {$servers = $ComputerName}
      "Pipeline" {$servers = $Name}
    }
    $servers | %{
      $RPC = Get-Counter "\MSExchange RpcClientAccess\User Count" -ComputerName $_
      $OWA = Get-Counter "\MSExchange OWA\Current Unique Users" -ComputerName $_
      $AS = Get-Counter “\MSExchange ActiveSync\Current Requests” -ComputerName $_
      $AB = Get-Counter "\MSExchangeAB\NSPI Connections Current" -ComputerName $_
      $IMAP =  get-counter "\MSExchangeimap4(_Total)\current connections" -ComputerName $_
      $EWS = Get-Counter "\W3SVC_W3WP(*msexchangeservicesapppool)\Active Requests" -ComputerName $_
      New-Object PSObject -Property @{
        Server = $_
         "RPC Client Access" = $RPC.CounterSamples[0].CookedValue
         "Outlook Web App" = $OWA.CounterSamples[0].CookedValue
	 "ActiveSync" = $AS.CounterSamples[0].CookedValue
	 "Address Book" = $AB.CounterSamples[0].CookedValue
	 "IMAP" = $IMAP.CounterSamples[0].CookedValue
	 "EWS" = $EWS.CounterSamples[0].CookedValue
      } | Select-Object Server,"RPC Client Access","Outlook Web App",IMAP,ActiveSync,"Address Book",EWS | FT -Auto
    }
  }
}