Logical Disk Defragmentation

Microsoft.Windows.Server.10.0.LogicalDisk.DefragAnalysis.Recovery (Recovery)

Performs defragmentation of the logical disk.

Element properties:

TargetMicrosoft.Windows.Server.10.0.LogicalDisk
MonitorMicrosoft.Windows.Server.10.0.LogicalDisk.DefragAnalysis
Reset MonitorTrue
RemotableTrue
Timeout3600
CategoryMaintenance
Enabledfalse
AccessibilityInternal

Member Modules:

ID Module Type TypeId RunAs 
Defrag WriteAction Microsoft.Windows.PowerShellWriteAction Default

Source Code:

<Recovery ID="Microsoft.Windows.Server.10.0.LogicalDisk.DefragAnalysis.Recovery" Accessibility="Internal" Enabled="false" Target="ServervNext!Microsoft.Windows.Server.10.0.LogicalDisk" Monitor="Microsoft.Windows.Server.10.0.LogicalDisk.DefragAnalysis" ResetMonitor="true" ExecuteOnState="Warning" Remotable="true" Timeout="3600">
<Category>Maintenance</Category>
<WriteAction ID="Defrag" TypeID="Windows!Microsoft.Windows.PowerShellWriteAction">
<ScriptName>Microsoft.Windows.Server.LogicalDisk.Defrag.ps1</ScriptName>
<ScriptBody><Script>

param ($TargetComputer, $DiskLabel)

$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
if ($null -eq $momAPI)
{
exit -1
}

Function Load-CimModules
{
$ErrorActionPreference = 'SilentlyContinue'
$error.Clear()

$CimModule = Get-Module CimCmdlets

if ($null -eq $CimModule)
{
Import-Module CimCmdlets

}
}

Load-CimModules
# 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();

Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oInstance = Get-CimInstance -CimSession $cimsession -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
}
catch
{
$oInstance = Get-WMIObject -ComputerName $sTargetComputer -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}

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();

Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oInstance = Get-CimInstance -CimSession $cimsession -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
}
catch
{
$oInstance = Get-WMIObject -ComputerName $sTargetComputer -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}

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.

Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oWMI = Get-CimClass -CimSession $cimsession -Namespace $sNamespace -ErrorAction SilentlyContinue | select -First 1
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}

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)


Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oInstance = Get-CimInstance -CimSession $cimsession -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
}
catch
{
$oInstance = Get-WMIObject -ComputerName $sTargetComputer -Namespace $sNamespace -Query ("Select * from "+$sInstanceQuery) -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}

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.
Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oWMI = Get-CimClass -CimSession $cimsession -Namespace $sNamespace -ErrorAction SilentlyContinue | select -First 1
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}
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]
}


Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oQuery = Get-CimInstance -CimSession $cimsession -Namespace $sNamespace -Query $sQuery -ErrorAction SilentlyContinue
}
catch
{
$oQuery = Get-WMIObject -ComputerName $sTargetComputer -Namespace $sNamespace -Query $sQuery -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}

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)


Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -ComputerName $sTargetComputer -SessionOption $cimSessionOption
$oQuery = Get-CimInstance -CimSession $cimsession -Namespace $sNamespace -Query $sQuery -ErrorAction SilentlyContinue
}
catch
{
$oQuery = Get-WMIObject -ComputerName $sTargetComputer -Namespace $sNamespace -Query $sQuery -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}

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
}

Function Is_Win32_Volume_Supported($TargetComputer)
{
$blnRet = $false
$ErrorActionPreference="SilentlyContinue"
$error.Clear()
$IsNano = Is_NanoServer

if ($true -eq $IsNano)
{
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)
}
}

