Can’t Delete a VHD if you Ctrl-C the New-VHD Command

6 minute read

TL;WR: If you started creating a classic fixed VHD with New-VHD, then tried to cancel it with Ctrl+C you’ll probably realize that it’s still stuck initializing in the background. Use WMI to kill it.
Click here to skip to the answer

I was experimenting with some data recovery stuff and needed some classic fixed VHD’s. I started creating a 1 TB VHD with New-VHD but needed to change it so I hit Ctrl+C and assumed that would cancel the cmdlet and the VHD creation. I assumed incorrectly.

When I tried to delete the VHD I got this error:

File In Use - The action can't be completed because the file is open in System. Close the file and try again.
Fig 1. File In Use, open in System, Ruh Roh!

I figured there was still a job creating the VHD so I scanned Virtualization CIM Classes and found the CIM_ConcreteJob class, after a quick poke this seemed to be what I was looking for.

Get-CimInstance -Namespace root\virtualization\v2 -ClassName CIM_ConcreteJob | select InstanceID, Caption, Description, Name, Status, StatusDescriptions, JobStatus, PercentComplete, Cancellable


InstanceID         : "D65CC6A8-E7B4-45E4-A13D-CF7BAD642912"
Caption            : Virtual Hard Disk Creation
Description        : Creating Virtual Hard Disk
Name               : Virtual Hard Disk Creation
Status             : OK
StatusDescriptions : {Job is running}
JobStatus          : Job is running
PercentComplete    : 7
Cancellable        : True

Now, about killing it: CIM_ConcreteJob.RequestStateChange(RequestedState, TimeoutPeriod). Looks simple enough!

Alright, lets try to request a safe/clean termination with a 30 second timeout.

$job = Get-CimInstance -Namespace root\virtualization\v2 -ClassName CIM_ConcreteJob -Filter 'InstanceID = "D65CC6A8-E7B4-45E4-A13D-CF7BAD642912"'
$job | Invoke-CimMethod -MethodName RequestStateChange -Arguments @{RequestedState = 4;TimeoutPeriod = (Get-Date).AddSeconds(30)}

ReturnValue PSComputerName
----------- --------------
      32773

Annnd the return code isn’t in the list. On the upside that looks like a common return code (we’ll come back to that in a minute). Let’s try the Kill command:

$job | Invoke-CimMethod -MethodName RequestStateChange -Arguments @{RequestedState = 5;TimeoutPeriod = (Get-Date).AddSeconds(30)}

----------- --------------
      32773

Great, same error. The doc says you can supply a null to indicate there’s no timeout requirement so we’ll try that instead:

$job | Invoke-CimMethod -MethodName RequestStateChange -Arguments @{RequestedState = 5;TimeoutPeriod = $null}

ReturnValue PSComputerName
----------- --------------
      32775

Sigh, At least it’s different error. Alright, assumption rechecking time. First, what is the specific Job implementation?

$job | Get-Member -View Base
#TypeName: Microsoft.Management.Infrastructure.CimInstance#root/virtualization/v2/Msvm_StorageJob          

Looks like this is an Msvm_StorageJob, let’s see if it’s implementation of RequestStateChange has any juicy details!

The good news is that we have the return codes, the bad news is that they don’t have any descriptions or names. Let’s add a new assumption for a minute and assume that the GetError method’s return values apply to the RequestStateChange method. Now our errors mean Invalid Parameter and Invalid State although that doesn’t get us very far.

Scraped Values

Code Description
Failed (32768)
Access Denied (32769)
Not Supported (32770)
Status is unknown (32771)
Timeout (32772)
Invalid parameter (32773)
System is in used *[sic]* (32774)
Invalid state for this operation (32775)
Incorrect data type (32776)
System is not available (32777)
Out of memory (32778)
Method Parameters Checked - Transition Started (4096)
Invalid State Transition (4097)
Use of Timeout Parameter Not Supported (4098)
Busy (4099)

Quality Microsoft documentation

A list of all of the return codes we've been looking at, but they have no titles or descriptions.
Fig 2. Thanks Microsoft.

Alright, maybe it is an invalid parameter. Possibly the .Net DateTime to CIM_DATETIME conversion getting botched? Let’s see if the same thing happens using WMIExplorer.

WMIExplorer returning the 32773 error code
Fig 3. Weary Sigh.

Good News: Calling convention in powershell at least matches the WMI Explorer results. Well, we have another option. The New Disk Wizard IS cancellable so let’s see if we can’t reverse engineer how it works. First up we’re going to do a simple WMI trace using event viewer Using these instructions and we see multiple events for RequestStateChange on Msvm_StorageJob instances, hmm…

