2010-12-22

Disk Info

I have a daily interest to know how the disks are used, and this script give some useful data from the Windows point of view.

function Get-Disk {
[CmdletBinding()]
param(
  [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
  [String]$ComputerName
)
Begin {
  "Get-Disk( '$ComputerName' )" | Write-Verbose
  $Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
}

Process {
  $CimSessionOption = New-CimSessionOption -Protocol Dcom
  $CimSession = New-CimSession -SessionOption $CimSessionOption -Verbose:$false -ComputerName $ComputerName
  $SqlDisks = @()
  'Get Local Disks...' | Write-Verbose
  $Disks = Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3' -CimSession $CimSession -Verbose:$false
  foreach ($Disk in $Disks) {
    "Disk.DeviceID = '$($Disk.DeviceID)'." | Write-Verbose
    $SqlDisk = New-Object -TypeName PSObject
    $SqlDisk.PSObject.TypeNames.Insert(0, 'SqlAdmin.Disk')
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveLetter -Value $Disk.DeviceID
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DiskFreeSpaceBytes -Value $Disk.FreeSpace
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DiskSizeBytes -Value $Disk.Size
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name FileSystem -Value $Disk.FileSystem
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeDirty -Value $Disk.VolumeDirty
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeName -Value $Disk.VolumeName
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeSerialNumber -Value $Disk.VolumeSerialNumber

    $Partition = Get-CimAssociatedInstance -CimInstance $Disk -ResultClassName Win32_DiskPartition -Verbose:$false
    "Partition.DeviceID = '$($Partition.DeviceID)'." | Write-Verbose
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionDeviceID -Value $Partition.DeviceID
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name BlockSizeBytes -Value $Partition.BlockSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name BlockCount -Value $Partition.NumberOfBlocks
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionDiskIndex -Value $Partition.DiskIndex
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionSizeBytes -Value $Partition.Size
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name StartingOffset -Value $Partition.StartingOffset
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name PartitionType -Value $Partition.Type

    $Drive = Get-CimAssociatedInstance -CimInstance $Partition -ResultClassName Win32_DiskDrive -Verbose:$false
    "Drive.DeviceID = '$($Drive.DeviceID)'." | Write-Verbose
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DrivePartitionCount -Value $Drive.Partitions
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveBytesPerSector -Value $Drive.BytesPerSector
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveDeviceID -Value $Drive.DeviceID
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name InterfaceType -Value $Drive.InterfaceType
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveFirmwareRevision -Value $Drive.FirmwareRevision
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveModel -Value $Drive.Model
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveSizeBytes -Value $Drive.Size
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveStatus -Value $Drive.Status
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name DriveSerialNumber -Value $Drive.SerialNumber

    $Volume = $Volumes = Get-CimInstance -ClassName Win32_Volume -Filter "DriveLetter='$($Disk.Name)'" -CimSession $CimSession -Verbose:$false
    "Volume.Label = '$($Volume.Label)'." | Write-Verbose
    'Running Defrag Analysis...' | Write-Verbose
    $DefragStopwatch = [System.Diagnostics.Stopwatch]::StartNew()
    $Report = Invoke-CimMethod -CimSession $CimSession -InputObject $Volume -MethodName DefragAnalysis -Verbose:$false
    $DefragStopwatch.Stop()
    "Defrag Analysis completed. Duration = $($DefragStopwatch.Elapsed.ToString()) [hh:mm:ss.ddd]." | Write-Verbose
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name AverageFileSize -Value $Report.DefragAnalysis.AverageFileSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name AverageFragmentsPerFile -Value $Report.DefragAnalysis.AverageFragmentsPerFile
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name AverageFreeSpacePerExtent -Value     $Report.DefragAnalysis.AverageFreeSpacePerExtent
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name ClusterSize -Value $Report.DefragAnalysis.ClusterSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeFreeSpace -Value $Report.DefragAnalysis.FreeSpace
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name FreeSpacePercentFragmentation -Value     $Report.DefragAnalysis.FreeSpacePercentFragmentation
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name LargestFreeSpaceExtent -Value $Report.DefragAnalysis.LargestFreeSpaceExtent
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name MFTPercentInUse -Value $Report.DefragAnalysis.MFTPercentInUse
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name MFTRecordCount -Value $Report.DefragAnalysis.MFTRecordCount
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalExcessFragments -Value $Report.DefragAnalysis.TotalExcessFragments
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFiles -Value $Report.DefragAnalysis.TotalFiles
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFolders -Value $Report.DefragAnalysis.TotalFolders
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFragmentedFiles -Value $Report.DefragAnalysis.TotalFragmentedFiles
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalFreeSpaceExtents -Value $Report.DefragAnalysis.TotalFreeSpaceExtents
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalMFTFragments -Value $Report.DefragAnalysis.TotalMFTFragments
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalMFTSize -Value $Report.DefragAnalysis.TotalMFTSize
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name TotalUnmovableFiles -Value $Report.DefragAnalysis.TotalUnmovableFiles
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name UsedSpace -Value $Report.DefragAnalysis.UsedSpace
    Add-Member -InputObject $SqlDisk -MemberType NoteProperty -Name VolumeSize -Value $Report.DefragAnalysis.VolumeSize

    $SqlDisks += $SqlDisk
  }
}

End {
  $Stopwatch.Stop()
  "$($SqlDisks.Count) disks found on the computer. Duration = $($Stopwatch.Elapsed.ToString()) [hh:mm:ss.ddd]." | Write-Verbose
  $SqlDisks
}

} # Get-Disk()

### INVOKE ###
$MyDisks = Get-Disk -ComputerName '.' -Verbose
$MyDisks | Out-GridView


I use the DCOM protocol due to some restrictions in some network segments.

The usage of the CmdLet Get-CimAssociatedInstance makes associating the classes mush easier compared to associating the classes in WMI.
The execution of the method DefragAnalysis is different used from CIM than WMI. The use of the CmdLet Invoke-CimMethod took some time to get, but I find it much cleaner to use.

When the script is executed, the output could look like this:

DriveLetter                   : C:
DiskFreeSpaceBytes            : 488987631616
DiskSizeBytes                 : 750153363456
FileSystem                    : NTFS
VolumeDirty                   : False
VolumeName                    : ********
VolumeSerialNumber            : ********
PartitionDeviceID             : Disk #0, Partition #0
BlockSizeBytes                : 512
BlockCount                    : 1465143296
PartitionDiskIndex            : 0
PartitionSizeBytes            : 750153367552
StartingOffset                : 1048576
PartitionType                 : Installable File System
DrivePartitionCount           : 1
DriveBytesPerSector           : 512
DriveDeviceID                 : \\.\PHYSICALDRIVE0
InterfaceType                 : IDE
DriveFirmwareRevision         : EXT0
DriveModel                    : Samsung SSD 840 EVO 750G
DriveSizeBytes                : 750153761280
DriveStatus                   : OK
DriveSerialNumber             : ********    
AverageFileSize               : 142
AverageFragmentsPerFile       : 1,15
AverageFreeSpacePerExtent     : 6483968
ClusterSize                   : 4096
VolumeFreeSpace               : 489014767616
FreeSpacePercentFragmentation : 31
LargestFreeSpaceExtent        : 334636171264
MFTPercentInUse               : 100
MFTRecordCount                : 473855
TotalExcessFragments          : 54011
TotalFiles                    : 339026
TotalFolders                  : 76795
TotalFragmentedFiles          : 12689
TotalFreeSpaceExtents         : 75369
TotalMFTFragments             : 2
TotalMFTSize                  : 485228544
TotalUnmovableFiles           : 147
UsedSpace                     : 261138595840
VolumeSize                    : 750153363456
The verbose output shows some durations on the execution:
VERBOSE: Get-Disk( '.' )
VERBOSE: Get Local Disks...
...
VERBOSE: Disk.DeviceID = 'C:'.
VERBOSE: Partition.DeviceID = 'Disk #0, Partition #0'.
VERBOSE: Drive.DeviceID = '\\.\PHYSICALDRIVE0'.
VERBOSE: Volume.Label = '********'.
VERBOSE: Running Defrag Analysis...
VERBOSE: Defrag Analysis completed. Duration = 00:00:56.9287395 [hh:mm:ss.ddd].
VERBOSE: 2 disks found on the computer. Duration = 00:00:59.5802508 [hh:mm:ss.ddd].


The script is not optimized for performance and do have a response time on some seconds. The response time increased especially when I added the defragmentation data.

Please notice that the classes Win32_Volume and Win32_DefragAnalysis are not available on Windows XP or earlier.

Reference

MSDN Library: Win32_DiskDrive, Win32_DiskPartition, Win32_LogicalDisk, Win32_Volume and „WMI Tasks: Disks and File Systems“.
Richard Saddaway: "Defrag Analysis Part 2".


History

2010-12-22 First release of the script.
2015-06-25 Second release of the script, now using CIM CmsLets.