#MsExchange 2010 Get-MailboxDatabaseCopyStatus

So Tony Redmond gave me a blank look when I spoke to him for like 30 seconds at MEC, but this little code snippet is featured on PG 491 of Microsoft Exchange 2010 Inside Out.

I use this a lot, and have it wrapped in to a .ps1. Just discovered that like Get-ExchangeServer you can pipe an array of objects to Get-MailboxDatabase. So you could say run:

"db01","db02" | Get-MailboxDatabase
Now that is cool. I have an issue at the moment where some Donkey thought it would be a good idea to reseed two 600GB databases (We use NetApp storage so there is a better way to do it!). I wanted a way to keep an eye on them so I updated the original script.

Do you can use the -database switch like this to get the database copy status for db01 and db04
 .\Check-DatabaseCopyStatus.ps1 -Database @("db01","db04")

-and you could you the server switch to get the database copy status for db01 and db04 just on ex1
 .\Check-DatabaseCopyStatus.ps1 -Database @("db01","db04") -Server ex1

Enjoy
PARAM([String]$Server="", [String[]]$Database = "*")
If($server -ne ""){$srvtxt=$server;$Server="\" + $server.ToUpper()}ELSE{$srvTxt="All"}
Write-Host "Server:" $srvtxt "`nDatabase:"$Database "`n"

##########################################################################################
#Load the Exchange 2010 bits & bobs
#########################################################################################
$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Write-Host "Loading Exchange Snapin"; Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}

$Database | Get-MailboxDatabase | Sort Name | FOREACH {$db=$_.Name; $xNow=$_.Server.Name ;$dbown=$_.ActivationPreference| Where {$_.Value -eq 1}; Write-Host $db "on" $xNow "Should be on" $dbOwn.Key -NoNewLine; If ( $xNow -ne $dbOwn.Key){Write-host " WRONG" -ForegroundColor Red; }ELSE{Write-Host " OK" -Foregroundcolor Green};Get-MailboxDatabaseCopyStatus $db$Server;"`n"}

Download it from here

#Powershell and Get-ExchangeServer

#IAMMEC

Now that is cool.  Been playing with the cmdlet Get-ExchangeServer.  I wanted to get a group of different server names in one go, without having the run the cmdlet multiple times.

So I thought I would try and chuck an array at Get-ExchangeServer and see what it does?  It only bloody works ;-)

$s=@()
$s +="MyServers*"
$s += "Exch*"
$s | Get-ExchangeServer

Neat trick, and you can also pass arrays in as a script parameter too.  Found this: http://santoshbenjamin.wordpress.com/2008/09/30/powershell-and-arrays-as-named-parameters/

The key for a script or function parameter is [String[]]$Server=”*”

I Like it ALOT!

Windows Certificates (Schannel 36885)

Ohh, Blog post by Email ;-)

Okay so following on from my last post (https://blog.flaphead.com/2014/03/16/windowspowershell-and-windows-certificates-2/) I wanted to share the reason behind the script.

We have been seeing lots of these in the System Event Log on out Exchange Servers:

Log Name: System
Source: Schannel
Event ID: 36885
Description:
When asking for client authentication, this server sends a list of trusted certificate authorities to the client. The client uses this list to choose a client certificate that is trusted by the server. Currently, this server trusts so many certificate authorities that the list has grown too long. This list has thus been truncated. The administrator of this machine should review the certificate authorities trusted for client authentication and remove those that do not really need to be trusted.

It turns out this is because Update for Root Certificates For Windows Server 2008 R2 x64 Edition [December 2012] (KB931125) was installed on some of the Exchange Servers but not all.

So I have another script to show Installed and Pending windows updates ;-) that I will share later.

Hope this helps

#MsExchange Versions

#IAMMEC

So if you want to find out the version of Exchange you are running you can always run:

Get-ExchangeServer | Select Name, ServerRole, Edition, AdminDisplayVersion

Now this is cool, but it only shows the major version and not the CU.

You can find exchange build information from two main places:

I have dropped the information into a CSV file [ExchangeBuilds].   I found that updating a CSV file with new builds was easier than editing code all the time.

Now code below to create a summary matrix.  Enjoy

