Exchange 2013: Database Storage datasource

Microsoft.Exchange.15.Database.Storage.DataSource (DataSourceModuleType)

Element properties:

TypeDataSourceModuleType
IsolationAny
AccessibilityInternal
RunAsDefault
OutputTypeSystem.PropertyBagData

Member Modules:

ID Module Type TypeId RunAs 
Scheduler DataSource System.Scheduler Default
PS ProbeAction Microsoft.Exchange.15.PowershellPropertyBagCommandExecuter.Probe Default

Overrideable Parameters:

IDParameterTypeSelectorDisplay NameDescription
IntervalSecondsint$Config/IntervalSeconds$Interval (seconds)The recurring interval of time in seconds in which to run the workflow.
TimeoutSecondsint$Config/TimeoutSeconds$Timeout (seconds)Specifies the time the workflow is allowed to run before being closed and marked as failed.
SyncTimestring$Config/SyncTime$Synchronization TimeThe synchronization time specified by using a 24-hour format. May be omitted.
VerboseLoggingint$Config/VerboseLogging$Verbose LoggingWorkflow will write verbose diagnostic events to the Operations Manager event log if this parameter is set to 1.

Source Code:

<DataSourceModuleType ID="Microsoft.Exchange.15.Database.Storage.DataSource" Accessibility="Internal">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="IntervalSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="TimeoutSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="SyncTime" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="InstallPath" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ComputerName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="VerboseLogging" type="xsd:integer"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="IntervalSeconds" Selector="$Config/IntervalSeconds$" ParameterType="int"/>
<OverrideableParameter ID="TimeoutSeconds" Selector="$Config/TimeoutSeconds$" ParameterType="int"/>
<OverrideableParameter ID="SyncTime" Selector="$Config/SyncTime$" ParameterType="string"/>
<OverrideableParameter ID="VerboseLogging" Selector="$Config/VerboseLogging$" ParameterType="int"/>
</OverrideableParameters>
<ModuleImplementation>
<Composite>
<MemberModules>
<DataSource ID="Scheduler" TypeID="System!System.Scheduler">
<Scheduler>
<SimpleReccuringSchedule>
<Interval>$Config/IntervalSeconds$</Interval>
<SyncTime>$Config/SyncTime$</SyncTime>
</SimpleReccuringSchedule>
<ExcludeDates/>
</Scheduler>
</DataSource>
<ProbeAction ID="PS" TypeID="Microsoft.Exchange.15.PowershellPropertyBagCommandExecuter.Probe">
<FileName>MbxDbStorageCollection.ps1</FileName>
<FileContents>
param($computerName,$verboseOutput,$separate)
$jobs = @()
$scriptBagCount = 0
$script:returnPropertyBagToPipeline = $false;

#Get-PSSnapin *exch* -Registered | Add-PSSnapin -ErrorAction SilentlyContinue

#-------------------------------------------------------------------------------
# localized performance counters for 2008R2 and later
#-------------------------------------------------------------------------------

# Returns 2 letters LanguageName (and full for china)
function Get-CultureLanguage()
{
$cul = Get-UICulture
if ($cul.TwoLetterISOLanguageName -eq 'zh') {
return $cul.Name
}
return $cul.TwoLetterISOLanguageName;
}

# Build a hash array of offsets into the counter buffer. Use the index
# values from the performance data queries to access the offsets.
function Get-RegLocalizedPerfCounterNames($computerName = $env:COMPUTERNAME)
{
$path = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage"
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computerName)
$sub = $Reg.OpenSubKey($path)
if ($sub-eq $null) {
$Reg.Close()
return $null
}
$val= $sub.GetValue('Counter')

if (($val -eq $null) -or ($val.Length -eq 0))
{
$Reg.Close()
return $null
}
#return registry values
$hashArray = @{}
for($i=0; $i -lt $val.Length; $i=$i+2)
{
[UInt32] $key = [UInt32]$val[$i]
if ($hashArray.ContainsKey($key) -ne $true)
{
$hashArray.Add($key, $val[$i+1])
}
}
$Reg.Close()
return $hashArray
}

# Reverse function for Get-RegLocalizedPerfCounterNames
# dic['name']=index
function Get-RegLocalizedPerfCounterIndexes($computerName = $env:COMPUTERNAME)
{
$path = "SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage"
$Reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computerName)
$sub = $Reg.OpenSubKey($path)
if ($sub-eq $null) {
$Reg.Close()
return $null
}
$val= $sub.GetValue('Counter')

