Windows Logical Disk Free Space Data Source Module

Microsoft.Windows.Server.10.0.FreeSpace.Moduletype (DataSourceModuleType)

Monitor the Logical Disk Free Space on Windows Servers

Element properties:

TypeDataSourceModuleType
IsolationAny
AccessibilityInternal
RunAsDefault
OutputTypeSystem.BaseData

Member Modules:

ID Module Type TypeId RunAs 
Scheduler DataSource System.Scheduler Default
PA ProbeAction Microsoft.Windows.PowerShellPropertyBagProbe Default
EmptyCD ConditionDetection System.ExpressionFilter Default
InstanceFilter ConditionDetection System.ExpressionFilter Default

Overrideable Parameters:

IDParameterTypeSelectorDisplay NameDescription
Intervalint$Config/IntervalSeconds$Interval secondsHow frequently (in seconds) the value should be sampled.
SystemDriveWarningMBytesThresholddouble$Config/SystemDriveWarningMBytesThreshold$Warning MBytes Threshold for System Drives
SystemDriveWarningPercentThresholddouble$Config/SystemDriveWarningPercentThreshold$Warning \% Threshold for System Drives
SystemDriveErrorMBytesThresholddouble$Config/SystemDriveErrorMBytesThreshold$Error MBytes Threshold for System Drives
SystemDriveErrorPercentThresholddouble$Config/SystemDriveErrorPercentThreshold$Error \% Threshold for System Drives
NonSystemDriveWarningMBytesThresholddouble$Config/NonSystemDriveWarningMBytesThreshold$Warning MBytes Threshold for Non-System Drives
NonSystemDriveWarningPercentThresholddouble$Config/NonSystemDriveWarningPercentThreshold$Warning \% Threshold for Non-System Drives
NonSystemDriveErrorMBytesThresholddouble$Config/NonSystemDriveErrorMBytesThreshold$Error MBytes Threshold for Non-System Drives
NonSystemDriveErrorPercentThresholddouble$Config/NonSystemDriveErrorPercentThreshold$Error \% Threshold for Non-System Drives

Source Code:

<DataSourceModuleType ID="Microsoft.Windows.Server.10.0.FreeSpace.Moduletype" Accessibility="Internal" Batching="false">
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="ComputerName" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="DiskLabel" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="IntervalSeconds" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SystemDriveWarningMBytesThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SystemDriveWarningPercentThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SystemDriveErrorMBytesThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SystemDriveErrorPercentThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="NonSystemDriveWarningMBytesThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="NonSystemDriveWarningPercentThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="NonSystemDriveErrorMBytesThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="NonSystemDriveErrorPercentThreshold" type="xsd:double"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="Interval" Selector="$Config/IntervalSeconds$" ParameterType="int"/>
<OverrideableParameter ID="SystemDriveWarningMBytesThreshold" Selector="$Config/SystemDriveWarningMBytesThreshold$" ParameterType="double"/>
<OverrideableParameter ID="SystemDriveWarningPercentThreshold" Selector="$Config/SystemDriveWarningPercentThreshold$" ParameterType="double"/>
<OverrideableParameter ID="SystemDriveErrorMBytesThreshold" Selector="$Config/SystemDriveErrorMBytesThreshold$" ParameterType="double"/>
<OverrideableParameter ID="SystemDriveErrorPercentThreshold" Selector="$Config/SystemDriveErrorPercentThreshold$" ParameterType="double"/>
<OverrideableParameter ID="NonSystemDriveWarningMBytesThreshold" Selector="$Config/NonSystemDriveWarningMBytesThreshold$" ParameterType="double"/>
<OverrideableParameter ID="NonSystemDriveWarningPercentThreshold" Selector="$Config/NonSystemDriveWarningPercentThreshold$" ParameterType="double"/>
<OverrideableParameter ID="NonSystemDriveErrorMBytesThreshold" Selector="$Config/NonSystemDriveErrorMBytesThreshold$" ParameterType="double"/>
<OverrideableParameter ID="NonSystemDriveErrorPercentThreshold" Selector="$Config/NonSystemDriveErrorPercentThreshold$" ParameterType="double"/>
</OverrideableParameters>
<ModuleImplementation Isolation="Any">
<Composite>
<MemberModules>
<DataSource ID="Scheduler" TypeID="System!System.Scheduler">
<Scheduler>
<SimpleReccuringSchedule>
<Interval>$Config/IntervalSeconds$</Interval>
<SyncTime/>
</SimpleReccuringSchedule>
<ExcludeDates/>
</Scheduler>
</DataSource>
<ProbeAction ID="PA" TypeID="Windows!Microsoft.Windows.PowerShellPropertyBagProbe">
<ScriptName>Microsoft.Windows.Server.FreeSpace.ps1</ScriptName>
<ScriptBody><Script>

