In the modern age we live in those of you not working in IT for shall we say, an extended period, will not appreciate the pain of having to watch a disk defragment. The current process of defragmenting the hard disk is a click button and to make your life really easy you can schedule the process.

Many of you would argue that in some circumstances the entire process of defragmenting a hard disk is no longer required, I have had this argument thrown up at me when it comes to virtual servers in particular.

The one thing to remember though is that fragmentation of files does occur whether your disk is virtual or not, SSD or not, and although performance degradation might not be severe, it only serves as good house keeping to keep your disks running optimally.

But I Have A Defrag Scheduled Task – Why Should I Care?

I recently discovered that although my servers had their defrag jobs scheduled and indeed the jobs were completing, when running a command-line defrag job I found that some drives had much more fragmentation of files than I would have expected. This is due to the fact that with changes to the optimisation methods in Server 2012, the default defrag option is to consolidate slabs of storage, which is effectively a light version of the full defragment process.

More information on the changes in defrag for Windows in Server 2012/R2 can be found at the following:  https://blogs.technet.microsoft.com/askcore/2014/02/17/whats-new-in-defrag-for-windows-server-20122012r2/

Using the Optimize-Volume command you can clearly see the difference in the below example between the initial run and the post manual defrag.

Fragmentation Level Discovery:

Fragmentation Post CB Remediation Script;

Using PowerShell with SCCM Configuration Baselines to Optimise Disks

So this got me thinking as to a more dynamic way to both monitor and correct the fragmentation levels on my servers. The answer I came up with was to create a Configuration Item and Baseline in SCCM to monitor for the presence of excessive file fragmentation and run through a remediation process to ensure that the drives are always at their optimal performance.

Microsoft Script Library Link –https://gallery.technet.microsoft.com/Dell-Driver-Automation-Tool-dddf1493

Disk Fragmentation Check Script:

$MaxFragmentation = "15"

# Defragmentation Processes
function DefragCheck ($MaxFragmentation)
{
	try
	{
		# Get list of Pysical Disks for determining health status and drive type
		$Disks = New-Object -TypeName System.Collections.ArrayList
		
		# Determine if the machine is virtual or physical then query disks
		If ((Get-WmiObject -Class Win32_ComputerSystem | Select-Object -Property Model) -like "*Virtual*")
		{
			$VirtualDisks = (Get-Partition | Get-Disk | Get-PhysicalDisk).DeviceID | Select-Object -Unique | Sort-Object
			
			# Add disk to disk array if not present
			if ($VirtualDisks -ne $null)
			{
				foreach ($Disk in $VirtualDisks)
				{
					if ($Disk -notin $Disks)
					{
						$Disks.Add($Disk) | Out-Null
					}
				}
			}
		}
		else
		{
			$DiskSerialNumbers = Get-Partition | Get-Disk | Select-Object SerialNumber -Unique
			$PhyiscalDisks = foreach ($Serial in $DiskSerialNumbers.SerialNumber) { Get-PhysicalDisk | Where-Object { $_.SerialNumber -eq $Serial.Trim() } | Select-Object FriendlyName, DeviceID | Sort-Object }
			
			# Add disk to disk array if not present
			if ($PhyiscalDisks -ne $null)
			{
				foreach ($Disk in $PhyiscalDisks)
				{
					if ($Disk.DeviceID -notin $Disks)
					{
						$Disks.Add($Disk.DeviceID) | Out-Null
					}
				}
			}
		}
		# Add disk to disk array if not present
		if ($PhyiscalDisks -ne $null)
		{
			foreach ($Disk in $PhyiscalDisks)
			{
				if ($Disk.DeviceID -notin $Disks)
				{
					$Disks.Add($Disk.DeviceID) | Out-Null
				}
			}
		}
		# Start Defrag Process for each disk
		foreach ($Disk in $Disks)
		{
			$CurrentDisk = Get-PhysicalDisk | Where-Object { $_.DeviceID -eq $Disk }
			If ($CurrentDisk.HealthStatus -eq "Healthy")
			{
				$Partitions = Get-Disk -DeviceId $CurrentDisk.DeviceID | Get-Partition | Where-Object { $_.NoDefaultDriveLetter -ne $true } | Get-Volume | Where-Object { $_.DriveType -eq "Fixed" }
				Write-Host "Working on $CurrentDisk with partitions $Paritions"
				If ($Partitions -ne $null)
				{
					# Create array for multiple disk drive fragmentation levels
					$VolumeFragmentLevels = New-Object -TypeName System.Collections.ArrayList
					foreach ($Partition in $Partitions)
					{
						# Check Volume Fragmentation Level
						$DriveLetter = ($Partition.DriveLetter + ":")
						$DriveLetter = [string]$DriveLetter.Trim()
						if ($DriveLetter -ne ":")
						{
							$PartitionToDefrag = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = '$DriveLetter'"
							$PartitionReport = $PartitionToDefrag.DefragAnalysis()
							$FragmentationLevel = $PartitionReport.DefragAnalysis.FilePercentFragmentation
							if ($FragmentationLevel -gt $MaxFragmentation)
							{
								$VolumeFragmentLevels.Add($FragmentationLevel) | Out-Null
							}
						}
					}
				}
			}
		}
		# Output the drive with the highest fragmentation level
		$CurrentFragmentation = $VolumeFragmentLevels | Sort-Object -Descending | Select-Object -First 1
		
		# Prepare output for SCCM CI. If the highest level of fragmentation is lower
		# than the value specified in the $MaxFragmentation variable, the drive
		# fragmentation level will be considered to be "low".
		
		if ($CurrentFragmentation -gt $MaxFragmentation)
		{
			$Fragmentation = "High"
		}
		else
		{
			$Fragmentation = "Low"
		}
		Return $Fragmentation
	}
	catch { }
}