#if no data in regestry when return null
#$val = Get-ItemProperty -Path $path -Name "Counter" -ErrorAction SilentlyContinue
if (($val -eq $null) -or ($val.Length -eq 0))
{
$Reg.Close()
return $null
}
#return registry values
$hashArray = @{}
for($i=0; $i -lt $val.Length; $i=$i+2)
{
$key = $val[$i+1]
if ($key -eq $null) {
continue;
}

if ($hashArray.ContainsKey($key) -ne $true)
{
$hashArray.Add($key, @([UInt32]$val[$i]))
} else {
$hashArray[$key]= $hashArray[$key] + [UInt32]$val[$i]
}

}
$Reg.Close()
return $hashArray
}

$global:pdhInited = $false

#Returns the performance object name or counter name corresponding to the specified index.
Function Get-PdhLookupPerfNameByIndex([UInt32] $ID, $computerName = $env:COMPUTERNAME)
{
if ($global:pdhInited -eq $false) {
$code = '[DllImport("pdh.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern UInt32 PdhLookupPerfNameByIndex(string szMachineName, uint dwNameIndex, System.Text.StringBuilder szNameBuffer, ref uint pcchNameBufferSize);'
$t = Add-Type -MemberDefinition $code -PassThru -Name PerfCounter -Namespace Utility
$global:pdhInited = $true

}

$Buffer = New-Object System.Text.StringBuilder(1024)
[UInt32]$BufferSize = $Buffer.Capacity
$rv = [Utility.PerfCounter]::PdhLookupPerfNameByIndex($computerName, $id, $Buffer, [Ref]$BufferSize)

if ($rv -eq 0)
{
return $Buffer.ToString().Substring(0, $BufferSize-1)
}
else
{
Throw 'Get-PdhLookupPerfNameByIndex : Unable to retrieve localized name for performance counter ID ({0}) on computer "{1}".' -f $ID, $ComputerName
}
}

# inner function for localized non-exchange counters.
# Usage:
# arrayCounters = result of Get-RegLocalizedPerfCounterNames
# [object/counter]Conf = @(ID,'EnglishName')
# parameters = @(objectConf,CounterConf)
# Returns Get-Counter Result
# P.S...
function Get-LocalizedPerfCounter([hashtable]$arrayCounters, [String] $format, [array]$paramters)
{
$cul = Get-UICulture
#English language
if( $($cul.LCID -band 0xff) -ne 0x09)
{
#try to get locolized values
if ( $arrayCounters -ne $null)
{
#from regestry
for($i=0; $i -lt $paramters.Count; $i++)
{
[UInt32] $id = [UInt32]$paramters[$i][0]
if ($arrayCounters.ContainsKey($id) -eq $true)
{
$paramters[$i][1] = $arrayCounters[$id]
}
else
{
Throw 'Get-PerfCounter : Unable to retrieve localized name. (Index : {0} Name : {1})' -f $id,$paramters[$i][1]
}
}
}
else
{
#win32 api
for($i=0; $i -lt $paramters.Count; $i++)
{
[UInt32] $id = [UInt32]$paramters[$i][0]
$paramters[$i][1] = Get-PdhLookupPerfNameByIndex $id
}
}
}
#get counter
$counter = $format -f $paramters[0][1], $paramters[1][1]
return (Get-Counter -ComputerName $computerName -ErrorAction SilentlyContinue ($counter))
}

# wrapper for Get-LocalizedPerfCounter for multi instance counters
# Usage:
# arrayCounters = result of Get-RegLocalizedPerfCounterNames
# objectName = @(Id,'English Name')
# counterName = @(Id,'English Name')
# instances - string template for instances
# returns hashtable dic['instanceName']=value
function Get-MultiInstancePerfCounter ([hashtable]$arrayCounters, [array]$objectName,[array]$counterName,[string]$instances) {
$samples = (Get-LocalizedPerfCounter $arrayCounters $("\{0}($instances)\{1}") $($objectName, $counterName)).CounterSamples;
$resultSet = @{}
foreach ($sample in $samples) {
if ($sample.Path -match "\\\\.+\\.+\((.+)\)\\") {
$resultSet.Add($Matches[1],$sample.CookedValue)
}
}
return $resultSet
}

# wrapper for Get-LocalizedPerfCounter for single instance counters
# Usage:
# arrayCounters = result of Get-RegLocalizedPerfCounterNames
# objectName = @(Id,'English Name')
# counterName = @(Id,'English Name')
# returns CookedValue
function Get-SingleInstancePerfCounter ($arrayCounters, $objectName,$counterName) {
return (Get-LocalizedPerfCounter $arrayCounters ("\{0}\{1}") $($objectName, $counterName)).CounterSamples.CookedValue;
}

# Build dictionary for exchange coutners in form
# dic['objectName']['counterName'] = foreign counterName
# dic['objectName']['objectName'] = foreign objectName
function Get-ExchangeLocalizedPerfCounterNames ($computerName = $env:COMPUTERNAME) {
$lang = Get-CultureLanguage
$ht = Get-LocalizationHashTable
return $ht[$lang]
}