param ($TargetComputer, $DebugFlag, $SysWarningMB, $SysWarningPct, $SysErrorMB, $SysErrorPct, $NonSysWarningMB, $NonSysWarningPct, $NonSysErrorMB, $NonSysErrorPct)

$ErrorActionPreference = "Stop"

# Event type constants
$EVENT_TYPE_LOG = 0
$EVENT_TYPE_ERROR = 1
$EVENT_TYPE_WARNING = 2
$EVENT_TYPE_INFORMATION = 4

# Typed property bag constants
$PROPERTY_TYPE_ALERT = 0
$PROPERTY_TYPE_EVENT = 1
$PROPERTY_TYPE_PERFORMANCE = 2
$PROPERTY_TYPE_STATE = 3

# State type constants
$STATE_SUCCESS = "Success"
$STATE_WARNING = "Warning"
$STATE_ERROR = "Error"

$momAPI = new-object -comObject MOM.ScriptAPI
Import-Module CimCmdlets # Workaround to force load CIM cmdlets

# WMI Constant
$wbemCimtypeUseDefault = 0 #Use Default Type CIM type - Custom
$wbemCimtypeSint16 = 2 #Signed 16-bit integer
$wbemCimtypeSint32 = 3 #Signed 32-bit integer
$wbemCimtypeReal32 = 4 #32-bit real number
$wbemCimtypeReal64 = 5 #64-bit real number
$wbemCimtypeString = 8 #String
$wbemCimtypeBoolean = 11 #Boolean value
$wbemCimtypeObject = 13 #CIM object
$wbemCimtypeSint8 = 16 #Signed 8-bit integer
$wbemCimtypeUint8 = 17 #Unsigned 8-bit integer
$wbemCimtypeUint16 = 18 #Unsigned 16-bit integer
$wbemCimtypeUint32 = 19 #Unsigned 32-bit integer
$wbemCimtypeSint64 = 20 #Signed 64-bit integer
$wbemCimtypeUint64 = 21 #Unsigned 64-bit integer
$wbemCimtypeDatetime = 101 #Date/time value
$wbemCimtypeReference = 102 #Reference to a CIM object
$wbemCimtypeChar16 = 103 #16-bit character

$ErrAction_None = 0
$ErrAction_Trace = 1
$ErrAction_ThrowError = 16
$ErrAction_Abort = 32
$ErrAction_ThrowErrorAndAbort = 48

$DISKSIZE_BYTES_IN_MB = 1048576

$g_ErrorEventNumber = 4001
$g_TraceEventNumber = 4002
$g_DebugFlag = $false

#---------------------------------------------------------------------------
# Returns WMI Instance requested. Tries to execute WMI query a N times.
#---------------------------------------------------------------------------
Function WMIGetInstanceExTryN
{
param ([string]$sTargetComputer,
[string]$sNamespace,
[string]$sInstanceQuery,
[int]$N)

for ($i = 0; $i -lt $N; $i++)
{
$error.Clear();
$oInstance = Get-CimInstance -ComputerName $sTargetComputer -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
if ($error.Count -gt 0)
{
if ($i -eq ($N-1))
{
ThrowScriptError ("The class name '" + $sInstanceQuery + "' returned no instances. Please check to see if this is a valid WMI class name.") $error[0]
}
}
else
{
break;
}
sleep -m 1000
}

return $oInstance
}

#---------------------------------------------------------------------------
# Returns WMI Instance requested.
#---------------------------------------------------------------------------
Function WMIGetInstanceEx
{
param ([string]$sTargetComputer,
[string]$sNamespace,
[string]$sInstanceQuery)

$error.Clear();
$oInstance = Get-CimInstance -ComputerName $sTargetComputer -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
if ($error.Count -gt 0)
{
ThrowScriptError ("The class name '" + $sInstanceQuery + "' returned no instances. Please check to see if this is a valid WMI class name.") $error[0]
}

return $oInstance
}

#---------------------------------------------------------------------------
# Connect to WMI.
#---------------------------------------------------------------------------
Function WMIConnect
{
param ([string]$sTargetComputer,
[string]$sNamespace)

$error.Clear()

# !!! Refactoring comment:
# Original VBScript only tries to connect to the namespace. Piping to get only the first one saves time.
$oWMI = Get-CimClass -ComputerName $sTargetComputer -Namespace $sNamespace -ErrorAction SilentlyContinue | select -First 1
if ($error.Count -gt 0)
{
$msg = "Unable to open WMI Namespace 'winmgmts:\\" + $sTargetComputer + "\" + $sNamespace + "'. Check to see if the WMI service is enabled and running, and ensure this WMI namespace exists."
ThrowScriptError $msg $error[0]
}
}

