Checkpoints (formerly known as snapshots) are something like an “undo” button for a virtual machine. As the name suggests, you set a marker at a particular point in time for the virtual machine. If you decide that you don’t like what’s happened to the virtual machine at any point after that, you can go back (revert) to that checkpoint. As we, and a great many others, have gone to great lengths to emphasize, checkpoints are in no way a replacement for backup. Like an actual “undo” button, all of the changes made after the checkpoint are lost if you revert. There is something like a “redo” button; you are given the opportunity to take another checkpoint when you revert to an earlier one. However, no copies of the virtual machine are ever made. Only changes are tracked. Do not attempt to use this feature as a backup tool.

The purpose of this post is to explain how to delete hyper-v checkpoints and what happens when you do so in various conditions. The reasoning behind deleting a checkpoint is straightforward: once you’ve decided that you are happy with the state that a virtual machine is in, or if you have multiple checkpoints and are certain that at least some of them are no longer useful, you can get rid of the unnecessary ones. This post will use a virtual machine with the following checkpoint states as an example:

Checkpoint Example

Checkpoint Example

I’ve renamed them to give them some meaning. We’ll treat this as though we are software developers testing potential patch builds to determine which has the optimal outcome. This configuration is not nearly as complicated as it might at first seem, but it does warranty some explanation. To decipher the tree, start at the root. The checkpoint named “Greenfield” represents the base virtual machine. It is using the original VHDX which is now frozen in time. Some data might be being read from it (particularly the operating system files) but nothing is being written to it. Both the “A” and the “C” branches are completely dormant; none of their files are being used at all. The “B” branch has more activity. Any unique data in checkpoints titled “Patch Branch B”, “Patch B1”, “Patch B2”, and “Patch B3 Variant 1” will be read as necessary. Checkpoint “Patch B3” is completely dormant. All writes are being directed to “Patch B3 Variant 1”.

Simple Checkpoint Deletion

For the sake of this discussion, we’ll say that each checkpoint named “Patch Branch” is nothing but a checkpoint right off of “Greenfield”, and that each of the other checkpoints was taken immediately after the application of the entity it is named after. To illustrate a simple deletion, imagine that it’s been determine that Patch A3 had a flaw and needed to be rebuilt. Rather than correct it in Patch A4, your development team has decided to re-release Patch A3. In that case, it makes sense to simply delete the checkpoint titled “Patch A3” and start over from there. To perform the delete, right-click on the checkpoint and click Delete Checkpoint:

Checkpoint: Simple Delete

Checkpoint: Simple Delete

Because “Patch A3” is in a dormant branch, all files related to that checkpoint will be permanently deleted and that will be the end of it. No other checkpoints or the running state of the virtual machine will be affected. To continue with the scenario outlined in the previous section, you would likely Apply the “Patch A2” checkpoint and then upgrade it with the re-released Patch A3 and then take a new checkpoint.

Branch Checkpoint Deletion

Let’s move on to something a bit more complex. Let’s say that the development team decided that the “C” patch branch was an unmitigated disaster and they just want to forget the whole thing to focus on “A” and “B” branches. To effect that, right-click on the “Patch Branch C” checkpoint and choose “Delete Checkpoint Subtree”:

Checkpoint: Subtree Delete

Checkpoint: Subtree Delete

This action will delete all of the checkpoints and all of their related files from the checkpoint that you chose and all of its child checkpoints. Like the “Patch A3” object, this is in a dormant tree so the running state of the virtual machine is not affected in any way.

Mid-Tree Deletion

Next, you get a call from the software engineering team that says that they are perfectly happy with the testing of Patch B1 and will be adding it to the permanent repository. Technically, you don’t have to do anything. But, this is the actively running branch and each layer of checkpoints impacts performance. However, Patches B2 and B3 are still undecided, so you can’t just delete the entire subtree like you did with the former Patch C branch. Fortunately, it’s not a problem. Just delete “Patch B1” the same way that you deleted “Patch A3”:

Checkpoint: Mid-tree Delete

Checkpoint: Mid-tree Delete

