Thursday, February 28, 2008

Controlling Diskpart from PowerShell

The other day, I got a question from one who had attended my PowerShell training classes: How do I control diskpart from a scripting language? Well, PowerShell can do it, but only because it is layered on top of .Net. And this is what I really like about PowerShell; do you do easily grow out of it.

 

The script below starts diskpart, redirects stdin/stdout and writes/reads to them. The tricky part is starting the process correctly and reading from the subprocess without stalling. The StandardOutput stream does not support timed reads, so if you read one time too many, the process freezes.

 

# ProcessStartInfo - to control stdin/stdout
$si=new-object diagnostics.processstartinfo
$si.filename="$env:windir\system32\diskpart.exe"
$si.RedirectStandardInput=$true
$si.RedirectStandardOutput=$true
$Si.UseShellExecute=$false

# Start process
$p=[diagnostics.process]::start($si)


# The difficult part - reading without stalling - and knowing when to stop
function show($maxwait=60){
# Buffer for matching the prompt
$read=""
$start=Get-Date
do {
# Touch EndOfStream to let peek see next bytes
# Do not know why, but it works
$p.StandardOutput.EndOfStream > $null
# If there is a byte pending
while ($p.StandardOutput.peek() -ne -1) {
# Get the byte as a char
$char=[char]$p.StandardOutput.read()
# Write it to the host
write-host -nonewline $char
# And save it in a buffer
$read+=$char
}
# Exit when maxwait has exceeded or the prompt at the end of the buffer
} until ($read -match "DISKPART\>\s+$" -or ((Get-Date)-$Start).TotalSeconds -gt $maxwait)
write-host
}

do {
show
$command=read-host "Command"
$p.StandardInput.Writeline($command)
} until($command -eq "exit")
# Get remaining output, no prompt at exit, so just timeout
show -maxwait 3

 


The method can also be used with other similar tools, just remember that if you do not have to keep any context, it is much easier to call the tool multiple times with different arguments. In PowerShell you can always capture the output using a simple variable -


sc $env:temp\x.x "list disk" -encoding ascii
$output=diskpart /s $env:temp\x.x

or


$ipconfig=ipconfig

 


Have fun!

2 comments:

chris said...

Hey, I came across this and it's pretty easy for simple functions. Of course you can pipe more but this is what I was looking for and your method is a bit more involved and probably more than I want to do:

$a = "list volume" | diskpart

Rick said...

I found you can also chain commands together like this:

"select disk 1", "offline disk" | diskpart