Using VBScript to Check Partitions for Advanced Format (4K / 512e) Sector Alignment
April 26, 2011 14 Comments
As you may know Dell recently announced that they will start shipping Advanced Format (4k/512e) drives in their enterprise systems in May 2011. For those not wanting to follow the links, the cliff’s notes version is that Advanced Format drives use a 4096 byte (4k) sector rather than a 512 byte sector. This allows higher bit densities by reducing the amount of space tied up in ECC and sector gap.
So what’s the big deal then? Well in order to make the transition, 4K/512e advanced format drives still present themselves externally as a 512 byte per sector drive, and internally lump reads and writes into eight logical 512 byte sectors to match up with their physical 4096 byte sectors. This has the unfortunate side effect that if your partitions are not aligned to a 4K boundary the disk may have to perform a Read-Modify-Write across two physical sectors. This process can incur extra latency waiting for an additional rotation of the disk platters which drastically impacts performance. Further compounding the matter Windows XP/2003 operating systems and most disk management tools create the first partition on the disk starting at LBA sector 63 which if you do your math correctly is not 4K aligned.
Recently the question came up as to whether or not it was possible to use a script to detect partitions that were not 4K aligned and therefore may be causing performance issues, or to perhaps use in an OSD task sequence to halt the OS install if the partition is not correctly aligned. After some browsing through the WMI classes I found what I was looking for. The Win32_DiskPartion class has a property StartingOffset that, according to MSDN is in fact the “starting offset (in bytes) of the partition”. A little modulus division later and you have a simple way to determine if your partition is 4K sector aligned.
Option Explicit Dim strComputer: strComputer = "." Dim objWMIService: Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Dim colPartitions: Set colPartitions = objWMIService.ExecQuery("Select * from Win32_DiskPartition",,48) Dim objPartition For Each objPartition In colPartitions Dim iResult: iResult = objPartition.StartingOffset / 4096 If iResult = Fix(iResult) Then WScript.Echo objPartition.Caption & " is 4K/512e sector aligned." Else WScript.Echo objPartition.Caption & " is NOT 4K/512e sector aligned." End If Next
There is one caveat I should mention if you’re planning to use this in an OSD task sequence from WinPE. It seems that the Win32_DiskPartition class has been removed from WinPE 3.x. Luckily others already have a solution for you.
UPDATE: 5/26/11
Per the request by Eric in the comments I’ve updated the script to show the drive letter associated with the partition.
Option Explicit Dim strComputer: strComputer = "." Dim objWMIService: Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Dim colPartitions: Set colPartitions = objWMIService.ExecQuery("Select * from Win32_DiskPartition",,48) Dim objPartition For Each objPartition In colPartitions Dim strDriveLetter: strDriveLetter = PartitionToDrive(objWMIService, objPartition.DeviceID) Dim iResult: iResult = objPartition.StartingOffset / 4096 If iResult = Fix(iResult) Then WScript.Echo "(" & strDriveLetter & ") " & objPartition.Caption & " is 4K/512e sector aligned." Else WScript.Echo "(" & strDriveLetter & ") " & objPartition.Caption & " is NOT 4K/512e sector aligned." End If Next Private Function PartitionToDrive(objWMIService, DeviceID) Dim objLogcialDisks: Set objLogcialDisks = objWMIService.ExecQuery( _ "ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & DeviceID & _ "'} WHERE AssocClass = Win32_LogicalDiskToPartition") Dim objLogicalDisk For Each objLogicalDisk In objLogcialDisks PartitionToDrive = objLogicalDisk.DeviceID Exit Function Next PartitionToDrive = "" End Function
Thanks for this script.
It does work, but ends with an Overflow error.
Could you please:
1. Fix the Overflow error.
2. Add partition letter (C, D, E,…)
3. Add volume name (for example – C: System, D: Data)
I noticed that Win32_DiskPartition does not contain parameters for drive letter and volume name.
However, Win32_LogicalDisk does contain those parameters (string Name; string VolumeName).
Eric,
I’m not seeing an overflow when I run it in my environment, can you provide the exact error you’re seeing?
Also, the reason for not using Win32_LogicalDisk is that the Win32_LogicalDisk class does not provide information on the starting offset of the partition itself. However it is possible to query WMI for the association between a Win32_DiskPartition and a Win32_LogicalDisk. I’ve provided an update to this post as an example.
On WinServer 2008 R2 Enterprise, I get:
Error: Overflow: '[string: "5247934720"]'
Code: 800A0006
Source: Microsoft VBScript runtime error
On WinXP SP3 eng x86, I get:
Error: Overflow: '[string: "55899586560"]'
Code: 800A0006
Source: Microsoft VBScript runtime error
In both cases, it points to the line with the IF statement.
Ok, I see the issue here. Win32_DiskPartition.StartingOffset is an Int64 and the VBScript Mod operator is only good up to 32bits. I’m guessing you have more than one partition to a disk and that at least one of those partitions starts more than 2^32 bytes into the disk. I wasn’t seeing this because I generally have one partition to a disk which means that the StartingOffset for all of my partitions is generally near the beginning of the disk and hence well below the 32bit limit for the Mod operator. The fix is fairly simple in that the script doesn’t really need the result of the Mod operator, it just needs to know that the result of StartingOffset / 4096 was not fractional. This can be accomplished by changing objPartition.StartingOffset Mod 4096 = 0 to Fix(objPartition.StartingOffset / 4096) = (objPartition.StartingOffset / 4096), which I’ve done above.
Thanks, Josh.
The fix worked.
And you were right – I do have some disks with more than 1 partition.
I even have 2 drives in RAID0, which are then split into 2 partitions (don’t ask why).
Anyway, the script works great right now.
Could you please also add “volume name”?
You can add a separate line for “volume name” by adding
& vbCr & _
.Eric
One thing you’ll find is I’m very much a “teach a man to fish” rather than “give a man a fish” kind of guy. To that end, I’ll give you some hints and leave it as an exercise for you.
Basically you’ve got a couple options, but IMHO the easiest and most “code readable” way would be use the PartitionToDrive function as a template and create a new function (let’s arbitrarily call it PartitionToVolumeLabel). Then you’ll need to change what property of the Win32_LogicalDisk instance (contained in objLogicalDisk in the function) is returned from the new function from “DeviceID” to whatever property contains the Logical Volume name. The documentation for the Win32_LogicalDisk class is here http://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx, but I bet you could guess what the property name is for volume name if you had to 😉
Then you just need to modify the main part of the script to take advantage of your new function. Again hints would be to duplicate (and modify) line 10, and modify lines 13 and 15.
If you give it a go and get stuck let me know what you’ve come up with and I’ll be happy to help.
I came up with this, but I’m doing something wrong:
Option Explicit
Dim strComputer: strComputer = "."
Dim objWMIService: Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Dim colPartitions: Set colPartitions = objWMIService.ExecQuery("Select * from Win32_DiskPartition",,48)
Dim objPartition
For Each objPartition In colPartitions
Dim strDriveLetter: strDriveLetter = PartitionToDrive(objWMIService, objPartition.DeviceID)
Dim strVolumeName: strVolumeName = PartitionToVolumeLabel(objWMIService, objPartition.VolumeName)
Dim iResult: iResult = objPartition.StartingOffset / 4096
If iResult = Fix(iResult) Then
WScript.Echo "(" & strDriveLetter & ") " & objPartition.Caption & " is 4K sector aligned." & vbCr & _
"Volume Name:" & strVolumeName
Else
WScript.Echo "(" & strDriveLetter & ") " & objPartition.Caption & " is NOT 4K sector aligned." & vbCr & _
"Volume Name:" & strVolumeName
End If
Next
Private Function PartitionToDrive(objWMIService, DeviceID)
Dim objLogcialDisks: Set objLogcialDisks = objWMIService.ExecQuery( _
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & DeviceID & _
"'} WHERE AssocClass = Win32_LogicalDiskToPartition")
Dim objLogicalDisk
For Each objLogicalDisk In objLogcialDisks
PartitionToDrive = objLogicalDisk.DeviceID
Exit Function
Next
PartitionToDrive = ""
End Function
Private Function PartitionToVolumeLabel(objWMIService, VolumeName)
Dim objVolumeNames: Set objVolumeNames = objWMIService.ExecQuery( _
"ASSOCIATORS OF {Win32_LogicalDisk.VolumeName='" & VolumeName & _
"'} WHERE AssocClass = Win32_LogicalDiskToPartition")
Dim objVolumeName
For Each objVolumeName In objVolumeNames
PartitionToVolumeLabel = objVolumeName.VolumeName
Exit Function
Next
PartitionToVolumeLabel = ""
End Function
“A” for effort, however I think you got a little overzealous with the modifications to the new function. The first part would still be like this:
Private Function PartitionToVolumeLabel(objWMIService, DeviceID)
Dim objLogcialDisks: Set objLogcialDisks = objWMIService.ExecQuery( _
“ASSOCIATORS OF {Win32_DiskPartition.DeviceID='” & DeviceID & _
“‘} WHERE AssocClass = Win32_LogicalDiskToPartition”)
Dim objLogicalDisk
For Each objLogicalDisk In objLogcialDisks
…
because you really still want to map your Win32_DiskPartiton object to it’s associated Win32_LogicalDisk (you can read up on the ASSOCIATORS OF statement here http://msdn.microsoft.com/en-us/library/aa384793(v=vs.85).aspx). Also, notice that we’re still passing the function the DeviceID of objPartition because objPartion references an instance of Win32_DiskPartion and therefore doesn’t contain a property VolumeName (that’s what we’re doing with the WMI query in the function; mapping the partition to the associated logical volume so we can get the volume name). Which makes this part of the main script:
Dim strVolumeName: strVolumeName = PartitionToVolumeLabel(objWMIService, objPartition.VolumeName)
into this:
Dim strVolumeName: strVolumeName = PartitionToVolumeLabel(objWMIService, objPartition.DeviceID)
The given changes (and a few variable name tweaks to the latter part of the function if you just copy and paste from this comment) should get you there.
OK, Josh.
Thanks for your instructions. I got it working now.
Josh,
One more thing: Is it possible to add to this script a check whether the HDD(s) are 4K Advanced Format or not?
This would save us time and effort – taking out the HDD from the PC/laptop and inspecting its label.
Unfortunately there is no easy way to do this in VBScript. In fact there is really no easy way to do this at all, and to my knowledge no way whatsoever unless you’re running under Windows 7 SP1 or Windows Server 2008 R2, and in either environment have KB982018 installed. Assuming you were in one of these environments you’ve got two options. Option one, forego VBScript, move over to C and go digging around in the DeviceIOControl API, issue a IOCTL_STORAGE_QUERY_PROPERTY query and examine the BytesPerPhysicalSector field in the returned STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR structure, all of which is WAY beyond the scope of this blog. Option two would be to have FSUTIL.exe do the dirty work for you by running “fsutil fsinfo ntfsinfo [driveletter]” capture the output and parse it for the line “Bytes Per Physical Sector :”. Even then, as noted in the KB article, you may still not get your answer because both the drive and the disk controller driver have to support the new request in the underlying IOCTL_STORAGE_QUERY_PROPERTY call and if they don’t you will get back “Not Supported”.
KB982018 is here: http://support.microsoft.com/kb/982018
and some more general information from Microsoft on 512e can be found here: http://msdn.microsoft.com/en-us/library/hh182553(v=vs.85).aspx
And here I thought that adding this function might do the trick:
Option Explicit
Dim strComputer: strComputer = "."
Dim objWMIService: Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Dim colPartitions: Set colPartitions = objWMIService.ExecQuery("Select * from Win32_DiskPartition",,48)
Dim objPartition
For Each objPartition In colPartitions
Dim strDriveLetter: strDriveLetter = PartitionToDrive(objWMIService, objPartition.DeviceID)
Dim strVolumeName: strVolumeName = PartitionToVolumeLabel(objWMIService, objPartition.DeviceID)
Dim strSectorBytes: strSectorBytes = PartitionToSectorBytes(objWMIService, objPartition.DeviceID)
Dim iResult: iResult = objPartition.StartingOffset / 4096
If iResult = Fix(iResult) Then
WScript.Echo objPartition.Caption & " is 4K sector aligned." & vbCr & _
"Partition Name: " & strVolumeName & " " & strDriveLetter & vbCr & _
"Partition Size: " & Int(objPartition.Size /1073741824) & " GB" & vbCr & _
"Bytes / Sector: " & strSectorBytes & " bytes"
Else
WScript.Echo objPartition.Caption & " is NOT 4K sector aligned." & vbCr & _
"Partition Name: " & strVolumeName & " " & strDriveLetter & vbCr & _
"Partition Size: " & Int(objPartition.Size /1073741824) & " GB" & vbCr & _
"Bytes / Sector: " & strSectorBytes & " bytes"
End If
Next
Private Function PartitionToDrive(objWMIService, DeviceID)
Dim objLogcialDisks: Set objLogcialDisks = objWMIService.ExecQuery( _
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & DeviceID & _
"'} WHERE AssocClass = Win32_LogicalDiskToPartition")
Dim objLogicalDisk
For Each objLogicalDisk In objLogcialDisks
PartitionToDrive = objLogicalDisk.DeviceID
Exit Function
Next
PartitionToDrive = ""
End Function
Private Function PartitionToVolumeLabel(objWMIService, DeviceID)
Dim objVolumeNames: Set objVolumeNames = objWMIService.ExecQuery( _
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & DeviceID & _
"'} WHERE AssocClass = Win32_LogicalDiskToPartition")
Dim objVolumeName
For Each objVolumeName In objVolumeNames
PartitionToVolumeLabel = objVolumeName.VolumeName
Exit Function
Next
PartitionToVolumeLabel = ""
End Function
Private Function PartitionToSectorBytes(objWMIService, DeviceID)
Dim objSectorBytes: Set objSectorBytes = objWMIService.ExecQuery( _
"ASSOCIATORS OF {Win32_DiskPartition.DeviceID='" & DeviceID & _
"'} WHERE AssocClass = Win32_DiskDriveToDiskPartition")
Dim objSectorByte
For Each objSectorByte In objSectorBytes
PartitionToSectorBytes = objSectorByte.BytesPerSector
Exit Function
Next
PartitionToSectorBytes = ""
End Function
While I commend you on creating the new function unfortunately I beleive it will only give you the logical bytes per sector not the physical bytes per sector. I can’t find any specific documentation on the matter but my testing shows this to be the case. Further, if you examine cimwin32.mof where the Win32_DiskDrive class is defined you can see it is utilizing the DISK_GEOMETRY structure and not the STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR structure that contains the BytesPerPhysicalSector member.
Yeah, I read the KB on Microsoft’s website.
I guess this won’t matter soon. All HDDs are going to be Advanced Format, and even then – their days are numbered due to the arrival of Solid State Disks.