A pleasant walk through computing

Comment for me? Send an email. I might even update the post!

Recover VirtualBox Snapshots

Update 2019-06-20 A reader tried to use this post to troubleshoot the exact same problem, and contacted me with questions. It turned out the post had errors due to me converting from Blogger. I apologize, and have corrected those errors. Thanks, Jeffrey!*

I had something kind of awful happen last night. I was taking VirtualBox on an mSATA USB storage drive between work and home to do lab testing. I had upgraded VBox at work, but not at home, and made the mistake starting my VM at home before upgrading. Running VMs on two different machines is tenuous, but I didn’t expect to suddenly be faced with my VM unable to boot.

I found that the .vbox file was bad. Most of the snapshot and hard disk elements were missing. I had all the snapshots themselves, but nothing to say how to use them. I was able to get the VM back to booting, but it was to my first snapshot, meaning I’d lost hours of work.

After many false starts, I managed this solution, got myself to my last state, and was able to clone the VM. I didn’t end up with a set of snapshots, but did have the last state of the VM, which mattered the most.

This isn’t a tutorial; it presumes you understand VirtualBox, editing XML, etc. Sorry, but it was more important for me to get this out of my head quickly.

Order of Snapshots

I needed to know the order of my snapshot chain. I didn’t have multiple nests, just one chain, which was lucky. But some of my false starts changed the modified file datetime, so I suddenly couldn’t rely on that (I *know, “*make a backup, fool!”)

(Note: VMDK? Yes, I was using VMware disks in VBox, since ultimately the VM is going into VMware. I don’t think that made any difference, but will use native VDI in the future, then convert the resulting disk to VMDK if needed.)

HxD Hex Editor to the rescue! Snapshot disks are huge, like 35GB, and I needed to peer into them because parent-child info is stored there. Most text editors load the file into memory—very bad for large files. Disk-based editing was the only way. I could have used the excellent UltraEdit, but just needed to see text. HxD loaded the files instantly (literally), and the information I needed was at the top, better viewed bytes/row = 128

# Disk DescriptorFile.version=1.CID=b5058026.parentCID=ffffffff.createType="monolithicSparse"..# Extent description.RW 83886080 SPARSE "Win7ProGold.vmdk"..# The disk Data Base .#DDB..ddb.virtualHWVersion = "4".ddb.adapterType="ide".ddb.uuid.image="3b111bc1-9b1e-4b4c-ad00-f4f4a09d7d43".ddb.uuid.parent="00000000-0000-0000-0000-000000000000".ddb.uuid.modification="b042f5da-7fa4-4f1f-a0a1-6820d032819b".ddb.uuid.parentmodification="00000000-0000-0000-0000-000000000000".ddb.geometry.cylinders="16383".ddb.geometry.heads="16".ddb.geometry.sectors="63".ddb.geometry.biosCylinders="1024".ddb.geometry.biosHeads="255".ddb.geometry.biosSectors="63"

Manually adding linefeeds at sensible places yields this:

# Disk DescriptorFile.version=1.CID=b5058026.parentCID=ffffffff.createType="monolithicSparse"..
# Extent description.RW 83886080 SPARSE "Win7ProGold.vmdk"..
# The disk Data Base .
# DDB..ddb.virtualHWVersion = "4".
ddb.adapterType="ide".
ddb.uuid.image="3b111bc1-9b1e-4b4c-ad00-f4f4a09d7d43".
ddb.uuid.parent="00000000-0000-0000-0000-000000000000".
ddb.uuid.modification="b042f5da-7fa4-4f1f-a0a1-6820d032819b".
ddb.uuid.parentmodification="00000000-0000-0000-0000-000000000000".
ddb.geometry.cylinders="16383".
ddb.geometry.heads="16".
ddb.geometry.sectors="63".
ddb.geometry.biosCylinders="1024".
ddb.geometry.biosHeads="255".
ddb.geometry.biosSectors="63"

The important entries are ddb.uuid.image and ddb.uuid.parent. I noted those for each snapshot disk, then made a list of the snapshot UUIDs and their uuid.parent IDs, et voila!