DefragCheck ($MaxFragmentation)

 

The script checks the levels of fragmentation across all fixed disks and reports back to SCCM with a “Low” or “High” value depending on whether the threshold specified in the $MaxFragmentation variable, in the above example I have this set at 15%.

Note that the fragmentation level returned is based on a high watermark level and not on each individual disk, the remediation action will later on determine which disk is to be defragmented.

Remediation Process

The next process is to optimise the fragmentation levels on the disk, which appears straight forward but you need to consider a couple of things such as the type of hard disk being used and the level of free space available to the volume you are defragmenting. In terms of hard disk type, SSD’s need special consideration as the defragment process can be I/O intensive and will reduce the life expectancy of your drive, so you need to cater for this. Separately you also need to ensure that there is sufficient free space for perform the job.

The below script puts these elements together and determines the following;

  • Determine if the drives are physical or virtual
  • Determine which physical disks are in use, their type and health status
  • Which volume is located on which physical disk
  • The amount of free space on each volume

These checks ensure that the correct drive type optimisation is applied and jobs are only run on volumes with sufficient free space and of course only on disks which are flagged as being in a “Healthy” state.

Remediation Script:

$MaxFragmentation = "15"
$DriveDefragJobs = 0

# Defragmentation Processes
function DefragDisk ($MaxFragmentation)
{
	try
	{
		# Get list of Pysical Disks for determining health status and drive type
		$Disks = New-Object -TypeName System.Collections.ArrayList
		
		# Determine if the machine is virtual or physical then query disks
		If ((Get-WmiObject -Class Win32_ComputerSystem | Select-Object -Property Model) -like "*Virtual*")
		{
			$VirtualDisks = (Get-Partition | Get-Disk | Get-PhysicalDisk).DeviceID | Select-Object -Unique | Sort-Object
			
			# Add disk to disk array if not present
			if ($VirtualDisks -ne $null)
			{
				foreach ($Disk in $VirtualDisks)
				{
					if ($Disk -notin $Disks)
					{
						$Disks.Add($Disk) | Out-Null
					}
				}
			}
		}
		else
		{
			$DiskSerialNumbers = Get-Partition | Get-Disk | Select-Object SerialNumber -Unique
			$PhyiscalDisks = foreach ($Serial in $DiskSerialNumbers.SerialNumber) { Get-PhysicalDisk | Where-Object { $_.SerialNumber -eq $Serial.Trim() } | Select-Object FriendlyName, DeviceID | Sort-Object }
			
			# Add disk to disk array if not present
			if ($PhyiscalDisks -ne $null)
			{
				foreach ($Disk in $PhyiscalDisks)
				{
					if ($Disk.DeviceID -notin $Disks)
					{
						$Disks.Add($Disk.DeviceID) | Out-Null
					}
				}
			}
		}
		# Add disk to disk array if not present
		if ($PhyiscalDisks -ne $null)
		{
			foreach ($Disk in $PhyiscalDisks)
			{
				if ($Disk.DeviceID -notin $Disks)
				{
					$Disks.Add($Disk.DeviceID) | Out-Null
				}
			}
		}
		# Start Defrag Process for each disk
		foreach ($Disk in $Disks)
		{
			$CurrentDisk = Get-PhysicalDisk | Where-Object { $_.DeviceID -eq $Disk }
			If ($CurrentDisk.HealthStatus -eq "Healthy")
			{
				$Partitions = Get-Disk -DeviceId $CurrentDisk.DeviceID | Get-Partition | Where-Object { $_.NoDefaultDriveLetter -ne $true } | Get-Volume | Where-Object { $_.DriveType -eq "Fixed" }
				If ($Partitions -ne $null)
				{
					foreach ($Partition in $Partitions)
					{
						# Check Volume Fragmentation Level
						$DriveLetter = ($Partition.DriveLetter + ":")
						$DriveLetter = [string]$DriveLetter.Trim()
						if ($DriveLetter -ne ":")
						{
							$PartitionToDefrag = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = '$DriveLetter'"
							$PartitionReport = $PartitionToDefrag.DefragAnalysis()
							$FragmentationLevel = $PartitionReport.DefragAnalysis.FilePercentFragmentation
							$FreeSpacePercentage = ($PartitionToDefrag.FreeSpace)/(($PartitionToDefrag.Capacity)/100)
							if ($FragmentationLevel -gt $MaxFragmentation)
							{
								$DriveDefragJobs++
								If ($FreeSpacePercentage -gt "10")
								{
									If ($CurrentDisk.MediaType -eq "SSD")
									{
										Optimize-Volume -DriveLetter $Partition.DriveLetter -ReTrim -AsJob
									}
									else
									{
										Optimize-Volume -DriveLetter $Partition.DriveLetter -Defrag -AsJob
									}
								}
								else
								{
									$Output = "Insufficient Free Space To Defragment Disk"
									Break
								}
							}
						}
					}
				}
			}
			else
			{
				$Output = "Disk Health Status Issue Detected"
				Break
			}
		}
		if ($DriveDefragJobs -gt 0)
		{
			$Output = "Defragmentation Jobs Initialised For $DriveDefragJobs Partitions"
		}
		else
		{
			$Output = "Drive Fragmentation Levels Are Healthy. No Defragment Jobs Required"
		}
		Return $Output
	}	
	catch { }
}