#---------------------------------------------------------------------------
# Returns WMI Instance requested.
#---------------------------------------------------------------------------
Function WMIGetInstance
{
param ([string]$sTargetComputer,
[string]$sNamespace,
[string]$sInstanceQuery)

WMIConnect $sTargetComputer $sNamespace
$oInstance = WMIGetInstanceEx $sTargetComputer $sNamespace $sInstanceQuery
return $oInstance
}

#---------------------------------------------------------------------------
# Returns WMI Instance requested.
#---------------------------------------------------------------------------
Function WMIGetInstanceNoAbort
{
param ([string]$sTargetComputer,
[string]$sNamespace,
[string]$sInstanceQuery)

$oInstance = Get-CimInstance -ComputerName $sTargetComputer -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue

return $oInstance
}

#---------------------------------------------------------------------------
# Executes the WMI query and returns the result set.
#---------------------------------------------------------------------------
Function WMIExecQuery
{
param ([string]$sTargetComputer,
[string]$sNamespace,
[string]$sQuery)

$error.Clear()

# !!! Refactoring comment:
# Original VBScript only tries to connect to the namespace. Piping to get only the first one saves time.
$oWMI = Get-CimClass -ComputerName $sTargetComputer -Namespace $sNamespace -ErrorAction SilentlyContinue | select -First 1
if ($error.Count -gt 0)
{
$msg = "Unable to open WMI Namespace 'winmgmts:\\" + $sTargetComputer + "\" + $sNamespace + "'. Check to see if the WMI service is enabled and running, and ensure this WMI namespace exists."
ThrowScriptError $msg, $error[0]
}

$oQuery = Get-CimInstance -ComputerName $sTargetComputer -Namespace $sNamespace -Query $sQuery -ErrorAction SilentlyContinue
if ($error.Count -gt 0)
{
ThrowScriptError ("The Query '" + $sQuery + "' returned an invalid result set. Please check to see if this is a valid WMI Query.") $error[0]
}

return $oQuery
}

#---------------------------------------------------------------------------
# Executes the WMI query and returns the result set, no abort version.
#---------------------------------------------------------------------------
Function WMIExecQueryNoAbort
{
param ([string]$sTargetComputer,
[string]$sNamespace,
[string]$sQuery)

$oQuery = Get-CimInstance -ComputerName $sTargetComputer -Namespace $sNamespace -Query $sQuery -ErrorAction SilentlyContinue

return $oQuery
}

#---------------------------------------------------------------------------
# Creates an event and sends it back to the mom server.
#---------------------------------------------------------------------------
Function ThrowScriptErrorNoAbort
{
param ([string]$sMessage,
[System.Management.Automation.ErrorRecord]$oErr)
# Retrieve the name of this (running) script
$ScriptFileName = $MyInvocation.ScriptName

if ($oErr -ne $null)
{
$sMessage = $sMessage + ". " + $oErr.ErrorDetails
}

$momAPI.LogScriptEvent($ScriptFileName, $g_ErrorEventNumber, $EVENT_TYPE_ERROR, $sMessage)

Write-Host $sMessage
}

#---------------------------------------------------------------------------
# Creates an event and sends it back to the mom server.
#---------------------------------------------------------------------------
Function ThrowScriptError
{
param ([string]$sMessage,
[System.Management.Automation.ErrorRecord]$oErr)
ThrowScriptErrorNoAbort $sMessage $oErr
exit
}

#---------------------------------------------------------------------------
# Verifies that number of arguments is correct
#---------------------------------------------------------------------------
Function VerifyNumberOfArguments
{
param ($NumberOfArguments)

if ($args.Length -ne $NumberOfArguments)
{
$sArgs = ""
foreach ($argument in $args)
{
$sArgs += " {" + $argument + "}"
}
ThrowScriptError ("Invalid number of arguments (" + $args.Length + " instead of " + $NumberOfArguments + "). Arguments:" + $sArgs) $null
}
}

#---------------------------------------------------------------------------
# Outputs to file and echo for debugging purposes
#---------------------------------------------------------------------------
Function TraceLogMessage
{
param ([string]$sMessage)

Write-Host $sMessage

If ($g_DebugFlag -eq $true)
{
# Retrieve the name of this (running) script
$ScriptFileName = $MyInvocation.ScriptName

$momAPI.LogScriptEvent($ScriptFileName, $g_TraceEventNumber, $EVENT_TYPE_INFORMATION, $sMessage)
}
}