#Import the csv and covert to a hash table
$ExchangeBuilds = import-csv .\ExchangeBuilds.csv
$BuildMatrix = @{}
ForEach($Item in $ExchangeBuilds){
  $BuildMatrix.Add($item.Build, $item.version)
}

#Get exchange servers
Get-ExchangeServer | Select Name, ServerRole, Edition, AdminDisplayVersion

#Loop the servers to create the matrix
$matrix =@()
$ExchangeServers = Get-ExchangeServer | Sort Name
ForEach($Item in $ExchangeServers){
  $tmpServer = $item.Name + "." + $item.domain
  Write-Host $tmpServer 
  $tmpMatrix = "" | Select Name, Roles, Edition, FileVersion,UpdateRollup
  $tmpMatrix.Name = $tmpServer 
  $tmpMatrix.Roles = $Item.ServerRole
  $tmpMatrix.Edition = $Item.Edition

  Switch($item.AdminDisplayVersion.Major){
     6 {$key="SOFTWARE\Microsoft\Exchange\Setup"}
     8 {$key="SOFTWARE\Microsoft\Exchange\Setup"}
    14 {$Key="SOFTWARE\Microsoft\ExchangeServer\v14\Setup"}
  }

  #Find the exchange binary path
  $type = [Microsoft.Win32.RegistryHive]::LocalMachine
  $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type, $tmpServer )
  $regKey = $regKey.OpenSubKey($key)

  #work out the file we need to use
  Switch($item.AdminDisplayVersion.Major){
     6      {$tmpPath = $regKey.GetValue("Services");      $tmpFile = "mad.exe";    $tmpPath += "\bin\"}
    Default {$tmpPath = $regKey.GetValue("MsiInstallPath");$tmpFile = "ExSetup.exe";$tmpPath += "bin\"}
  }

  #Get the file verision of mad.exe or exsetup.exe
  $tmpPath = $tmpPath.replace(":", "$")
  $tmpunc  = "\\" + $tmpServer + "\" + $tmpPath + $tmpFile

  IF (test-path $tmpunc){
    $tmpVer = (dir "$tmpunc").VersionInfo.FileVersion
    $tmpMatrix.FileVersion = $tmpVer
    $tmpMatrix.UpdateRollup = $BuildMatrix.$tmpver
  }
  $Matrix += $tmpMatrix
}
$Matrix

#MsExchange export GAL and Addresslists to CSV

#IAMMEC

So I was asked to export the GAL so we could give it to another part of the company that has not been assimilated yet.

GAL

Get-GlobalAddressList | ForEach{$n=$_.Name;$p=$pwd.path+"\"+$n+".csv";$l=$_.LdapRecipientFilter;$n;Get-Recipient -RecipientPreviewFilter $l -resultsize unlimited| Where-Object {$_.HiddenFromAddressListsEnabled -ne $true} | Select-Object Name,PrimarySmtpAddress | Export-CSV $p -NoTypeInformation}

Address List

Get-AddressList | ForEach{$n=$_.Name;$p=$pwd.path+"\"+$n+".csv";$l=$_.LdapRecipientFilter;$n;Get-Recipient -RecipientPreviewFilter $l -resultsize unlimited| Where-Object {$_.HiddenFromAddressListsEnabled -ne $true} | Select-Object Name,PrimarySmtpAddress | Export-CSV $p -NoTypeInformation}

Both lumps of code export to a csv file in the current path ($p)

Ejnjoy

Exchange PrepareAD

#MsExchange

If you can remember back when you built you Exchange environment, you ran setup /PrepareAD and you may or not have noticed this message:

Setup will prepare the organization for Exchange 2013 by using 'Setup /PrepareAD'. No Exchange 2010 server roles have been detected in this topology. After this operation, you will not be able to install any Exchange 2010 servers.
For more information, visit: http://technet.microsoft.com/library(EXCHG.150)/ms.exch.setupreadiness.NoE14ServerWarning.aspx

Now if you forgot about this, your are screwed. In this case I would not be able to install any Exchange 2010 in the future.

Wouldn’t it be nice if Microsoft made you press any key or something, so if you have a DOH! moment you can fix things?!

Parsing Message Headers using Powershell

#MsExchange #Powershell

So sometimes I want or need to look at the message headers of an email to work out where it comes from.  You can use something like http://mxtoolbox.com/public/tools/emailheaders.aspx to do that, but I thought, you must be able to so this with Windows Powershell.  Guess what you can ;-)

Meet Parse-EmailHeaders.ps1.  Now I cheated a little by using Sapien Powershell Studio to build the GUI as I couldn’t be bothered to manually create the GUI in notepad ;-)

So fireup powershell and run the script.

Parse-EmailHeader(1)

Essentially, in outlook get the message header of an email and paste it in to the GUI box and click on the Parse Button.

Parse-EmailHeader(2)

In the shell you will see what it’s up to.

PS C:\PS> .\Parse-EmailHeader.ps1
Looking for C:\PS\Parse-EmailHeader_KnownIPs.csv
 - Found
 --  60  Found
Discovering IP Geo Information
 + / +
[fe80::4100:46ba:3a5c:54ce] + / + [fe80::b42c:7f9:e0ef:23d%10]
10.11.123.10 + / + 10.11.250.20
91.206.176.84 + / + 10.47.216.189
2a01:111:f400:7e04::177 + / + 2a01:111:e400:8814::26
10.242.16.26 + / + 10.242.136.153
213.199.154.78 + / + 10.174.65.75
2a01:111:f400:7e00::105 + / + 2a01:111:e400:1000::18
10.242.64.18 + / + 10.242.68.20
unknown unknown GB GB GB unknown unknown GB unknown unknown
http://maps.googleapis.com/maps/api/staticmap?size=800x800&sensor=false&path=color:0xff0000ff|weight:5|UB40SL|E149YY&mar
kers=size:mid%7Ccolor:red%7CUB40SL%7CE149YY
Looking for C:\PS\Parse-EmailHeader_KnownIPs.csv
 - Found
 --  60  Found
Discovering IP Geo Information
 + / +
 + / + 10.180.165.174
 + / + 10.114.67.131
 + / + 10.152.6.199
 + / + mail-la0-f45.google.com
relay146.msgfocus.com. [86.54.102.146] + / + mx.google.com
209.85.215.45 + / + 10.47.216.92
2a01:111:f400:7e04::108 + / + 2a01:111:e400:9414::19
10.141.8.147 + / + 10.242.141.17
unknown unknown unknown unknown unknown unknown unknown CL unknown unknown
http://maps.googleapis.com/maps/api/staticmap?size=800x800&sensor=false&path=color:0xff0000ff|weight:5|UB40SL|E149YY&mar
kers=size:mid%7Ccolor:red%7CUB40SL%7CE149YY
Looking for C:\PS\Parse-EmailHeader_KnownIPs.csv
 - Found
 --  60  Found
Discovering IP Geo Information
 + / +

http://maps.googleapis.com/maps/api/staticmap?size=800x800&sensor=false&path=color:0xff0000ff|weight:5|UB40SL|E149YY&mar
kers=size:mid%7Ccolor:red%7CUB40SL%7CE149YY
PS C:\PS> .\Parse-EmailHeader.ps1
Looking for C:\PS\Parse-EmailHeader_KnownIPs.csv
 - Found
 --  60  Found
Discovering IP Geo Information
 + / +
[10.0.0.189:8127] helo=HOME-?? + / + Momo-dev:3.5.1.0
unknown unknown UA
http://maps.googleapis.com/maps/api/staticmap?size=800x800&sensor=false&path=color:0xff0000ff|weight:5|UB40SL|E149YY&mar
kers=size:mid%7Ccolor:red%7CUB40SL%7CE149YY

The end result is a saved file called Parse-EmailHeaders.html which will automagically open once the parsing is complete.

Parse-EmailHeader(3)

Now, as an added bonus, if a file called Parse-EmailHeaders_KnownIPs.csv exists, it will use it to populate Country and City of IP’s you know about.

IP,Country,City
2a01:111:e400:8000::28,Microsoft EOP (NL),Amsterdam
10.242.80.27,Microsoft EOP (NL),Amsterdam
10.242.77.156,Microsoft EOP (NL),Amsterdam
10.16.249.240,Microsoft EOP (IE),Dublin

Why have this?  Well I put all my known Exchange Hub and Edge servers in, with the Datacentre location so I can see the path it took.  Create the csv file in the same folder as the .ps1 and chop and change as you wish.

Hope you like

Parse-EmailHeader.ps1 (zip)

Fix-ResynchronizingOrInitializing.ps1

#MsExchange #Powershell

Sometimes you get databases in a DAG with an Initializing or Resynchronizing state.  The way I have found to fix this is to Suspend the database copy and then resume it.

If you have quite a few databases its a pain in the butt using the console to do this .. so I wrote this.  You have to run it on the server that has a copy in either Initializing or Resynchronizing state.

##########################################################################################
#Load the Exchange 2010 bits & bobs
#########################################################################################
$xPsCheck = Get-PSSnapin | Select Name | Where {$_.Name -Like "*Exchange*"}
If ($xPsCheck -eq $Null) {Write-Host "Loading Exchange Snapin"; Add-PsSnapin Microsoft.Exchange.Management.PowerShell.e2010}

$gmdcs = Get-MailboxDatabaseCopyStatus
ForEach($item in $gmdcs){
  $tmpstatus = $item.Status
  $tmpName   = $item.Name
  Write-Host $tmpName "["$tmpStatus"]"
  IF (($tmpStatus -eq "Resynchronizing") -OR ($tmpStatus -eq "Initializing")){
    Write-Host "- Suspending"
    suspend-MailboxDatabaseCopy -Identity $tmpName -Confirm:$False
    Write-Host "- Resuming"
    resume-MailboxDatabaseCopy -Identity $tmpName
  }
}

Exchange 2013 Command Log

#MsExchange #Powershell

One of the things I like about Exchange 2010 is the ability in the Exchange Management Console to view the command log.  So typically I would do some in the console, the get the command by clicking View and selecting View Exchange Management Shell Command Log, finding the command I just ran and using it in a script.

Well Exchange 2013 is all web based now and that feature has gone :-( but you can still get the information by using the  Search-AdminAuditLog cmdlet.

This works for both Exchange 2010 and 2013 .. enjoy

$whoai = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).Name
 $saal = Search-AdminAuditLog -UserIds $whoai | Sort RunDate -Descending
 $Matrix = @()
 ForEach($Item in $saal){
 $tmpMatrix = ""|Select Caller, When, Change
 $tmpMatrix.Caller = $Item.Caller
 $tmpMatrix.When = $Item.RunDate
 $tmpMatrix.Change = $Item.CmdLetName + " "
 $tmpMatrix.Change += "'" + $item.ObjectModified + "' "
$tmpCmdletParameters = $item.CmdletParameters
 $Param = ""
 ForEach($tmpParam in $tmpCmdletParameters){
 $Param += " -" + $tmpParam.Name + " '" + $tmpParam.Value + "' "
 }
 $tmpMatrix.Change += $Param
 $Matrix += $tmpMatrix
 }
$Matrix | ft -auto -wrap

Number of Mailboxes on an Exchange Server

#MsExchange #Powershell

I wanted to look at a quicker way to discover the number of mailboxes per database.  Sure you could run something like Get-MailboxDatabase | Get-Mailbox, but depending on the environment and number of mailboxes this does take an age.  Instead I discovered that the actual mailbox count per database is hidden in the AD!

So open the Exchange Management shell and run this!

Write-Host "Getting Mailbox Databases"
[array] $dbs = Get-MailboxDatabase
Write-Host ($dbs.count) "Found"

$matrix=@()
Write-Host "Getting Mailbox Count"
ForEach($db in $dbs){
$tmpMatrix = "" | Select database, usercount, Server
$tmpMatrix.Server = $db.Server
$dn = $db.DistinguishedName
$tmpMatrix.database = $db.name
[ADSI] $objDB = "GC://$($dn -replace ‘/’,'\/’)"
if ( ($objDB.objectClass -ne $null) -and ($objDB.objectClass.Contains(“msExchPrivateMDB”)) ) {
$tmpMatrix.usercount = [Math]::Max(0,([Int] ($objDB.homeMDBBL.Count)) – 1)
}
$matrix += $tmpMatrix
}

Quick ;-)