DefragDisk ($MaxFragmentation)

Monitoring

After deploying the scripts simply run a report on the deployed CB and you should see a full list of compliant machines. Clicking on items within the compliant tab should generate results similar to the screen below;

Note: By default compliance scripts will time out after 60 seconds, and although there is a method to change this I would suggest leaving the time out value as the default. The remediation job will continue to process in the background as it is run as background job, so your servers will become compliant once they refresh their state on the next CB run.

Step By Step CI & CB Instructions:

1. Create a new Configuration Item

2. Select The Following Operating Systems

3. Add Settings (Detection & Remediation PowerShell Scripts)

4. Add Compliance Rule

5. Finish

Deploying Your Configuration Baseline

When deploying your CB make sure you take factors such as are you running Shadow Copies on specific servers or Hyper-V servers from local disks. If so you might want to exclude these servers from the device collection you apply this to as to ensure your previous versions remain intact etc.

Once you have your device collection membership established and maintenance window established, you can apply the CB and come back in the morning to servers with more optimal hard disks.

Manual Script

Below is a manual script for those of you not using SCCM and who wish to deploy this via an alternative method;

<#	
	.NOTES
	===========================================================================
	 Created on:   	04/01/2017 12:00
	 Created by:   	Maurice.Daly
	 Organization: 	DeployEverything.com
	 Filename:     	DefragmentDisks.PS1
	===========================================================================
	.DESCRIPTION
			This script detects the fragmentation levels of all fixes 
			disks, then runs optimisation jobs based on drives over the 
			threshold set in the "$MaxFragmentation" variable
#>

$MaxFragmentation = "15"

# Get list of Pysical Disks for determining health status and drive type
$Disks = New-Object -TypeName System.Collections.ArrayList