CorrelationId = {950ABF70-EA9B-4722-8E07-B77566C28E44}; ProcessId = 57856; Protocol = DCOM; Operation = MI_Session::Invoke; User = NULL; Namespace = root\virtualization\v2
---
CorrelationId = {950ABF70-EA9B-4722-8E07-B77566C28E44}; GroupOperationId = 117375; OperationId = 117376; Operation = Start IWbemServices::ExecMethod - root\virtualization\v2 : \\.\ROOT\virtualization\v2:Msvm_StorageJob.InstanceID="7C8C39C1-03ED-456B-9A31-1B7A456BA347"::RequestStateChange; ClientMachine = [REDACTED]; User = [REDACTED]\[REDACTED]; ClientProcessId = 57856; NamespaceName = 133044712970425349
---
CorrelationId = {950ABF70-EA9B-4722-8E07-B77566C28E44}; GroupOperationId = 117375; OperationId = 112244; ClassName= Msvm_StorageJob; MethodName = RequestStateChange; ImplementationClass = Msvm_StorageJob; ClientMachine = [REDACTED]; User = [REDACTED]\[REDACTED]; ClientProcessId = 57856; NamespaceName = \\.\root\virtualization\v2
---
ProviderInfo for GroupOperationId = 117375; Operation = Provider::ExecMethod - VmmsWmiInstanceAndMethodProvider : Msvm_StorageJob.InstanceID="7C8C39C1-03ED-456B-9A31-1B7A456BA347"::RequestStateChange; HostID = 2188; ProviderName = VmmsWmiInstanceAndMethodProvider; ProviderGuid = {0c172fd4-1b2a-11da-994c-0008744f51f3}; Path = 
---
Stop OperationId = 117376; ResultCode = 0x0

Unfortunately that didn’t show me the parameters (which makes sense, in WMI method parameters are packed into an object) but it does verify that we’re going about it the right way. To find the parameters we’re going to have to bust out API Monitor.

APIMonitor showing the WMI Calls from the Hyper-V Disk Wizard.
Fig 4. Rohitab API Monitor.
APIMonitor showing the parameter object creation.
Fig 4. The Hyper-V Wizard Generating its parameters.

Aside I just want to point out how amazingly handy this tool is. It goes much deeper than procmon/sysmon/WPA/WPP traces (unless you somehow manage to get the Microsoft private symbols/TMFs) and has a ton of accellerators that make ‘getting the job done’ faster than if you have to bust out dnSpy/dotpeek/windbg/ida/etc. In this case it’s automatically capturing the contents of the objects in the parameter pointers and saving them. No manual breakpoint necessary. I’ve even used API Monitor to hijack a firmware update tool and edit it’s ioctls on the fly to force it to update a device that the OEM didn’t want updated. Long story.

Aside-Aside Sorry for the zoom & swoop effect, middle ground between “open full image in new tab” and “fancy overlay with buttons and stuff”.

You can see the documentation on how you pass parameters to a WMI Method here but the gist is that the wizard uses GetMethod to have WMI build you the __PARAMETERS object and PutMethod to populate the properties, then when ExecMethodAsync is called you just give it the pointer to your populated object. API monitor lets you see what values the Wizard is packing into that object before the call happens.

A quick analysis shows that mmc is building the request with RequestedState of 4 and a TimeoutPeriod of null. I’m totally certain that we tried that earlier (Narrator: We didn’t.) it must be something else going on. Let’s see if we can manually cancel a VHD creation started from the wizard!

$tjob = Get-CimInstance -Namespace root\virtualization\v2 -ClassName CIM_ConcreteJob -Filter 'InstanceID = "GUID-INSTANCE-ID-FROM-MMC"'
$tjob | Invoke-CimMethod -MethodName RequestStateChange -Arguments @{RequestedState = 4;TimeoutPeriod = $null}

ReturnValue PSComputerName
----------- --------------
       4096

4096 isn’t listed as a storage job result but looking at the CIM_ConcreteJob docs that’s Method Parameters Checked - Transition Started (4096)! It worked! So why can’t we cancel the VHD creation started via powershell? The two CIM jobs appeared identical!

Oh no…

Oh no…

Guess what combination of RequestedState’s and TimeoutPeriod’s we didn’t try?!

$jobc = Get-CimInstance -Namespace root\virtualization\v2 -ClassName CIM_ConcreteJob -Filter 'InstanceID = "D65CC6A8-E7B4-45E4-A13D-CF7BAD642912"'
$jobc | Invoke-CimMethod -MethodName RequestStateChange -Arguments @{RequestedState = 4;TimeoutPeriod = $null}

ReturnValue PSComputerName
----------- --------------
       4096

If I had tried a few different parameter combinations off the bat (honestly there weren’t that many) I would’ve probably figured this out 90 minutes ago. On the other hand, if this had been documented in any way I wouldn’t have had to guess all of that.

How to cancel a fixed VHD creation started by New-VHD


# Step 1. Find the InstanceID of your job
Get-CimInstance -Namespace root\virtualization\v2 -ClassName CIM_ConcreteJob | select InstanceID, Caption, Description, Name, Status, StatusDescriptions, JobStatus, PercentComplete, Cancellable

#I only had one job running so it was easy enough to figure out.
InstanceID         : D65CC6A8-E7B4-45E4-A13D-CF7BAD642912
Caption            : Virtual Hard Disk Creation
Description        : Creating Virtual Hard Disk
Name               : Virtual Hard Disk Creation
Status             : OK
StatusDescriptions : {Job is running}
JobStatus          : Job is running
PercentComplete    : 7
Cancellable        : True


# Step 2. Grab and cancel the Job
$jobc = Get-CimInstance -Namespace root\virtualization\v2 -ClassName CIM_ConcreteJob -Filter 'InstanceID = "D65CC6A8-E7B4-45E4-A13D-CF7BAD642912"'
$jobc | Invoke-CimMethod -MethodName RequestStateChange -Arguments @{RequestedState = 4;TimeoutPeriod = $null}