# encoding: ascii
# api: powershell
# title: Get Exchange DB Stats
# description: Gathers lots of statistics about Exchange 2010 databases, and sends an email with both a html table, and also CSS graphs representing disk usage.
# version: 0.1
# type: function
# author: rfoust
# license: CC0
# x-poshcode-id: 3421
# x-archived: 2016-04-06T09:14:32
# x-published: 2013-05-20T18:36:00
#
#
# requires -version 2.0
#
# get-sgstats2010.ps1
#
# returns various info about storage groups
#
# Author: rfoust@duke.edu
# Modified: May 20, 2012
#
# This has only been tested with Exchange 2010
# Use -nomountpoint if you don't use mountpoints in your environment

param([string]$server=$env:computername.tolower(), [string]$database="NotSpecified", [switch]$all, [switch]$noemail, [switch]$nologcheck, [switch]$nomountpoint)

#Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin
if (!(get-pssnapin Microsoft.Exchange.Management.PowerShell.E2010 -erroraction silentlycontinue))
	{
	Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
	}

# found this really cool function here: http://www.olavaukan.com/tag/powershell/
function Util-Convert-FileSizeToString {	 
	    param (
	        [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
	        [int64]$sizeInBytes
	    )
	 
	    switch ($sizeInBytes)
	    {
	        {$sizeInBytes -ge 1TB} {"{0:n$sigDigits}" -f ($sizeInBytes/1TB) + " TB" ; break}
	        {$sizeInBytes -ge 1GB} {"{0:n$sigDigits}" -f ($sizeInBytes/1GB) + " GB" ; break}
	        {$sizeInBytes -ge 1MB} {"{0:n$sigDigits}" -f ($sizeInBytes/1MB) + " MB" ; break}
	        {$sizeInBytes -ge 1KB} {"{0:n$sigDigits}" -f ($sizeInBytes/1KB) + " KB" ; break}
	        Default { "{0:n$sigDigits}" -f $sizeInBytes + " Bytes" }
	    }
	}


# specifying only a single database using -database makes the script run faster for testing
if ($database -eq "NotSpecified")
	{
	# $dbs = get-mailboxdatabase -status | ? { $_.replicationtype -eq "Remote" } | select-object servername,name,databasesize,mounted | sort servername,name
	$dbs = get-mailboxdatabase -status | ? { $_.replicationtype -eq "Remote" } | sort servername,name
	}
else
	{
	$dbs = get-mailboxdatabase $database -status | sort servername,name
	}

$fragments = @()	# array to hold html fragments
$data = @()		# array to hold the psobjects
$prettydata = @()	# array to hold pretty data (strings, not raw bytes)


foreach ($db in $dbs)
	{
	write-host "Processing $db."
	
	$raw = new-object psobject
	$pretty = new-object psobject

	write-host "Gathering count of mailboxes on $db."
	$mailboxes = (get-mailbox -database $db.name -resultsize unlimited).count

	#get copy status
	$copystatus = (get-mailboxdatabasecopystatus $db.name | ? { $_.activecopy -eq $false }).status

	if (-not $nologcheck)
		{
		#figure out how much space the logs are consuming
		# $drive will probably end up being D$ in all cases, but might as well do the logic to figure it out
		write-host "Finding out how much disk space the log files are using for $db ..."
		$drive = $db.logfolderpath.tostring().substring(0,1) + "$"
		$substring = $db.logfolderpath.tostring().substring(0,2)
		$uncpath = "\\$($db.server)\" + $drive + ($db.logfolderpath.tostring().replace($substring,"")) + "\*.log"
		$logsize = (ls $uncpath | measure-object -property length -sum).sum
		$logsizetotal += $logsize
		$logsize = util-convert-filesizetostring $logsize
		}
	
	#calculate average mailbox size
	Write-Host "Calculating average mailbox size ..."
	$avg = get-mailboxstatistics -database $db | % { $_.totalitemsize.value.tobytes() }
	$avg = ($avg | Measure-Object -Average).average
	$avgTotal += $avg

	if ($avg)
		{
		$avgPretty = util-convert-filesizetostring $avg
		}
		
	#calculate deleted mailbox size
	Write-Host "Calculating deleted mailbox size ..."
	$deletedMBsize = get-mailboxstatistics -database $db | ? { $_.DisconnectDate -ne $null } | % { $_.totalitemsize.value.tobytes() }
	$deletedMBsize = ($deletedMBsize | Measure-Object -Sum).sum
	$deletedMBsizeTotal += $deletedMBsize
	
	if ($deletedMBsize)
		{
		$deletedMBsizePretty = util-convert-filesizetostring $deletedMBsize
		}
	
	#calculate dumpster size
	Write-Host "Calculating dumpster size ..."
	$dumpster = get-mailboxstatistics -database $db | % { $_.totaldeleteditemsize.value.tobytes() }
	$dumpster = ($dumpster | Measure-Object -Sum).sum
	$dumpsterTotal += $dumpster
	
	if ($dumpster)
		{
		$dumpsterPretty = util-convert-filesizetostring $dumpster
		}
	
	#get a shorter db size
	$dbsize = $db.databasesize.tobytes()
	$dbsizetotal += $dbsize
	if ($dbsize)
		{
		$dbsizePretty = util-convert-filesizetostring $dbsize
		}

	#get free space on the mountpoint volume
	if ($nomountpoint)
		{
		$freespace = (gwmi win32_volume -computer $db.server | where { $_.name -eq ($db.logfolderpath.drivename + "\") }).freespace
		}
	else
		{
		$freespace = (gwmi win32_volume -computer $db.server | where { $_.name -eq ($db.logfolderpath.tostring() + "\") }).freespace
		}
	$freespacetotal += $freespace
	if ($freespace)
		{
		$freespacePretty = util-convert-filesizetostring $freespace
		}

	#get capacity on the mountpoint volume
	if ($nomountpoint)
		{
		$capacity = (gwmi win32_volume -computer $db.server | where { $_.name -eq ($db.logfolderpath.drivename + "\") }).capacity
		}
	else
		{
		$capacity = (gwmi win32_volume -computer $db.server | where { $_.name -eq ($db.logfolderpath.tostring() + "\") }).capacity
		}
	$capacitytotal += $capacity
	if ($capacity)
		{
		$capacityPretty = util-convert-filesizetostring $capacity
		}

	#get a shorter whitespace size
	$whitespace = $db.availablenewmailboxspace.tobytes()
	$whitespacetotal += $whitespace
	if ($whitespace)
		{
		$whitespacePretty = util-convert-filesizetostring $whitespace
		}


	# create psobject with raw bytes
	$raw | add-member NoteProperty "ServerName" $db.servername
	$raw | add-member NoteProperty "Database" $db.name
	$raw | add-member NoteProperty "Mailboxes" $mailboxes
	$raw | add-member NoteProperty "CopyStatus" $copystatus
	$raw | add-member NoteProperty "DBSize" $dbsize
	# $raw | add-member NoteProperty "Mounted" $db.mounted
	$raw | add-member NoteProperty "LogSize" $logsize
	$raw | add-member NoteProperty "FreeSpace" $freespace
	$raw | add-member NoteProperty "TotalSpace" $capacity
	$raw | add-member NoteProperty "Whitespace" $whitespace
	$raw | add-member NoteProperty "Deleted Mbox" $deletedMBsize
	$raw | add-member NoteProperty "Dumpster" $dumpster
	$raw | add-member NoteProperty "Avg Mbox" $avgPretty
	$raw | add-member NoteProperty "Last Full Backup" $db.lastfullbackup
	$raw | add-member NoteProperty "Last Incr Backup" $db.lastincrementalbackup

	$data += $raw

	# create psobject with pretty display sizes (MB, GB, etc)
	$pretty | add-member NoteProperty "ServerName" $db.servername
	$pretty | add-member NoteProperty "Database" $db.name
	$pretty | add-member NoteProperty "Mailboxes" $mailboxes
	$pretty | add-member NoteProperty "CopyStatus" $copystatus
	$pretty | add-member NoteProperty "DBSize" $dbsizePretty
	# $pretty | add-member NoteProperty "Mounted" $db.mounted
	$pretty | add-member NoteProperty "LogSize" $logsize
	$pretty | add-member NoteProperty "FreeSpace" $freespacePretty
	$pretty | add-member NoteProperty "TotalSpace" $capacityPretty
	$pretty | add-member NoteProperty "Whitespace" $whitespacePretty
	$pretty | add-member NoteProperty "Deleted Mbox" $deletedMBsizePretty
	$pretty | add-member NoteProperty "Dumpster" $dumpsterPretty
	$pretty | add-member NoteProperty "Avg Mbox" $avgPretty
	$pretty | add-member NoteProperty "Last Full Backup" $db.lastfullbackup
	$pretty | add-member NoteProperty "Last Incr Backup" $db.lastincrementalbackup

	$prettydata += $pretty
	}

# add a "total" row
$thingy = new-object psobject
write-host; write-host "Calculating totals ..."

$mailboxes = ($data | measure-object mailboxes -sum).sum
$deletedMBsizetotal = ($deletedMBsizetotal | Measure-Object -Sum).sum

$thingy | add-member NoteProperty "ServerName" "Total"
$thingy | add-member NoteProperty "Database" $data.count
$thingy | add-member NoteProperty "DBSize" (util-convert-filesizetostring $dbsizetotal)
$thingy | add-member NoteProperty "Mailboxes" $mailboxes
if (-not $nologcheck)
	{
	$thingy | add-member NoteProperty "LogSize" (util-convert-filesizetostring $logsizetotal)
	}
$thingy | add-member NoteProperty "FreeSpace" (util-convert-filesizetostring $freespacetotal)
$thingy | add-member NoteProperty "TotalSpace" (util-convert-filesizetostring $capacitytotal)
$thingy | add-member NoteProperty "Whitespace" (util-convert-filesizetostring $whitespacetotal)
$thingy | Add-Member NoteProperty "Deleted Mbox" (util-convert-filesizetostring $deletedMBsizeTotal)
$thingy | Add-Member NoteProperty "Dumpster" (util-convert-filesizetostring $dumpsterTotal)
#$thingy | Add-Member NoteProperty "Avg Mbox" (util-convert-filesizetostring $avgTotal)

$prettyData += $thingy

# add raw data total row
$thingy = new-object psobject

$mailboxes = ($data | measure-object mailboxes -sum).sum
$deletedMBsizetotal = ($deletedMBsizetotal | Measure-Object -Sum).sum

$thingy | add-member NoteProperty "ServerName" "Total"
$thingy | add-member NoteProperty "Database" $data.count
$thingy | add-member NoteProperty "DBSize" $dbsizetotal
$thingy | add-member NoteProperty "Mailboxes" $mailboxes
if (-not $nologcheck)
	{
	$thingy | add-member NoteProperty "LogSize" $logsizetotal
	}
$thingy | add-member NoteProperty "FreeSpace" $freespacetotal
$thingy | add-member NoteProperty "TotalSpace" $capacitytotal
$thingy | add-member NoteProperty "Whitespace" $whitespacetotal
$thingy | Add-Member NoteProperty "Deleted Mbox" $deletedMBsizeTotal
$thingy | Add-Member NoteProperty "Dumpster" $dumpsterTotal
#$thingy | Add-Member NoteProperty "Avg Mbox" $avgTotal

$data += $thingy

# dump pretty data out to screen
$prettyData

# bullet graph idea came from here: http://www.usrecordings.com/test-lab/bullet-graph.htm
# powershell html chart was inspired by: http://jdhitsolutions.com/blog/2012/02/create-html-bar-charts-from-powershell/
$style = "<style type=`"text/css`">
html * { margin: 0; padding: 0; border: 0; }
body { text-align: left; font: 10pt Arial, sans-serif; }
TH { border-width: 1px; padding: 0px; border-style: solid; border-color: black; background-color: thistle }
h1 { font: 30pt Arial, sans-serif; padding: 10px 0 5px 0; width: 540px; margin: 0 auto 10px auto; border-bottom: 1px solid #8f8f8f; text-align: left; }
h2 { font: 20pt Arial, sans-serif; }
p#contact { font: 10pt Arial, sans-serif; width: 398px; margin: 0 auto; padding-top: 7px; text-align: left; line-height: 140%; }
a:link, a:visited, a:hover { color: rgb(32,108,223); font-weight: bold; text-decoration: none; }
a:hover { color: #cc0000; font-weight: bold; }


div#container { position: relative; margin: 0px 50px; padding: 0; text-align: left; top: 80px; width: 250px; }

/*	BULLET GRAPH */
div.box-wrap { position: relative; width: 200px; height: 21px; top: 0; left: 0; margin: 0; padding: 0; }

/*	CHANGE THE WIDTH AND BACKGROUND COLORS AS NEEDED */
div.box1 { position: absolute; height: 20px; width: 30%; left: 0; background-color: #eeeeee; z-index: 1; font-size: 0; }
div.box2 { position: absolute; height: 20px; width: 30%; left: 30%; background-color: #dddddd; z-index: 1; font-size: 0; }
div.box3 { position: absolute; height: 20px; width: 30%; left: 60%; background-color: #bbbbbb; z-index: 1; font-size: 0; }
div.box4 { position: absolute; height: 20px; width: 10%; left: 90%; background-color: #bbbbbb; z-index: 1; font-size: 0; }

/* RED LINE */
div.target { position: absolute; height: 20px; width: 1px; left: 32px; top: 0; background-color: #cc0000; z-index: 7; font-size: 0; }

/* ONE SEGMENT ONLY */
div.actual { position: absolute; height: 8px; left: 0px; top: 6px; background-color: #000000; font-size: 0; z-index: 5; font-size: 0; }

/* TWO SEGMENTS */
div.actualWhitespace { position: absolute; height: 8px; left: 0px; top: 6px; background-color: #b580fe; font-size: 0; z-index: 5; font-size: 0; }
div.actualDeleted { position: absolute; height: 8px; left: 0px; top: 6px; background-color: #2d006b; font-size: 0; z-index: 5; font-size: 0; }
div.actualDumpster { position: absolute; height: 8px; left: 0px; top: 6px; background-color: #dabffe; font-size: 0; z-index: 5; font-size: 0; }
div.actualData { position: absolute; height: 8px; left: 0px; top: 6px; background-color: #400099; font-size: 0; z-index: 5; font-size: 0; }

div.mylabel { 
	position: relative; 
	height: 20px; 
	width: 145px; 
	left: -155px; 
	top: 2px; 
	background-color: #fff; 
	z-index: 7; 
	font-size: 0;
	color: #000000; 
	font: 10pt Arial, sans-serif; 
	text-align: right; 
}

div.scale-tb1 {
	padding: 0; 
	margin: 0;
	font-size: 0;
	width: 200px;
	border: 0;	
	position: relative; 
	top: 10px; 
	left: 0px; 
	border-top: 1px solid #8f8f8f; 
}

div.scale-tb2 {
	padding: 0; 
	margin: 0;
	font-size: 0;
	width: 200px;
	border: 0;	
	position: relative; 
	top: 0px; 
	left: 0px; 
}

/*	SCALE MARKS */
div.sc21 { position: absolute; height: 7px; width: 1px; left: 0px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc22 { position: absolute; height: 7px; width: 1px; left: 39px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc23 { position: absolute; height: 7px; width: 1px; left: 79px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc24 { position: absolute; height: 7px; width: 1px; left: 119px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc25 { position: absolute; height: 7px; width: 1px; left: 159px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc26 { position: absolute; height: 7px; width: 1px; left: 199px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }

div.sc31 { position: absolute; height: 7px; width: 1px; left: 0px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc32 { position: absolute; height: 7px; width: 1px; left: 39px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc33 { position: absolute; height: 7px; width: 1px; left: 79px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc34 { position: absolute; height: 7px; width: 1px; left: 119px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc35 { position: absolute; height: 7px; width: 1px; left: 159px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }
div.sc36 { position: absolute; height: 7px; width: 1px; left: 199px; top: 0px; background-color: #8f8f8f; z-index: 7; font-size: 0; }


/*	SCALE TEXT */
div.cap21 { position: absolute; top: 40px; left: -2px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap22 { position: absolute; top: 40px; left: 35px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap23 { position: absolute; top: 40px; left: 71px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap24 { position: absolute; top: 40px; left: 112px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap25 { position: absolute; top: 40px; left: 152px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap26 { position: absolute; top: 40px; left: 191px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }

div.cap31 { position: absolute; top: 29px; left: -2px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap32 { position: absolute; top: 29px; left: 35px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap33 { position: absolute; top: 29px; left: 71px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap34 { position: absolute; top: 29px; left: 112px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap35 { position: absolute; top: 29px; left: 152px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
div.cap36 { position: absolute; top: 29px; left: 191px; width: 15px; font: 8pt Arial, sans-serif; text-align: center; color: #575757; }
</style>"

$fragments += "<H2>Exchange 2010 Statistics</H2>"
$fragments += $prettyData | ConvertTo-Html -fragment
# $fragments += "<br>"

$html = @()

$html += "<div id=`"container`">"
$html += "Database Size Graph"

foreach ($db in $data)
	{
	if ($db.servername -ne "Total")
		{		
		$html += "<div class=`"box-wrap`">
		<div class=`"box1`"></div>
		<div class=`"box2`"></div>
		<div class=`"box3`"></div>
		<div class=`"box4`"></div>
		<div class=`"target`" style=`"left: $([math]::round((($db.totalspace - $db.freespace) / $db.totalspace) * 100))%`"></div>
		<div class=`"actualWhitespace`" style=`"width: $([math]::round((($db.dbsize) / $db.totalspace)*100))%`"></div>
		<div class=`"actualDeleted`" style=`"width: $([math]::round((($db.dbsize - $db.whitespace) / $db.totalspace)*100))%`"></div>
		<div class=`"actualDumpster`" style=`"width: $([math]::round((($db.dbsize - $db.whitespace - $db.'deleted mbox') / $db.totalspace)*100))%`"></div>
		<div class=`"actualData`" style=`"width: $([math]::round((($db.dbsize - $db.dumpster - $db.'deleted mbox' - $db.whitespace) / $db.totalspace)*100))%`"></div>
		<div class=`"mylabel`">$($db.database)</div>

		<div class=`"cap31`">0%</div>
		<div class=`"cap32`">20%</div>
		<div class=`"cap33`">40%</div>
		<div class=`"cap34`">60%</div>
		<div class=`"cap35`">80%</div>
		<div class=`"cap36`">100%</div>	

		<div class=`"scale-tb2`">
		<div class=`"sc31`"></div>
		<div class=`"sc32`"></div>
		<div class=`"sc33`"></div>
		<div class=`"sc34`"></div>
		<div class=`"sc35`"></div>
		<div class=`"sc36`"></div>
		</div>	

		</div>

		<p style=`"height: 30px;`"></p>"
		}
	else	# total row
		{
		$html += "<div class=`"box-wrap`">
		<div class=`"box1`"></div>
		<div class=`"box2`"></div>
		<div class=`"box3`"></div>
		<div class=`"box4`"></div>
		<div class=`"target`" style=`"left: $([math]::round((($db.totalspace - $db.freespace) / $db.totalspace) * 100))%`"></div>
		<div class=`"actualWhitespace`" style=`"width: $([math]::round((($db.dbsize) / $db.totalspace)*100))%`"></div>
		<div class=`"actualDeleted`" style=`"width: $([math]::round((($db.dbsize - $db.whitespace) / $db.totalspace)*100))%`"></div>
		<div class=`"actualDumpster`" style=`"width: $([math]::round((($db.dbsize - $db.whitespace - $db.'deleted mbox') / $db.totalspace)*100))%`"></div>
		<div class=`"actualData`" style=`"width: $([math]::round((($db.dbsize - $db.dumpster - $db.'deleted mbox' - $db.whitespace) / $db.totalspace)*100))%`"></div>
		<div class=`"mylabel`">Total</div>

		<div class=`"cap31`">0%</div>
		<div class=`"cap32`">20%</div>
		<div class=`"cap33`">40%</div>
		<div class=`"cap34`">60%</div>
		<div class=`"cap35`">80%</div>
		<div class=`"cap36`">100%</div>	

		<div class=`"scale-tb2`">
		<div class=`"sc31`"></div>
		<div class=`"sc32`"></div>
		<div class=`"sc33`"></div>
		<div class=`"sc34`"></div>
		<div class=`"sc35`"></div>
		<div class=`"sc36`"></div>
		</div>	

		</div>

		<p style=`"height: 30px;`"></p>"
		}
	}

# show a graph key
$html += "<div class=`"box-wrap`">
		<div class=`"box1`"></div>
		<div class=`"box2`"></div>
		<div class=`"box3`"></div>
		<div class=`"box4`"></div>
		<div class=`"target`" style=`"left: 90%`"></div>
		<div class=`"actualWhitespace`" style=`"width: 100%`"></div>
		<div class=`"actualDeleted`" style=`"width: 75%`"></div>
		<div class=`"actualDumpster`" style=`"width: 50%`"></div>
		<div class=`"actualData`" style=`"width: 25%`"></div>
		<div class=`"mylabel`">Key:</div>

		</div>	
		MBs - Dumpster - Del MB - Whitespace<br>
		Red Line: Used Disk Space
		</div>

		<p style=`"height: 30px;`"></p>"

$html += "</div><!-- container -->"
$fragments += $html	
$emailbody = convertto-html -head $style -Body $fragments
$date = get-date -uformat "%Y-%m-%d"

if (-not $noemail)
	{
	$smtpServer = "your.smtpserver.com"
	$msg = new-object Net.Mail.MailMessage
	$smtp = new-object Net.Mail.SmtpClient($smtpServer)
	$msg.From = "somebody@somewhere.com"
	$msg.To.Add("you@somewhere.com")
	$msg.Subject = "Exchange 2010 Daily Statistics"
	$msg.Body = $emailbody
	$msg.IsBodyHtml = $true

	$smtp.Send($msg)
	}