Function Is_NanoServer
{
$ErrorActionPreference="SilentlyContinue"
$error.Clear()
$IsNano = (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Server\ServerLevels").GetValue("NanoServer")

$IsNano = $IsNano -eq 1

$error.Clear()

return $IsNano

}

#---------------------------------------------------------------------------
# Function: StripEndChar
# Usage:
# Parameter (sName)
# Returns the sName without last special Character "\"
#---------------------------------------------------------------------------
Function StripEndChar([string]$sName)
{
if ([string]::IsNullOrEmpty($sName))
{
return [string]::Empty
}
else
{
return $sName.Substring(0, $sName.Length-1)
}
}

Function Load-Module ([string] $ModuleName)
{
if ([string]::IsNullOrEmpty($ModuleName) )
{
return $false
}

$ErrorActionPreference="SilentlyContinue"
$error.Clear()

$retval = $false
$cim = Get-Module -Name $ModuleName

########Check for powershell 1.0
if ($error.Count -ne 0)
{
$type = $error[0].Exception.GetType()
if ([System.Management.Automation.CommandNotFoundException] -eq $type)
{
$error.Clear()
return $retval
}

$error.Clear()
}

if ($null -eq $cim)
{
Import-Module $ModuleName
if ($error.Count -eq 0)
{
$retval = $true
}

$error.Clear()
}
else
{
$retval = $true
}

return $retval


}

Function Get-Volumes([bool]$IsNano,[bool]$IsVolumeSupported,[string] $TargetComputer)
{
$ErrorActionPreference="SilentlyContinue"
$error.Clear()

$oVolumes = $null

if ($true -eq $IsNano)
{
$bLoadModule = Load-Module -ModuleName "Storage"
if($false -eq $bLoadModule)
{
return $null
}

$oVolumes = Get-Volume | where {$_.DriveType -eq "Fixed" -and $false -eq [string]::IsNullOrEmpty($_.FileSystem )}
}
else
{
if ($true -eq $IsVolumeSupported)
{
$oVolumes = WMIGetInstanceNoAbort $TargetComputer "root\cimv2" "Win32_Volume where (DriveType=3) and FileSystem!=null"
}
else
{
$oVolumes = WMIGetInstanceNoAbort $TargetComputer "root\cimv2" "Win32_LogicalDisk where (DriveType=3) and FileSystem!=null"
}
}

return $oVolumes
}

Function Get-VolumeId([bool]$IsNano,[bool]$IsVolumeSupported,$Volume)
{
$ErrorActionPreference="SilentlyContinue"
$error.Clear()

if ($null -eq $Volume)
{
return $null
}

if ($true -eq $IsVolumeSupported)
{
$VolumeId = $Volume.DriveLetter
if([string]::IsNullOrEmpty($VolumeId) )
{
$VolumeId = StripEndChar -sName $Volume.Name
}
}
else
{
if ($true -eq $IsNano )
{
$VolumeId = $Volume.DriveLetter
if([string]::IsNullOrEmpty($VolumeId) )
{
$VolumeId = StripEndChar -sName $Volume.UniqueId
}
else
{
if (1 -eq $VolumeId.Length)
{
$VolumeId = $VolumeId + ":"
}
}
}
else
{
$VolumeId = $Volume.DeviceId
}

}

return $VolumeId
}

Function CreatePerformanceCounterData($strObjectName, $strCounterName, $strInstanceName, $varValue)
{

return @{ "PerfObject" = $strObjectName;
"PerfCounter" = $strCounterName;
"PerfInstance" = $strInstanceName;
"PerfValue" = $varValue;
}
}

Function Create-PerformancePropertyBag ($strObjectName, $strCounterName, $strInstanceName, $varValue,$objMomScriptAPI)
{
$ErrorActionPreference = "SilentlyContinue"
$error.Clear()

if ([string]::IsNullOrEmpty($varValue))
{
$varValue = 0
}

if ($null -eq $objMomScriptAPI)
{
return
}

$objTypedPropertyBag = $objMomScriptAPI.CreateTypedPropertyBag($PROPERTY_TYPE_PERFORMANCE)

$objTypedPropertyBag.AddValue("PerfObject" ,$strObjectName)
$objTypedPropertyBag.AddValue("PerfCounter" ,$strCounterName)
$objTypedPropertyBag.AddValue("PerfInstance" ,$strInstanceName)
$objTypedPropertyBag.AddValue("PerfValue" ,$varValue)
$objTypedPropertyBag


}

Function Create-EmptyPerfData ($objMomScriptAPI)
{
if ($null -eq $objMomScriptAPI)
{
return

}

$ErrorActionPreference = 'SilentlyContinue' # Scoped only to function
$error.Clear()
$objTypedPropertyBag = $objMomScriptAPI.CreateTypedPropertyBag($PROPERTY_TYPE_PERFORMANCE)

$objTypedPropertyBag.AddValue("PerfObject" ,"EMPTY")
$objTypedPropertyBag.AddValue("PerfCounter" ,"EMPTY")
$objTypedPropertyBag.AddValue("PerfInstance" ,"EMPTY")
$objTypedPropertyBag.AddValue("PerfValue" ,"EMPTY")

#Add the property bag to the script#s XML output
$objTypedPropertyBag

$error.Clear()
}

Function Convert-ToBoolean([string] $sBool)
{
[bool] $result = $false
[bool] $iresult = $false

if ($false -eq [string]::IsNullOrEmpty($sBool) )
{
$result = $sBool.Equals("true",[System.StringComparison]::InvariantCultureIgnoreCase)
$iresult = $sBool.Equals("1",[System.StringComparison]::InvariantCultureIgnoreCase)
$result = $result -or $iresult
}

return $result
}

Function Unload-Module([string]$ModuleName)
{
$ErrorActionPreference = 'SilentlyContinue' # Scoped only to function
$error.Clear()

if ([string]::IsNullOrEmpty($ModuleName))
{
return
}

$module = Get-Module $ModuleName
if ($null -eq $module)
{
return
}

Remove-Module -Name $ModuleName -Force
}

Function Load-CimModules
{
$error.Clear()

$CimModule = Get-Module CimCmdlets

if ($null -eq $CimModule)
{
Import-Module CimCmdlets
$error.Clear()
}
}
#Copyright (c) Microsoft Corporation. All rights reserved.

# Parameters that should be passed to this script
# 0 Computer (FQDN)
# 1 Logical Drive Letter

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

$IsMsftVolumeSupported = Is_MSFT_Volume_Supported_On_NanoServer $TargetComputer
$IsVolumeInfoSupported = Is_Win32_Volume_Supported $TargetComputer

if ($IsMsftVolumeSupported -eq $true) # Win32_Volume is not supported on NanoServer
{
Start-DefragOnNano -TargetComputer $TargetComputer -DiskLabel $DiskLabel
}
else
{
if ($false -eq $IsVolumeInfoSupported)
{
TraceLogMessage ("This Operating System doesn't support volumes WMI class.")
return
}

Start-Defrag -TargetComputer $TargetComputer -DiskLabel $DiskLabel
}

Unload-Module -ModuleName "CimCmdLets"
}