Once this completes, you’ll see that “Patch B2” and both of the “Patch B3” items are still present and unchanged… or are they? The merge operation combined all of its changes back into its parent of “Patch Branch B”, as you likely expected. However, those files were the parents of “Patch B2”. If you check, you’ll find that the parent objects of “Patch B2” are now the files for “Patch Branch B”. The parent chain for the items below “Patch B2” are unchanged. The action that you took has the effect of reducing the overall length of the I/O chain for data reads. However, any difference tracking between the former “Patch Branch B” and “Patch B1” are now lost; “Patch B1” is permanently applied to “Patch Branch B”.

Active Tree Deletion

After some time, the development team contacts you again, saying that they are thrilled with the entire B patch series that you’re working with. Patch B3 Variant 1, which you are currently working with, is better than the original Patch B3, so they just want to get rid of that and end testing of the Patch B branch entirely. To effect this, you could, as before, leave everything alone. But, to improve your performance, you could clean up all of those checkpoints. So, you choose to right-click “Patch Branch B” and click Delete Checkpoint Subtree:

Checkpoint: Delete Active Tree

Checkpoint: Delete Active Tree

This is the outcome:

Checkpoint: Active Tree Deleted

Checkpoint: Active Tree Deleted

Was that a mistake, or was it what you really wanted to do? That depends. Let’s go over what all has happened here. First, anything that happened in the “Patch B3” item is completely lost. Second, all of the changes made in “Patch Branch B”, “Patch Branch B2”, and “Patch Branch B3 Variant 1” are all collapsed into the singular “Now” object. What did not happen is any combining of the “A” branch with the “B” branch, even though there are no longer any objects with the “B” name. The “Now” object and the “Patch Branch A” object exist in the tree at exactly the same level, which means that they are separate, unrelated items. Since you may need to do some more testing with the “A” branch, you might find it wise to create another checkpoint from the level that you’re at now just to keep the “B” branch alive. If you jump to another checkpoint without creating a new checkpoint at this level, the entire branch formerly known as “B” will be lost.

Downstream Checkpoint Deletion

With the successful testing of the “B” branch, the development would like you to return to testing the “A” branch. They’re not sure about A1, so they wanted you to start there again. This is how it looks:

Checkpoint: Downstream Setup

Checkpoint: Downstream Setup

As you test, they discover that A1 had a critical flaw that was then propagated into A2. They don’t want either of these patches to see the light of day. What should you do? This?:

Checkpoint: Delete Downstream Quiz

Checkpoint: Delete Downstream Quiz

If this is what you do, you will not have met the requirements of the development team. This action will discard “Patch A2”. However, it will permanently roll all of the changes from “Patch A1” back into “Patch Branch A” and set that as the running state of the virtual machine. Since the development team wanted you to get rid of Patch A1, that means that the “Patch Branch A” object is useless. In this scenario, you fortunately have the “Greenfield” object to apply, but not everyone remembers to take a checkpoint for that purpose. What you actually want to do is first apply “Patch Branch A” (since you don’t care about keeping anything underneath it, there’s no point in taking another checkpoint when prompted). That will leave you with both “Patch A1” and “Patch A2” as dormant downstream objects. You can now safely delete the subtree from the “Patch A1” level to achieve the stated outcome:

Checkpoint: Delete Downstream Tree

Checkpoint: Delete Downstream Tree

Accepting the Current State — Deleting All Checkpoints

After a strange silence, the head of development calls you. Their department has run out of money. They’ve decided to release the “Patch B” tree. What they were working on for the “Patch A” branch is going to be rebranded as “version 2.0” so they can make money on it, but that will all be done at some point in the future. So, they want you to make the “Patch B” branch permanent and get rid of everything else. As you learned from the previous section, this means that you first need to apply the saved “Patch B” branch so that it is active. Then, just start at the root and Delete Checkpoint Subtree:

Checkpoint: Delete All

Checkpoint: Delete All

A couple of things happen when you delete the root. All checkpoints in the active tree are merged upward into the root. All checkpoint data in all other branches are irrevocably lost. So, before you do this, take care to start at the “Now” object and trace your way upward back to the root to ensure that you really are in the tree that you think that you’re in. If you want to discard all of the changes, apply the root object first and then delete the entire subtree.

Once this operation completes, there will be no checkpoints at all. All read and write activity will occur on the primary VHDX and VM files.