{3b111bc1-9b1e-4b4c-ad00-f4f4a09d7d43}.vmdk UUID  parent 00000000-0000-0000-0000-000000000000
{bf1e79c5-69f0-49d6-ae11-05fd4d4e6e5d}.vmdk UUID  parent 3b111bc1-9b1e-4b4c-ad00-f4f4a09d7d43
{558ea477-39bd-40c6-953b-2c3c8b961668}.vmdk UUID  parent bf1e79c5-69f0-49d6-ae11-05fd4d4e6e5d
{35dd8ad4-47d0-4033-823f-dbbc8f367d9a}.vmdk UUID  parent 558ea477-39bd-40c6-953b-2c3c8b961668
{81ce7961-fa37-46ef-8ae0-a8461e5ebe1b}.vmdk UUID  parent 35dd8ad4-47d0-4033-823f-dbbc8f367d9a
{0197c2be-2ae3-4599-938d-fcf013b77abb}.vmdk UUID  parent 81ce7961-fa37-46ef-8ae0-a8461e5ebe1b
{2b790ec1-be3a-4a7b-8075-89bc54948825}.vmdk UUID  parent 0197c2be-2ae3-4599-938d-fcf013b77abb

Here's the same info in a tree:

00000000-0000-0000-0000-000000000000
|_3b111bc1-9b1e-4b4c-ad00-f4f4a09d7d43
  |_bf1e79c5-69f0-49d6-ae11-05fd4d4e6e5d
    |_558ea477-39bd-40c6-953b-2c3c8b961668
      |_35dd8ad4-47d0-4033-823f-dbbc8f367d9a
        |_81ce7961-fa37-46ef-8ae0-a8461e5ebe1b
          |_0197c2be-2ae3-4599-938d-fcf013b77abb
            |_2b790ec1-be3a-4a7b-8075-89bc54948825

.vbox File

Now, how to update the .vbox file. Without going into the sordid tale…

  1. Remove snapshot info from <Machine> element.
  2. Remove top <Snapshot> element.
  3. Add nested <HardDisk> elements in the correct order.
  4. Set the StorageController <Image> uuid to the last hard disk element’s uuid.

Yuck

Note: The final child snapshot is self-closed XML, terminated by />

<machine laststatechange="2016-02-02T12:16:14Z" snapshotfolder="Snapshots" ostype="Windows7\_64" name="Win7ProGold" uuid="{e21fa63b-d412-4e19-88c8-971ec7bc3ad7}">  
        <mediaregistry>  
          <harddisks>  
            <harddisk uuid="{3b111bc1-9b1e-4b4c-ad00-f4f4a09d7d43}" type="Normal" format="VMDK" location="Win7ProGold.vmdk">  
              <harddisk uuid="{bf1e79c5-69f0-49d6-ae11-05fd4d4e6e5d}" format="VMDK" location="Snapshots/{bf1e79c5-69f0-49d6-ae11-05fd4d4e6e5d}.vmdk">  
                <harddisk uuid="{558ea477-39bd-40c6-953b-2c3c8b961668}" format="VMDK" location="Snapshots/{558ea477-39bd-40c6-953b-2c3c8b961668}.vmdk">  
                  <harddisk uuid="{35dd8ad4-47d0-4033-823f-dbbc8f367d9a}" format="VMDK" location="Snapshots/{35dd8ad4-47d0-4033-823f-dbbc8f367d9a}.vmdk">  
                    <harddisk uuid="{81ce7961-fa37-46ef-8ae0-a8461e5ebe1b}" format="VMDK" location="Snapshots/{81ce7961-fa37-46ef-8ae0-a8461e5ebe1b}.vmdk">  
                      <harddisk uuid="{0197c2be-2ae3-4599-938d-fcf013b77abb}" format="VMDK" location="Snapshots/{0197c2be-2ae3-4599-938d-fcf013b77abb}.vmdk">  
                        <harddisk uuid="{2b790ec1-be3a-4a7b-8075-89bc54948825}" format="VMDK" location="Snapshots/{2b790ec1-be3a-4a7b-8075-89bc54948825}.vmdk" />  
                      </harddisk>  
                    </harddisk>  
                  </harddisk>  
                </harddisk>  
              </harddisk>  
            </harddisk>  
          </harddisks>    
    ...
    
    <StorageControllers>  
      <StorageController name="SATA" type="AHCI" PortCount="2" useHostIOCache="false" Bootable="true" IDE0MasterEmulationPort="0" IDE0SlaveEmulationPort="1" IDE1MasterEmulationPort="2" IDE1SlaveEmulationPort="3">  
        <AttachedDevice type="HardDisk" hotpluggable="false" port="0" device="0">  
          <Image uuid="{2b790ec1-be3a-4a7b-8075-89bc54948825}">  
        </AttachedDevice>

At this point, I could start the VM successfully and clone it, flattening the disk structure.

Auto Updating a New Windows 7 Installation

