Logical Disk Fragmentation Level Monitor Type

Microsoft.Windows.Server.10.0.LogicalDisk.DefragAnalysis.Monitortype (UnitMonitorType)

Element properties:

RunAsDefault
AccessibilityInternal
Support Monitor RecalculateFalse

Member Modules:

ID Module Type TypeId RunAs 
Scheduler DataSource System.Scheduler Default
Script ProbeAction Microsoft.Windows.PowerShellPropertyBagProbe Default
ErrorFilter ConditionDetection System.ExpressionFilter Default
FilterDrive ConditionDetection System.ExpressionFilter Default
SuccessFilter ConditionDetection System.ExpressionFilter Default

Overrideable Parameters:

IDParameterTypeSelectorDisplay NameDescription
SchedulerStartstring$Config/SchedulerStart$Start timeThe time of the day (HH:MM) that the fragmentation check should be run
SchedulerDaysOfWeekMaskint$Config/SchedulerDaysOfWeekMask$Days Of Week maskThe day(s) that the fragmentation check should be run. The values for the days are Sunday (1), Monday (2), Tuesday (4), Wednesday (8), Thursday (16), Friday (32) and Saturday (64). To specify multiple days, add the values for the days together. For example, for Monday, Wednesday, and Friday, specify 42 (2+8+32).
FilePercentFragmentationThresholddouble$Config/FilePercentFragmentationThreshold$File Percent Fragmentation ThresholdIf the "Use OS Recommendation" is set to "False" then this value will be used as the threshold for fragmentation levels.
UseOSRecommendationbool$Config/UseOSRecommendation$Use OS RecommendationThis parameter determines whether the fragmentation level check will use the default threshold determined by the operating system or not. If this parameter is set to "False" then the value from "File Percent Fragmentation Threshold" will be used.

Source Code:

<UnitMonitorType ID="Microsoft.Windows.Server.10.0.LogicalDisk.DefragAnalysis.Monitortype" Accessibility="Internal">
<MonitorTypeStates>
<MonitorTypeState ID="Warning" NoDetection="false"/>
<MonitorTypeState ID="Success" NoDetection="false"/>
</MonitorTypeStates>
<Configuration>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SchedulerStart" type="xsd:string"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="SchedulerDaysOfWeekMask" type="xsd:integer"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="FilePercentFragmentationThreshold" type="xsd:double"/>
<xsd:element xmlns:xsd="http://www.w3.org/2001/XMLSchema" minOccurs="1" name="UseOSRecommendation" type="xsd:boolean"/>
</Configuration>
<OverrideableParameters>
<OverrideableParameter ID="SchedulerStart" Selector="$Config/SchedulerStart$" ParameterType="string"/>
<OverrideableParameter ID="SchedulerDaysOfWeekMask" Selector="$Config/SchedulerDaysOfWeekMask$" ParameterType="int"/>
<OverrideableParameter ID="FilePercentFragmentationThreshold" Selector="$Config/FilePercentFragmentationThreshold$" ParameterType="double"/>
<OverrideableParameter ID="UseOSRecommendation" Selector="$Config/UseOSRecommendation$" ParameterType="bool"/>
</OverrideableParameters>
<MonitorImplementation>
<MemberModules>
<DataSource ID="Scheduler" TypeID="System!System.Scheduler">
<Scheduler>
<WeeklySchedule>
<Windows>
<Daily>
<Start>$Config/SchedulerStart$</Start>
<End>$Config/SchedulerStart$</End>
<DaysOfWeekMask>$Config/SchedulerDaysOfWeekMask$</DaysOfWeekMask>
</Daily>
</Windows>
</WeeklySchedule>
<ExcludeDates/>
</Scheduler>
</DataSource>
<ProbeAction ID="Script" TypeID="Windows!Microsoft.Windows.PowerShellPropertyBagProbe">
<ScriptName>Microsoft.Windows.Server.LogicalDisk.DefragAnalysis.ps1</ScriptName>
<ScriptBody><Script>

param ($TargetComputer)

$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)