Function Is_MSFT_Volume_Supported_On_NanoServer($TargetComputer)
{
$ErrorActionPreference = "SilentlyContinue"
$error.Clear()
$bRet = $false
$IsNano = Is_NanoServer
if ($true -eq $IsNano)
{
Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -SessionOption $cimSessionOption
$oWMI = Get-CimClass -CimSession $cimsession -NameSpace "root\microsoft\windows\storage" MSFT_Volume -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}
if ($oWMI -ne $null)
{
$bRet = $true
}
}

return $bRet
}

Function Start-DefragOnNano($TargetComputer,$DiskLabel)
{
$ErrorActionPreference = "SilentlyContinue"
$error.Clear()
$IsDiskFound = $false

$oWmiDiskSet = WMIGetInstanceNoAbort $TargetComputer "root\microsoft\windows\storage" "MSFT_Volume WHERE (DriveType=3 or DriveType=6) and FileSystem!=null"
foreach ($oWmiDisk in $oWmiDiskSet)
{
$sDriveLetter = $oWmiDisk.DriveLetter
if ([string]::IsNullOrEmpty($sDriveLetter))
{
$sDriveLetter = $oWmiDisk.UniqueId
}
else
{
$sDriveLetter = $oWmiDisk.DriveLetter + ":" # MSFT_Volume only has "C" instead of "C:" as DriveLetter
}

if ($sDriveLetter -ieq $DiskLabel)
{
TraceLogMessage ("Perform Defragmentation (disk: " + $DiskLabel + "; computer: " + $TargetComputer + ").")
$error.Clear()
Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -SessionOption $cimSessionOption
$ret = Invoke-CimMethod -CimSession $cimsession -CimInstance $oWmiDisk -MethodName Optimize -Arguments @{ReTrim=$false;Analyze=$false;Defrag=$true;SlabConsolidate=$false;TierOptimize=$false} -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}
TraceLogMessage ("Defragmentation completed (disk: " + $DiskLabel + "; computer: " + $TargetComputer + ").")

If ($ret.ReturnValue -ne 0 -or $error.Count -ne 0)
{
ThrowScriptError ("Defrag failed (disk: " + $DiskLabel + "; computer: " + $TargetComputer + "). Return code: " + $ret.ReturnValue + ".") $error[0]
}

$IsDiskFound = $true
break
}

}