There are many reasons you may create a clean Windows installation:

  • Gold image for deployment
  • Lab installation for testing
  • Reinstall/recovery

Installing all the Windows updates is tedious, especially with reboots. I don’t know why Microsoft doesn’t include a big “Bring up to date” button that would let you configure for reboots. I figured someone had this sorted, and started searching.

I can’t take credit for the PowerShell script, but am glad to have found it. It does have one oddity: it attempts to disable OpenSSHd. I don’t know if that’s a weird security leftover, or a precaution by the author. The script still runs even if the OpenSSHd service isn’t found, which it shouldn’t be. It’s not part of Windows.

I made these changes:

  1. Commented out OpenSSH commands
  2. After searching for updates, I selectively hide updates such as KB3035583, which is the Windows 10 advertisement (not needed in my case).

Here’s the link to the original script by Joe Fitzgerald:

https://gist.github.com/joefitzgerald/8203265

And the script itself, with my changes. To execute the script, right-click and choose Run with PowerShell. Or

  1. open admin command prompt
  2. cd to location of script file
  3. > powershell.exe -ExecutionPolicy ByPass -File win-update.ps1

 

param($global:RestartRequired=0,
        $global:MoreUpdates=0,
        $global:MaxCycles=10)

function Check-ContinueRestartOrEnd() {
    $RegistryKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
    $RegistryEntry = "InstallWindowsUpdates"
    switch ($global:RestartRequired) {
        0 {         
            $prop = (Get-ItemProperty $RegistryKey).$RegistryEntry
            if ($prop) {
                Write-Host "Restart Registry Entry Exists - Removing It"
                Remove-ItemProperty -Path $RegistryKey -Name $RegistryEntry -ErrorAction SilentlyContinue
            }
            
            Write-Host "No Restart Required"
            Check-WindowsUpdates
            
            if (($global:MoreUpdates -eq 1) -and ($script:Cycles -le $global:MaxCycles)) {
                Stop-Service $script:ServiceName -Force
                Set-Service -Name $script:ServiceName -StartupType Disabled -Status Stopped 
                Install-WindowsUpdates
            } elseif ($script:Cycles -gt $global:MaxCycles) {
                Write-Host "Exceeded Cycle Count - Stopping"
            } else {
                Write-Host "Done Installing Windows Updates"
                Set-Service -Name $script:ServiceName -StartupType Automatic -Status Running         
            }
        }
        1 {
            $prop = (Get-ItemProperty $RegistryKey).$RegistryEntry
            if (-not $prop) {
                Write-Host "Restart Registry Entry Does Not Exist - Creating It"
                Set-ItemProperty -Path $RegistryKey -Name $RegistryEntry -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File $($script:ScriptPath)"
            } else {
                Write-Host "Restart Registry Entry Exists Already"
            }
            
            Write-Host "Restart Required - Restarting..."
            Restart-Computer
        }
        default { 
            Write-Host "Unsure If A Restart Is Required" 
            break
        }
    }
}

