DEV Community

TiltedLunar123
TiltedLunar123

Posted on

My VM state check reads one line of VBoxManage output, and it's one character from reading the wrong one

I have a PowerShell script that builds Fedora VMs in VirtualBox with no clicking. It also does AlmaLinux, Rocky, and CentOS Stream. After it kicks off an install it sits in a loop asking VirtualBox one question over and over: is this VM still running, or did it power off. The install is done when the guest powers itself off, so that answer is the whole exit condition.

The question goes through one small function, Get-VMState. It shells out to VBoxManage, reads back the machine-readable dump, and pulls the single line it cares about.

Here's the whole thing:

function Get-VMState {
    param(
        [Parameter(Mandatory)][string]$VBoxManage,
        [Parameter(Mandatory)][string]$VMName
    )

    $info = Invoke-VBoxManage -VBoxManage $VBoxManage -Arguments @("showvminfo", $VMName, "--machinereadable") -NoThrow
    if ([string]::IsNullOrWhiteSpace($info)) {
        return ""
    }

    $line = ($info -split "`r?`n" | Where-Object { $_ -like 'VMState=*' } | Select-Object -First 1)
    if (-not $line) {
        return ""
    }

    return ($line.Split("=", 2)[1].Trim('"'))
}
Enter fullscreen mode Exit fullscreen mode

Nothing exciting. Except showvminfo --machinereadable hands you output like this:

name="Fedora-Workstation"
VMState="running"
VMStateChangeTime="2026-07-08T00:00:00.000000000"
Enter fullscreen mode Exit fullscreen mode

Look at lines 2 and 3. VMState and VMStateChangeTime. One is the answer I want. The other is a timestamp that starts with the exact same eight characters.

The filter is $_ -like 'VMState=*'. The part carrying the weight there is the =. VMStateChangeTime starts with VMState, but the next character is C, not =, so it never matches the pattern. If I'd been a little lazier and written 'VMState*', both lines match, and then Select-Object -First 1 hands back whichever one happens to print first.

I had never tested any of this. The function has been in the resume path and the install-wait loop the whole time, working fine, completely unexercised. That's the part that got under my skin. It works because of one character in a wildcard, and nothing in the repo would tell me if I ever broke it.

So this week I wrote the tests. The one I actually care about:

It "Does not pick up VMStateChangeTime instead of VMState" {
    Mock Invoke-VBoxManage {
        'VMStateChangeTime="2026-07-08T00:00:00.000000000"' + "`n" + 'VMState="running"'
    }
    Get-VMState -VBoxManage "vbox" -VMName "vm" | Should -Be "running"
}
Enter fullscreen mode Exit fullscreen mode

I put VMStateChangeTime first on purpose. If the match ever loosens to a prefix, Select-Object -First 1 grabs the timestamp line, the function returns a date string instead of running or poweroff, and the wait loop has no idea the install ever finished. It would just sit there until the 90-minute timeout gives up. Ordering the mock wrong-side-up is the whole point of the test.

There's a second small thing in that return line I'm glad I pinned:

return ($line.Split("=", 2)[1].Trim('"'))
Enter fullscreen mode Exit fullscreen mode

Split("=", 2) splits on the first = only and stops. If a value ever contained an =, splitting on all of them and taking [1] would chop the value in half. VirtualBox's state values don't have equals signs today, but the two-argument split costs nothing and means I don't have to keep that assumption in my head. Trim('"') strips the quotes VBoxManage wraps around everything.

The rest of the tests cover the dull cases that still matter: quotes get stripped, poweroff reads back as poweroff, a missing VMState line returns an empty string, and no output at all returns an empty string instead of throwing. Six tests. None of them found a bug.

That's the honest bit. This wasn't a fix. The code was already right. I just had a function steering a loop that eventually detaches media and deletes a disk with a hashed password on it, and I had zero coverage saying that function stays right. The tests are there so the next time I "clean up" that filter, something goes red before an install quietly hangs for an hour and a half.

Same commit added tests for two other spots that were one substring away from misbehaving: the artifact cleanup that deletes ks.cfg and the OEMDRV disk after install, and the existing-VM check that has to match Fedora-Workstation without also firing on Fedora-Workstation-2. That last one leans on an anchored regex and [regex]::Escape so a dot in a VM name stays a literal dot. Different post.

Whole thing is here if you want to read it: https://github.com/TiltedLunar123/Fedora-VirtualBox-Auto-Installer-PowerShell

It works. The code I trust the least now is the one-liner I never reopen, the kind that's never once broken so I never look at it. This was one of those.

Top comments (0)