#---------------------------------------------------------------------------
# Verifies the expression. If equals to False then generates an error and quits the script
# Usage:
# Verify Not WMISet Is Nothing, "WMISet is invalid!"
# Verify WMISet.Count = 1, "Invalid quantity of services with name 'Server' (qty = " &amp; WMISet.Count &amp; ")."
#---------------------------------------------------------------------------
Function Verify
{
param ([bool]$bBool,
[string]$sMessage)

If ($bBool -eq $false)
{
ThrowScriptError $sMessage $null
}
}

Function GetRegistryKeyValue
{
param ([string]$keyPath,
[string]$key)

$error.Clear()

$strKeyValue = Get-ItemProperty -Path $keyPath -Name $key -ErrorAction SilentlyContinue
if ($error.Count -gt 0)
{
ThrowScriptError ("An error occurred while reading the registry: '" + $keyPath + $key + "'") $error[0]
}
return $strKeyValue.$key
}


#---------------------------------------------------------------------------
# Function: ExpressedInMB
# Usage:
# Parameter (SizeInBytes)
# Returns the Size Expressed in MBytes
#---------------------------------------------------------------------------
Function ExpressedInMB
{
param ($SizeInBytes)

$NumberSizeExpInMB = [math]::Round($SizeInBytes / $DISKSIZE_BYTES_IN_MB, 0)
return $NumberSizeExpInMB
}


#Copyright (c) Microsoft Corporation. All rights reserved.
#
# Parameters that should be passed to this script
# 0 Computer (FQDN)
# 1 Debug Flag - If True then script can output some trace information to event log.
# 2 System Drive Warning MBytes Threshold
# 3 System Drive Warning Percent Threshold
# 4 System Drive Error MBytes Threshold
# 5 System Drive Error Percent Threshold
# 6 Non System Drive Warning MBytes Threshold
# 7 Non System Drive Warning Percent Threshold
# 8 Non System Drive Error MBytes Threshold
# 9 Non System Drive Error Percent Threshold

$BYTES_IN_MB = 1048576 #=2^20

$DriveHealthyState = 0
$DriveWarningState = 1
$DriveErrorState = 2

Function Main()
{
$g_DebugFlag = [System.Convert]::ToBoolean($DebugFlag)

$IsVolumeInfoSupported = Is_Win32_Volume_Supported $TargetComputer
$SystemDrive = Get_System_Drive $TargetComputer

$bBagIsNotEmpty = "false"

if ($IsVolumeInfoSupported)
{
$oWmiDiskSet = WMIGetInstance $TargetComputer "root\cimv2" "Win32_Volume"
}
else
{
$oWmiDiskSet = WMIExecQuery $TargetComputer "root\cimv2" "select * from Win32_LogicalDisk where (DriveType=3 or DriveType=6) and FileSystem != null"
}

foreach ($oWmiDisk in $oWmiDiskSet)
{
If ($oWmiDisk.DriveType -eq 3)
{
$nFreeSpace = $oWmiDisk.FreeSpace
if ($nFreeSpace -eq $null) { $nFreeSpace = 0 }

if ($IsVolumeInfoSupported)
{
$sDriveLetter = $oWmiDisk.DriveLetter
$nMaxSize = $oWmiDisk.Capacity
If ($sDriveLetter -eq $null)
{
$sDriveLetter = $oWmiDisk.Name.Substring(0, $oWmiDisk.Name.Length-1)
}
}
else
{
$sDriveLetter = $oWmiDisk.DeviceId
$nMaxSize = $oWmiDisk.Size
}

# If nMaxSize is null or 0 then drive is not formatted - do not return property bag for it.
if ($nMaxSize -ne $null -and $nMaxSize -gt 0)
{
$nPctFree = [math]::Round($nFreeSpace / $nMaxSize * 100, 0)
$nMBFree = [math]::Round($nFreeSpace / $BYTES_IN_MB, 0)
if ($sDriveLetter -eq $SystemDrive)
{
$bIsSystemDrive = "true"
}
else
{
$bIsSystemDrive = "false"
}

$bBagIsNotEmpty = "true"

$oBag = $momAPI.CreatePropertyBag()
$oBag.AddValue("IsNotEmpty", $bBagIsNotEmpty)
$oBag.AddValue("PerfInstance", $sDriveLetter)
$oBag.AddValue("DiskState",
(GetDriveHealthState $bIsSystemDrive $SysWarningMB $SysWarningPct $SysErrorMB $SysErrorPct $NonSysWarningMB $NonSysWarningPct $NonSysErrorMB $NonSysErrorPct $nPctFree $nMBFree) )
$oBag

}
}
}

if ($bBagIsNotEmpty -eq "false")
{
$oBag = $momAPI.CreatePropertyBag()
$oBag.AddValue("IsNotEmpty", $bBagIsNotEmpty)
$oBag
}

}