function Install-WindowsUpdates() {
    $script:Cycles++
    Write-Host 'Evaluating Available Updates:'
    $UpdatesToDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl'
    foreach ($Update in $SearchResult.Updates) {
        if (($Update -ne $null) -and (!$Update.IsDownloaded)) {
            [bool]$addThisUpdate = $false
            if ($Update.InstallationBehavior.CanRequestUserInput) {
                Write-Host "SKIPPING: $($Update.Title) because it requires user input"
            } else {
                if (!($Update.EulaAccepted)) {
                    Write-Host "> Note: $($Update.Title) has a license agreement that must be accepted. Accepting the license."
                    $Update.AcceptEula()
                    [bool]$addThisUpdate = $true
                } else {
                    [bool]$addThisUpdate = $true
                }
            }
        
            if ([bool]$addThisUpdate) {
                Write-Host "Adding: $($Update.Title)"
                $UpdatesToDownload.Add($Update) |Out-Null
            }
        }
    }
    
    if ($UpdatesToDownload.Count -eq 0) {
        Write-Host "No Updates To Download..."
    } else {
        Write-Host 'Downloading Updates...'
        $Downloader = $UpdateSession.CreateUpdateDownloader()
        $Downloader.Updates = $UpdatesToDownload
        $Downloader.Download()
    }
    
    $UpdatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl'
    [bool]$rebootMayBeRequired = $false
    Write-Host 'The following updates are downloaded and ready to be installed:'
    foreach ($Update in $SearchResult.Updates) {
        if ($Update.IsDownloaded) {
            Write-Host "> $($Update.Title)"
            $UpdatesToInstall.Add($Update) |Out-Null
              
            if ($Update.InstallationBehavior.RebootBehavior -gt 0){
                [bool]$rebootMayBeRequired = $true
            }
        }
    }
    
    if ($UpdatesToInstall.Count -eq 0) {
        Write-Host 'No updates available to install. Press any key.'
        $global:MoreUpdates=0
        $global:RestartRequired=0
        #wait for user input, so message is seen
        $wait = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
        break
    }

    if ($rebootMayBeRequired) {
        Write-Host 'These updates may require a reboot'
        $global:RestartRequired=1
    }
    
    Write-Host 'Installing updates...'
  
    $Installer = $script:UpdateSession.CreateUpdateInstaller()
    $Installer.Updates = $UpdatesToInstall
    $InstallationResult = $Installer.Install()
  
    Write-Host "Installation Result: $($InstallationResult.ResultCode)"
    Write-Host "Reboot Required: $($InstallationResult.RebootRequired)"
    Write-Host 'Listing of updates installed and individual installation results:'
    if ($InstallationResult.RebootRequired) {
        $global:RestartRequired=1
    } else {
        $global:RestartRequired=0
    }
    
    for($i=0; $i -lt $UpdatesToInstall.Count; $i++) {
        New-Object -TypeName PSObject -Property @{
            Title = $UpdatesToInstall.Item($i).Title
            Result = $InstallationResult.GetUpdateResult($i).ResultCode
        }
    }
    
    Check-ContinueRestartOrEnd
}

function Check-WindowsUpdates() {
    Write-Host "Checking For Windows Updates"
    $Username = $env:USERDOMAIN + "\" + $env:USERNAME
 
    New-EventLog -Source $ScriptName -LogName 'Windows Powershell' -ErrorAction SilentlyContinue
 
    $Message = "Script: " + $ScriptPath + "`nScript User: " + $Username + "`nStarted: " + (Get-Date).toString()

    Write-EventLog -LogName 'Windows Powershell' -Source $ScriptName -EventID "104" -EntryType "Information" -Message $Message
    Write-Host $Message

    $script:UpdateSearcher = $script:UpdateSession.CreateUpdateSearcher()
    $script:SearchResult = $script:UpdateSearcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0")      
    if ($SearchResult.Updates.Count -ne 0) {
        $script:SearchResult.Updates |Select-Object -Property Title, Description, SupportUrl, UninstallationNotes, RebootRequired, EulaAccepted |Format-List
        $global:MoreUpdates=1
    } else {
        Write-Host 'There are no applicable updates'
        $global:RestartRequired=0
        $global:MoreUpdates=0
    }
    
    #clf hide undesired updates
    foreach ($Update in $SearchResult.Updates) {
        if ($Update.Title.Contains("KB3035583")) {
            Write-Host "Hiding $($Update.Title) because it's the Windows 10 Notification."
            $Update.IsHidden = $TRUE
        }
    }
}

$script:ScriptName = $MyInvocation.MyCommand.ToString()
$script:ScriptPath = $MyInvocation.MyCommand.Path
$script:UpdateSession = New-Object -ComObject 'Microsoft.Update.Session'
$script:UpdateSession.ClientApplicationID = 'Packer Windows Update Installer'
$script:UpdateSearcher = $script:UpdateSession.CreateUpdateSearcher()
$script:SearchResult = New-Object -ComObject 'Microsoft.Update.UpdateColl'
$script:Cycles = 0

# $script:ServiceName = "OpenSSHd"

# Stop-Service $script:ServiceName -Force
# Set-Service -Name $script:ServiceName -StartupType Disabled -Status Stopped 

Check-WindowsUpdates
if ($global:MoreUpdates -eq 1) {
    Install-WindowsUpdates
} else {
    Check-ContinueRestartOrEnd
}

 

References

An alternative method, clever but not as flexible.

http://serverfault.com/questions/320750/best-way-to-fully-update-a-new-installed-windows

WSUS Update class

https://msdn.microsoft.com/en-us/library/windows/desktop/aa386099(v=vs.85).aspx

Powershell ReadKey doesn’t work when using editor.

https://social.technet.microsoft.com/Forums/windowsserver/en-US/0787bcba-f977-400d-8b62-57ec64a71a8a/why-do-i-get-an-exception-calling-readkey?forum=winserverpowershell

ShoreTel - Brilliantly Simple?

And only an 858 page manual!

unnamed