Function Main()
{
# Fragmentation analysis requires lots of time and consumes lots of CPU.
# So it is important to trace such activity in events log in order to be able
# to understand what is going on.
$g_DebugFlag = $true

$IsVolumeInfoSupported = Is_Win32_Volume_Supported $TargetComputer

if ($IsVolumeInfoSupported -ne $true)
{
exit
}

$oWmiDiskSet = WMIGetInstance $TargetComputer "root\cimv2" "Win32_Volume WHERE (DriveType=3 or DriveType=6) and FileSystem!=null"

foreach ($oWmiDisk in $oWmiDiskSet)
{
$sDriveLetter = $oWmiDisk.DriveLetter
if ($sDriveLetter -eq $null)
{
$sDriveLetter = $oWmiDisk.Name
$sDriveLetter = $sDriveLetter.SubString(0, $sDriveLetter.Length - 1)
}
if ($sDriveLetter -ne $null -and $sDriveLetter -ne "")
{
TraceLogMessage ("Running DefragAnalysis (disk: " + $sDriveLetter + "; computer: " + $TargetComputer + ").")

$error.Clear()
$ret = Invoke-CimMethod -CimInstance $oWmiDisk -MethodName DefragAnalysis

TraceLogMessage ("DefragAnalysis results (return code: " + $ret.ReturnValue + ")(disk: " + $sDriveLetter + "; computer: " + $TargetComputer + "): OSRecommended = " + $ret.DefragRecommended + "; FilePercentFragmentation = " + $ret.DefragAnalysis.FilePercentFragmentation + ".")

If ($ret.ReturnValue -ne 0 -or $error.Count -ne 0)
{
$oBag = $momAPI.CreatePropertyBag()
$oBag.AddValue("DiskLabel", $sDriveLetter)
$oBag.AddValue("OSRecommended", $ret.DefragRecommended)
$oBag.AddValue("FilePercentFragmentation", $ret.DefragAnalysis.FilePercentFragmentation)
$oBag
}
}
}
}

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
}

Main

</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>TargetComputer</Name>
<Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/NetworkName$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>3600</TimeoutSeconds>
</ProbeAction>
<ConditionDetection ID="FilterDrive" TypeID="System!System.ExpressionFilter">
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="String">Property[@Name='DiskLabel']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="String">$Target/Property[Type="Windows!Microsoft.Windows.LogicalDevice"]/DeviceID$</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</ConditionDetection>
<ConditionDetection ID="ErrorFilter" TypeID="System!System.ExpressionFilter">
<Expression>
<Or>
<Expression>
<And>
<Expression>
<SimpleExpression>
<ValueExpression>
<Value Type="Boolean">$Config/UseOSRecommendation$</Value>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">false</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Double">Property[@Name='FilePercentFragmentation']</XPathQuery>
</ValueExpression>
<Operator>Greater</Operator>
<ValueExpression>
<Value Type="Double">$Config/FilePercentFragmentationThreshold$</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</And>
</Expression>
<Expression>
<And>
<Expression>
<SimpleExpression>
<ValueExpression>
<Value Type="Boolean">$Config/UseOSRecommendation$</Value>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Boolean">Property[@Name='OSRecommended']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</And>
</Expression>
</Or>
</Expression>
</ConditionDetection>
<ConditionDetection ID="SuccessFilter" TypeID="System!System.ExpressionFilter">
<Expression>
<Not>
<Expression>
<Or>
<Expression>
<And>
<Expression>
<SimpleExpression>
<ValueExpression>
<Value Type="Boolean">$Config/UseOSRecommendation$</Value>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">false</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Double">Property[@Name='FilePercentFragmentation']</XPathQuery>
</ValueExpression>
<Operator>Greater</Operator>
<ValueExpression>
<Value Type="Double">$Config/FilePercentFragmentationThreshold$</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</And>
</Expression>
<Expression>
<And>
<Expression>
<SimpleExpression>
<ValueExpression>
<Value Type="Boolean">$Config/UseOSRecommendation$</Value>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
<Expression>
<SimpleExpression>
<ValueExpression>
<XPathQuery Type="Boolean">Property[@Name='OSRecommended']</XPathQuery>
</ValueExpression>
<Operator>Equal</Operator>
<ValueExpression>
<Value Type="Boolean">true</Value>
</ValueExpression>
</SimpleExpression>
</Expression>
</And>
</Expression>
</Or>
</Expression>
</Not>
</Expression>
</ConditionDetection>
</MemberModules>
<RegularDetections>
<RegularDetection MonitorTypeStateID="Warning">
<Node ID="ErrorFilter">
<Node ID="FilterDrive">
<Node ID="Script">
<Node ID="Scheduler"/>
</Node>
</Node>
</Node>
</RegularDetection>
<RegularDetection MonitorTypeStateID="Success">
<Node ID="SuccessFilter">
<Node ID="FilterDrive">
<Node ID="Script">
<Node ID="Scheduler"/>
</Node>
</Node>
</Node>
</RegularDetection>
</RegularDetections>
</MonitorImplementation>
</UnitMonitorType>