if ($false -eq $IsDiskFound)
{
TraceLogMessage ("ERROR: Could not find a disk (disk: " + $DiskLabel + "; computer: " + $TargetComputer + "). In cluster environment this might occur when disk does not belong to current computer anymore.")
}

return

}

Function Start-Defrag($TargetComputer,$DiskLabel)
{
$ErrorActionPreference = "SilentlyContinue"
$error.Clear()
$IsDiskFound = $false

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

foreach ($oWmiDisk in $oWmiDiskSet)
{
$sDriveLetter = $oWmiDisk.DriveLetter
if ([string]::IsNullOrEmpty($sDriveLetter))
{
$sDriveLetter = $oWmiDisk.Name
$sDriveLetter = StripEndChar -sName $sDriveLetter
}

if ($sDriveLetter -ieq $DiskLabel)
{
TraceLogMessage ("Perform Defragmentation (disk: " + $DiskLabel + "; computer: " + $TargetComputer + ").")
$error.Clear()
Load-CimModules
try
{
$cimSessionOption = New-CimSessionOption -Protocol DCOM
$cimsession = New-CimSession -SessionOption $cimSessionOption
$ret = Invoke-CimMethod -CimSession $cimsession -CimInstance $oWmiDisk -MethodName Defrag -Arguments @{Force=$false} -ErrorAction SilentlyContinue
}
Finally
{
Get-CimSession | Remove-CimSession
$cimsession =$null
$cimSessionOption = $null
}
TraceLogMessage ("Defragmentation completed (disk: " + $DiskLabel + "; computer: " + $TargetComputer + "): FilePercentFragmentation = " + $ret.DefragAnalysis.FilePercentFragmentation + ".")

If ($ret.ReturnValue -ne 0 -or $error.Count -ne 0)
{
ThrowScriptError ("Defrag failed (disk: " + $DiskLabel + "; computer: " + $TargetComputer + "). Return code: " + $ret.ReturnValue + ".") $error[0]
}

$IsDiskFound = $true
break
}

}

if ($false -eq $IsDiskFound)
{
TraceLogMessage ("ERROR: Could not find a disk (disk: " + $DiskLabel + "; computer: " + $TargetComputer + "). In cluster environment this might occur when disk does not belong to current computer anymore.")
}

return

}

Function Load-CimModules
{
$error.Clear()

$CimModule = Get-Module CimCmdlets

if ($null -eq $CimModule)
{
Import-Module CimCmdlets
$error.Clear()
}
}
Main
</Script></ScriptBody>
<Parameters>
<Parameter>
<Name>TargetComputer</Name>
<Value>$Target/Host/Property[Type="Windows!Microsoft.Windows.Computer"]/NetworkName$</Value>
</Parameter>
<Parameter>
<Name>DiskLabel</Name>
<Value>$Target/Property[Type="Windows!Microsoft.Windows.LogicalDevice"]/DeviceID$</Value>
</Parameter>
</Parameters>
<TimeoutSeconds>3600</TimeoutSeconds>
</WriteAction>
</Recovery>