# Gets Exchange performance counters in localized way
# Usage: if counter is multiinstance returns hashtable dic['instanceName']=value
# if counter is singleinstance returns just value
function Get-ExchangePerfCounter ($objectName,$counterName,$instanceName = $null) {
$ht = $exchangeLocalizedCounterNames
$locObjectName=$objectName
$locCounterName = $counterName
if ($ht -ne $null -and $ht.ContainsKey($objectName)) {
$htCounter = $ht[$objectName]
if ($htCounter.ContainsKey($objectName)) {
$locObjectName = $htCounter[$objectName]
}
if ($htCounter.ContainsKey($counterName)) {
$locCounterName = $htCounter[$counterName]
}
}
if ($instanceName -eq $null) {
$value = Get-Counter -ComputerName $computerName "\$locObjectName\$locCounterName" -ErrorAction SilentlyContinue
return $value.CounterSamples.CookedValue
} else {
$samples = (Get-Counter -ComputerName $computerName "\$locObjectName($instanceName)\$locCounterName" -ErrorAction SilentlyContinue).CounterSamples;
$resultSet = @{}
foreach ($sample in $samples) {
if ($sample.Path -match "\\\\.+\\.+\((.+)\)\\") {
$resultSet.Add($Matches[1],$sample.CookedValue)
}
}
return $resultSet
}
}

#Creates SCOM Property Bag for performance instance. if value is null. recored is skipped
function createBag($objectName,$counterName,$instanceName,$value,$misc=@{}) {
if ($value -eq $null) {
LogEvent $INFORMATION_EVENT_TYPE "No Value for Property Bag ObjectName: $objectName CounterName: $counterName InstanceName: $instanceName" 2
return
}
$bag = $api.CreatePropertyBag()
$bag.AddValue('ObjectName',$objectName)
$bag.AddValue('CounterName',$counterName)
$bag.AddValue('InstanceName',$instanceName)
$bag.AddValue('Value',$value)
foreach ($prop in $misc.GetEnumerator()) {
$prop.AddValue($prop.Key,$prop.Value)
}

$Script:scriptBagCount++

if($script:returnPropertyBagToPipeline)
{
$bag
LogEvent $INFORMATION_EVENT_TYPE "New Property Bag (via pipeline) ObjectName: $objectName CounterName: $counterName InstanceName: $instanceName Value: $value" 2
}
else
{
$api.AddItem($bag)
LogEvent $INFORMATION_EVENT_TYPE "New Property Bag (via AddItem) ObjectName: $objectName CounterName: $counterName InstanceName: $instanceName Value: $value" 2
}

}

# Creates scom property bag by counter configuration for single InstanceName.
# Usage:
# configuration hashtable: @{ObjectName,CounterName,InstanceName}
# $pc - counter configuration in system
# $sc - counter configuration for counter in SCOM
# $misc - additional data for property bag @{Name=Value}
function SingleExchangeCounter([hashtable]$pc,[hashtable]$sc,[hashtable]$misc=@{}) {
$value = Get-ExchangePerfCounter $pc.ObjectName $pc.CounterName $pc.InstanceName
if ($pc.InstanceName -ne $null) {
$value = $value[$pc.InstanceName]
}
createBag $sc.ObjectName $sc.CounterName $sc.InstanceName $value $misc
}

# MultiInstance version of SingleExchangeCounter
function MultiExchangeCounter($pc,$sc,$misc=@{}) {
$values = Get-ExchangePerfCounter $pc.ObjectName $pc.CounterName $pc.InstanceName
foreach ($value in $values.GetEnumerator()){
createBag $sc.ObjectName $sc.CounterName $value.Key $value.Value $misc
}
}

# Creates entry in EventLog if current Debug_mode &gt;= level
function LogEvent($eventType, $message,[int]$level=1)
{
if ($DEBUG_MODE -ge $level)
{
$api.LogScriptEvent($DEBUG_MODULE, $SCRIPT_EVENT_ID, $eventType, $message);
}
}

# Gets a volume (inc. mnt point) name, id, capacity, freespace and caption by folder name
Function GetFolderVolume($path) {
GWMI Win32_Volume -ComputerName $computerName | ?{ $path -like "$($_.Name)*" } | sort Name -Descending | select -First 1 Name, DeviceId, Caption, Capacity, FreeSpace
}

if (!$separate) {
$exchangeLocalizedCounterNames = Get-ExchangeLocalizedPerfCounterNames $computerName
}
$globalLocalizedCounterNames = Get-RegLocalizedPerfCounterNames $computerName
$computerPrincipalName = $computerName
$DEBUG_MODE = $verboseOutput

$SCRIPT_EVENT_ID = 1474

$DEBUG_MODE = $verboseOutput
$DEBUG_MODULE = "MbxDbStorageCollection.ps1"
$UNEXPECTED_ERROR_ID = 102;