Function GetDriveHealthState
{
param ($bIsSystem, $dSysWarnMB, $dSysWarnPct, $dSysErrorMB, $dSysErrorPct, $dNonSysWarnMB, $dNonSysWarnPct, $dNonSysErrorMB, $dNonSysErrorPct, $dPctFree, $dMBFree)

$fResult = $DriveHealthyState

If ($bIsSystem)
{
If (($dMBFree -le $dSysErrorMB) -and ($dPctFree -le $dSysErrorPct))
{
$fResult = $DriveErrorState
}
ElseIf (($dMBFree -le $dSysWarnMB) -and ($dPctFree -le $dSysWarnPct))
{
$fResult = $DriveWarningState
}
}
Else
{
If (($dMBFree -le $dNonSysErrorMB) -and ($dPctFree -le $dNonSysErrorPct))
{
$fResult = $DriveErrorState
}
ElseIf (($dMBFree -le $dNonSysWarnMB) -and ($dPctFree -le $dNonSysWarnPct))
{
$fResult = $DriveWarningState
}
}

return $fResult
}

Function Is_Win32_Volume_Supported($TargetComputer)
{
$blnRet = $false
if ((Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels").GetValue("NanoServer") -eq 1)
{
return $blnRet #NanoServer does not support Win32_Volume
}
$objWMISet = WMIGetInstance $TargetComputer "root\cimv2" "Win32_OperatingSystem"
foreach ($objWMIOS in $objWMISet)
{
if ([int]$objWMIOS.BuildNumber -ge 3624) { $blnRet = $true }
}
return $blnRet
}


Function Get_System_Drive($TargetComputer)
{
$objWMISet = WMIGetInstance $TargetComputer "root\cimv2" "Win32_OperatingSystem"
foreach ($objWMIOS in $objWMISet)
{
return $objWMIOS.SystemDirectory.SubString(0,2)
}
}

Main
</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>TargetComputer</Name>
<Value>$Config/ComputerName$</Value>
</Parameter>
<Parameter>
<Name>DebugFlag</Name>
<Value>false</Value>
</Parameter>
<Parameter>
<Name>SysWarningMB</Name>
<Value>$Config/SystemDriveWarningMBytesThreshold$</Value>
</Parameter>
<Parameter>
<Name>SysWarningPct</Name>
<Value>$Config/SystemDriveWarningPercentThreshold$</Value>
</Parameter>
<Parameter>
<Name>SysErrorMB</Name>
<Value>$Config/SystemDriveErrorMBytesThreshold$</Value>
</Parameter>
<Parameter>
<Name>SysErrorPct</Name>
<Value>$Config/SystemDriveErrorPercentThreshold$</Value>
</Parameter>
<Parameter>
<Name>NonSysWarningMB</Name>
<Value>$Config/NonSystemDriveWarningMBytesThreshold$</Value>
</Parameter>
<Parameter>
<Name>NonSysWarningPct</Name>
<Value>$Config/NonSystemDriveWarningPercentThreshold$</Value>
</Parameter>
<Parameter>
<Name>NonSysErrorMB</Name>
<Value>$Config/NonSystemDriveErrorMBytesThreshold$</Value>
</Parameter>
<Parameter>
<Name>NonSysErrorPct</Name>
<Value>$Config/NonSystemDriveErrorPercentThreshold$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>300</TimeoutSeconds>
</ProbeAction>
<ConditionDetection ID="EmptyCD" TypeID="System!System.ExpressionFilter">
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Boolean">Property[@Name='IsNotEmpty']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</ConditionDetection>
<ConditionDetection ID="InstanceFilter" TypeID="System!System.ExpressionFilter">
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="String">Property[@Name='DiskLabel']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="String">$Config/DiskLabel$</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</ConditionDetection>
</MemberModules>
<Composition>
<Node ID="InstanceFilter">
<Node ID="EmptyCD">
<Node ID="PA">
<Node ID="Scheduler"/>
</Node>
</Node>
</Node>
</Composition>
</Composite>
</ModuleImplementation>
<OutputType>System!System.BaseData</OutputType>
</DataSourceModuleType>