Working with DiskPart in PowerShell

Diskpart is one of the most commonly used utilities when automating Windows OS installs and server/desktop provisioning. Unfortunately, it's very awkward to work with, when it comes to getting any data out of it. It only accepts input from a text file containing each command on a single line, and as of Windows 8.1/Server 2012R2 it has no ability to output any data in a useful format such as XML.

Although the output appears in the form of a table, it's really just plain text with spacing.
The actual data it outputs is not even consistent - for example, depending on the size of a disk or partition, it may give you the size or free space in Bytes, KB, MB or GB.
When I needed to programmatically figure out which disk in a server was the one I wanted to create a new partition on, I decided to create a usable table out of the diskpart output, using PowerShell.

Here's how you can do it.

Getting data from diskpart

First, we need to create a diskpart script. All that is, is a text file containing each of the diskpart commands we want to run, in order.

To get information about the disks available to Windows, and their status, you need to use the following diskpart commands:


rescan
list disk

The above lines need to be placed into a txt file, and then the diskpart command is run with the /s switch. <br>
For example:


diskpart /s c:\script.txt

Now, you could just create that txt file manually, and place it in a known location, and that's fine, but I personally prefer to create files dynamically within my PowerShell script - that way everything is up-dateable from one location in future if your script needs change - so here's how you can easily create the dispart txt file on the fly, using PowerShell:


#Set the name of the diskpart txt file
$txtfile = "c:\diskpartscript.txt"

#Add lines to the txt file
"rescan" | out-file $txtfile -Force -Encoding ASCII
"list disk" | out-file $txtfile -Append -Encoding ASCII

The next step is to run diskpart with the /s switch, and the location of your text file. This is where we store the output of the diskpart command in a PowerShell variable.


#Run diskpart with the script file we just created.
#Store output in the $output variable
$output = (diskpart /s $txtfile)

Now we have something like this in the $output variable:


Microsoft DiskPart version 6.1.7601
Copyright (C) 1999-2008 Microsoft Corporation.
On computer: Server01

Please wait while DiskPart scans your configuration...

DiskPart has finished scanning your configuration.

Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
Disk 0 Online 50 GB 0 B
Disk 1 Online 7168 MB 5120 KB
Disk 2 Online 30 GB 30 GB

You could add some error control at this point, if you like.

Cleaning up the output

Now we need to clean up the output so that PowerShell can understand it better.

First, we'll remove all the lines that aren't useful to us.

The number of lines may vary between versions of Windows, for example in Windows 7 there are 10 lines of text we need to remove from the top, but in Windows Server 2012 R2, there are 11 lines (Microsoft kindly added an extra blank line for no reason other than to ignore the concept of standardization and scriptability).

The good thing is that there is always a line of "- - - - -" characters where we want to cut off. So, here's how you can find that line, and set the cutoff line number appropriately.


#determine where the useless diskpart text ends
$count = 0
foreach($line in $output)
{
if($line.Contains("----"))
{
$cutoff = ($count+1)
}
$count++
}

Now that we have set the $cutoff variable appropriately, we can do the following:


#remove the top x lines of useless text
$output = $output | select -last (($output.count)-$cutoff)

In the code above, we're using the ".count" property of the $output variable. This property tells us the total number of lines it contains. We know how many of the lines at the top are useless, so we want to start at the line after that (which we've already set in the $cutoff variable). If there were 14 lines of output, for example, and the "- - - -" cutoff point of the text was on the 10th line, we'd want to subtract 11 from that, to start at the 10th line.

So, what we're telling PowerShell to do here is read through the whole output, and only select the LAST (14 - 11 = 3) lines - which would be lines 11, 12 and 13.

Converting the diskpart output
Next we'll use regex to convert all spaces into commas, and then remove commas at the beginning at end of each line. Effectively turning the output into CSV format.


#convert spaces to commas using the regex value of \s+ (all spaces)
$output = foreach-object -inputobj $output {$_ -replace "\s+",","}

#remove beginning and end commas using the .trimstart and .trimend methods
$output = foreach-object -inputobj $output {$_.trimstart(",")}
$output = foreach-object -inputobj $output {$_.trimend(",")}

This leaves us with the following in the $output variable:


Disk,0,Online,50,GB,0,B
Disk,1,Online,7168,MB,5120,KB
Disk,2,Online,30,GB,30,GB

Next we need to create the datatable to place this data into. We know that the headings should be from the original output: Column1: Disk Number, Column 2: Disk Status, Column 3: Disk size value, Column 4: Disk size unit, Column 5: Free Space Value, Column 6: Free Space Unit.

Notice the "unit" columns. This is what enables us to differentiate the "value" columns between Bytes, KB, MB and GB. Based on what we find when we read this data back, we can perform *1024 or /1024 calculations to convert the values to similar units.


#create a datatable and add the appropriate rows with headings

$diskTable = New-Object System.Data.DataTable
$diskTable.Columns.Add("disk")|out-null
$diskTable.Columns.Add("status")|out-null
$diskTable.Columns.Add("sizevalue", [int])|out-null
$diskTable.Columns.Add("sizeunit")|out-null
$diskTable.Columns.Add("freespacevalue", [int])|out-null
$diskTable.Columns.Add("freespaceunit")|out-null

Now we need to add each line of data to a row in the datatable.


#add data to table
foreach($line in $output)
{
#for each line in the $output variable do the following:

#create a new row in the datatable
$row = $diskTable.NewRow();

#split the line into individual blocks, using a comma as the delimeter
$item = $line.split(",")

#Add the disk number to the "disk" column
$row.disk = $item[0] + " " + $item[1]
#Add the status to the "status" column
$row.status = $item[2]
#Add the size value to the sizevalue column
$row.sizevalue = [int]$item[3]
#Add the size unit (B, KB, MB, GB) to the sizeunit column
$row.sizeunit = $item[4]
#Add the free space value to the freespacevalue column
$row.freespacevalue = [int]$item[5]
#Add the free space unit to the freespaceunit column
$row.freespaceunit = $item[6]

#Add this data to the row in the datatable, then move on to the next data line (if any)
#Note: |Out-Null just avoids an unnecessary output of information to the screen when the script is running
$diskTable.Rows.Add($row) |Out-Null

}

This all gives you the following in the $diskTable variable:


disk : Disk 0
status : Online
sizevalue : 50
sizeunit : GB
freespacevalue : 0
freespaceunit : B

disk : Disk 1
status : Online
sizevalue : 7168
sizeunit : MB
freespacevalue : 5120
freespaceunit : KB

disk : Disk 2
status : Online
sizevalue : 30
sizeunit : GB
freespacevalue : 30
freespaceunit : GB

You can now work with this data just as you would any other PowerShell output, for example, to list the disk names where size is listed as less than 60, you might do something like this:


$diskTable | where {$_.sizenum -lt 60} | select {$_.disk}

Of course, you'd want to do a few more things first, like some calculations to convert all the size and space values to the same unit etc.

Place all of this in a function and you can use it over and over to work with diskpart output from any computer you're working with.