#Event Severity values
$INFORMATION_EVENT_TYPE = 0
$EVENT_TYPE_ERROR = 1

$api = new-object -comObject 'MOM.ScriptAPI';

LogEvent $INFORMATION_EVENT_TYPE ('Exchange Mailbox Database Storage Collection Script Started '+$computerPrincipalName)

#Function PublishMsExchangeDbInstancePerfCounter($counterName, $dbName, $instanceName) {
# $values = Get-ExchangePerfCounter 'MSExchange Database' $counterName "* - $dbName"
# $values.GetEnumerator() | foreach { createBag "Mailbox Database" $counterName $instanceName ($_.Value) }
#}

Function Main() {

$dbCounters = @('Database Page Fault Stalls/sec', 'I/O Database Reads Average Latency', 'I/O Database Writes Average Latency', 'I/O Log Reads Average Latency', 'I/O Log Writes Average Latency' )

$mdbs = Get-MailboxDatabase -Status -Server $computerName;
$copies = Get-MailboxDatabaseCopyStatus -Server $computerName
$vols = GWMI Win32_Volume -ComputerName $computerName| select DeviceId, Caption, Capacity, FreeSpace

foreach ($mdb in $mdbs) {
$dbName = $mdb.Name
$copy = $copies|? { $_.DatabaseName -eq $dbName }

# Database Size
if ($null -ne $mdb.DatabaseSize)
{
createBag 'Exchange Mailbox Database' 'Database Size (MB)' ($copy.Name) ($mdb.DatabaseSize.ToMB())
}
# Database available space
$dbVolume = $vols | ?{$_.DeviceId -eq $copy.DatabaseVolumeName }
$dbSpace = $dbVolume.FreeSpace / 1MB
createBag 'Exchange Mailbox Database' 'Database Available Space (MB)' ($copy.Name) ($dbSpace)

# Log size
$logsLen = Get-ChildItem $mdb.LogFolderPath -Filter *.log | measure -Property Length -Sum
$logsSize = $logsLen.Sum / 1MB
createBag 'Exchange Mailbox Database' 'Transaction Log Size (MB)' ($copy.Name) ($logsSize)

# Logs available space
$logVolume = $vols | ?{$_.DeviceId -eq $copy.LogVolumeName }
$logSpace = $logVolume.FreeSpace / 1MB
createBag 'Exchange Mailbox Database' 'Transaction Log Available Space (MB)' ($copy.Name) ($logSpace)

# Index size
$indexLen = Split-Path -Path $mdb.EdbFilePath | Get-ChildItem | ?{ $_.PSIsContainer } | Get-ChildItem -Recurse | measure -Property Length -Sum
$indexSize = $indexLen.Sum / 1MB
createBag 'Exchange Mailbox Database' 'Index Size (MB)' ($copy.Name) ($indexSize)

# $dbCounters | % { PublishMsExchangeDbInstancePerfCounter $_ $dbName ($copy.Name) }
if ($null -ne $mdb.DatabaseSize)
{
$msg = "Storage Space: DB: " + $_.Name + " - size: " + $mdb.DatabaseSize.ToMB() + " - space: " + $dbSpace + " - logsize: " + $logsSize + " - logspace:" + $logSpace + " - indexsize: " + $indexSize
LogEvent $INFORMATION_EVENT_TYPE $msg
}
}

LogEvent $INFORMATION_EVENT_TYPE 'Rule DataSource Finished'
if ($Script:scriptBagCount -gt 0) {
$api.ReturnItems()
}
}

Main

trap
{
if ($api -eq $null) {
$api = new-object -comObject 'MOM.ScriptAPI';
}
$msg = "($($_.InvocationInfo.ScriptLineNumber)): $($_.InvocationInfo.Line)`r`n Exception: $($_.Exception)"
$api.LogScriptEvent(
$DEBUG_MODULE,
$UNEXPECTED_ERROR_ID,
$EVENT_TYPE_ERROR,
$msg);

exit -1;
}

</FileContents>
<Parameters>'$Config/ComputerName$' $Config/VerboseLogging$ 1</Parameters>
<InstallPath>$Config/InstallPath$</InstallPath>
<TimeoutSeconds>$Config/TimeoutSeconds$</TimeoutSeconds>
</ProbeAction>
<!--ConditionDetection ID="MbxFilter" TypeID="System!System.ExpressionFilter">
<Expression>
<SimpleExpression>
<ValueExpression>
<Value Type="String">$Target/Host/Property[Type='Microsoft.Exchange.15.Server']/IsMailboxServer$</Value>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="String">True</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</ConditionDetection-->
</MemberModules>
<Composition>
<Node ID="PS">
<!--<Node ID="MbxFilter">-->
<Node ID="Scheduler"/>
</Node>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.PropertyBagData</OutputType>
</DataSourceModuleType>