# Determine if the machine is virtual or physical then query disks
If ((Get-WmiObject -Class Win32_ComputerSystem | Select-Object -Property Model) -like "*Virtual*")
{
	$VirtualDisks = (Get-Partition | Get-Disk | Get-PhysicalDisk).DeviceID | Select-Object -Unique | Sort-Object
	
	# Add disk to disk array if not present
	if ($VirtualDisks -ne $null)
	{
		foreach ($Disk in $VirtualDisks)
		{
			if ($Disk -notin $Disks)
			{
				$Disks.Add($Disk) | Out-Null
			}
		}
	}
}
else
{
	$DiskSerialNumbers = Get-Partition | Get-Disk | Select-Object SerialNumber -Unique
	$PhyiscalDisks = foreach ($Serial in $DiskSerialNumbers.SerialNumber) { Get-PhysicalDisk | Where-Object { $_.SerialNumber -eq $Serial.Trim() } | Select-Object FriendlyName, DeviceID | Sort-Object }
	
	# Add disk to disk array if not present
	if ($PhyiscalDisks -ne $null)
	{
		foreach ($Disk in $PhyiscalDisks)
		{
			if ($Disk.DeviceID -notin $Disks)
			{
				$Disks.Add($Disk.DeviceID) | Out-Null
			}
		}
	}
}

# Start Defrag Process for each disk
foreach ($Disk in $Disks)
{
	Write-Host "Working on Disk $Disk"
	$CurrentDisk = Get-PhysicalDisk | Where-Object { $_.DeviceID -eq $Disk }
	If ($CurrentDisk.HealthStatus -eq "Healthy")
	{
		Write-Host "Current disk number is $Disk"
		$Partitions = Get-Disk -DeviceId $CurrentDisk.DeviceID | Get-Partition | Where-Object { $_.NoDefaultDriveLetter -ne $true } | Get-Volume | Where-Object { $_.DriveType -eq "Fixed" }
		If ($Partitions -ne $null)
		{
			Write-Host "Working on Partitions $($Partitions.DriveLetter)"
			foreach ($Partition in $Partitions)
			{
				# Check Volume Fragmentation Level
				$DriveLetter = ($Partition.DriveLetter + ":")
				$DriveLetter = [string]$DriveLetter.Trim()
				if ($DriveLetter -ne ":")
				{
					$PartitionToDefrag = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter = '$DriveLetter'"
					$PartitionReport = $PartitionToDefrag.DefragAnalysis()
					$FragmentationLevel = $PartitionReport.DefragAnalysis.FilePercentFragmentation
					Write-Host "Fragmentation on drive $DriveLetter is currently $FragmentationLevel percent. The disk type is $($CurrentDisk.MediaType) and is currently in a $($CurrentDisk.HealthStatus) state."
					$FreeSpacePercentage = ($PartitionToDefrag.FreeSpace)/(($PartitionToDefrag.Capacity)/100)
					Write-Host "Current free space on drive $DriveLetter stands at $FreeSpacePercentage"
					if ($FragmentationLevel -gt $MaxFragmentation)
					{
						If ($FreeSpacePercentage -gt "10")
						{
							If ($CurrentDisk.MediaType -eq "SSD")
							{
								Write-Host "Optimising SSD Drive Partitions On Disk Number $($CurrentDisk.Number)"
								Optimize-Volume -DriveLetter $Partition.DriveLetter -ReTrim -Verbose
							}
							else
							{
								Write-Host "Optimising HDD Drive Partitions On Disk Number $($CurrentDisk.Number)"
								Optimize-Volume -DriveLetter $Partition.DriveLetter -Defrag -Verbose
							}
						}
						else
						{
							Write-Warning = "Insufficient Free Space To Defragment Disk"
							Break
						}
					}
					else
					{
						Write-Host -ForegroundColor Green "Current Partition $($Partition.DriveLetter) Does Not Require Defragmentation"
					}
					
				}
			}
		}
	}
	else
	{
		Write-Error "Drive Status Returned As Unhealthy"
	}
}

 

Maurice Daly
Maurice has been working in the IT industry since 1999 and was awarded his first MVP Enterprise Mobility award in 2017. Technology focus includes Active Directory, Group Policy, Hyper-V, Windows Deployment (SCCM & MDT) and Office 365.

(1939)

There are no comments.

Leave a Reply