TL;DR

I keep four small-form-factor PCs on a bench for testing and repurposing — bought used, need fresh OS images, fresh BIOS settings, and no monitor or keyboard. A PiKVM V4 Plus with a multiport switch gives me eyes and hands on all four boxes over the network. Dell’s cctk command-line tool (Command | Configure) lets me bake BIOS settings — boot order, AHCI mode, Wake-on-LAN, power-on-after-failure — into scripted runs instead of clicking through F2 menus. No monitor, no keyboard, no physical access for weeks at a time. Everything repeatable, everything as code.

The problem: a bench of headless Dells

I’ve got a workbench in the lab — four Dell OptiPlex and Latitude boxes picked up cheap as government surplus. (A couple are the same kind of machine I used in my surplus-Dell k3s build, now living on the bench.) They’re test nodes, staging machines for OS images, a place to try hardware before cluster deployment. None of them have a permanent home: they get re-imaged, reconfigured, tested again, and rotated through different roles.

Traditionally this meant dragging a monitor and keyboard over every time something needed a BIOS tweak or an OS reinstall. Seventeen cables, a keyboard that never quite works, an hour of setup to change one setting. And if the box was in a rack or behind other equipment, you had a worse time of it.

The real problem: BIOS lives in the firmware, and the firmware doesn’t care about your preference for graphical menus. The F2 setup screen is convenient for one machine. For four machines, doing the same steps repeatedly, it’s madness. Scripting it means extracting the BIOS config into something repeatable and code-like — which is what cctk is for. But you still need to see what’s happening during an OS install or a boot sequence, which is where PiKVM comes in.

PiKVM: KVM-over-IP without the rackmount expense

PiKVM is KVM (keyboard, video, mouse) over IP — think “Dell iDRAC for a used desktop.” It’s a small box that sits between your bench machines and your network, giving you keyboard, mouse, display, and power control from a web interface or a CLI. No agent software on the target, no special driver, no licensing. Just USB in, HDMI in, network out.

The PiKVM V4 Plus supports one KVM connection; stack a Switch Multiport Extender (4 ports per unit, daisy-chains) and you’ve got remote management over a whole bench. At today’s used prices, under $300 for the pair. Order of magnitude cheaper than a single iDRAC license.

Here’s the topology I’ve got:

Bench desktops (4×)
    │ [USB, HDMI, ATX power headers]
PiKVM V4 Plus ←──┬─ Switch unit 0 (ports 0–3)
          https://192.168.1.221 (web UI + API)
          Your workstation (SSH, browser, CLI scripts)

PiKVM gives you three things:

  1. Video capture — you see exactly what’s on the target’s display in real-time.
  2. Keyboard and mouse — you can type and click without being physically present.
  3. Power control — ATX hard-off, reboot, and (if the boxes support it) Wake-on-LAN magic packets.

Setup is straightforward: plug HDMI and USB into the PiKVM, plug Ethernet into your lab network, and you’ve got remote access. Authentication defaults to admin/admin; you can change it later.

Scripting it with the helper CLI

PiKVM ships with a REST API, but there’s no official Python library for the multiport switching scenario. I use a wrapper CLI (pikvm.py) that handles the common tasks:

python3 pikvm.py status                    # show active port, video state, power
python3 pikvm.py switch 2                  # switch to port 2, wait for video sync
python3 pikvm.py snap -o screenshot.jpg    # capture the display
python3 pikvm.py key F2 Enter               # press F2, then Enter
python3 pikvm.py type 'diskpart'            # type a string into the keyboard buffer
python3 pikvm.py mouse 960 540 --click left # click at pixel coordinates
python3 pikvm.py msd select win11.img       # attach a virtual USB drive
python3 pikvm.py msd attach                 # mount it to the active port
python3 pikvm.py atx 1 reset                # power-cycle port 1

The workflow is look, then act. Snap a screenshot, read it (via vision), take an action based on what you see, then snap again to verify. Never blind-type through menus — you’ll miss the reboot message and end up typing random characters into Windows.

For example, reimaging a box:

# Power cycle the target
python3 pikvm.py atx 0 reset

# Catch the one-time boot menu with F12, held for ~12 seconds post-reset
python3 pikvm.py spam F12 --seconds 12

# Screenshot to see which boot device shows up
python3 pikvm.py snap -o /tmp/boot_menu.jpg

# Attach the Win11 image from storage, select it at the boot menu
python3 pikvm.py msd select win11-bench.img
python3 pikvm.py msd attach
python3 pikvm.py key ArrowDown Enter  # select PiKVM USB from the list

# Windows Setup runs from here; you don't need to watch it
# Once it boots into Windows, WinPE, or OOBE, the image is off the USB
# Now you can switch away and work on the next port

The magic is that once the target is running off its own disk (SSD), you can switch ports without yanking the install media out from under it. So while port 0 is in unattended OOBE setup, you switch to port 1 and start its image. Full parallelism.

Dell CCTK: BIOS configuration as shell commands

Dell’s cctk tool (Command | Configure, version 5.2.2) is a command-line utility that reads and writes BIOS settings without the F2 menu. It runs on the Dell (in WinPE or Windows), not on your workstation. Syntax is simple:

# Read a setting (no = sign)
cctk --SataOperation

# Write a setting (with = sign)
cctk --SataOperation=Ahci
cctk --BootOrder --sequence=<from-listing>
cctk --WakeOnLan=LanOnly
cctk --DeepSleepCtrl=Disable
cctk --AcPwrRcvry=On
cctk --Asset=HOMELAB-001

Settings take effect at the next reboot. Exit code 0 = success; anything else means check the error codes in the cctkerrorcodes.txt file.

The bench BIOS recipe — what I run on a fresh Dell the moment WinPE boots:

cctk --SataOperation=Ahci
cctk --UefiNwStack=Enable
cctk --WakeOnLan=LanOnly
cctk --DeepSleepCtrl=Disable
cctk --AcPwrRcvry=On

Why each one:

  • AHCI: Most modern systems ship in RAID mode by default (legacy Dell). AHCI is the standard for modern SSDs and Kubernetes nodes. Without this, etcd cries and your cluster doesn’t boot.
  • UEFI network stack: Enables PXE boot from the onboard NIC — critical if you want to image via network boot later.
  • Wake-on-LAN: Lets you power on the box remotely using a magic packet (PiKVM’s wol command or any network tool).
  • Disable Deep Sleep: If the box is in sleep state (S5), Wake-on-LAN won’t work. Disabling Deep Sleep Control keeps the NIC awake.
  • AC Power Recovery: If AC power flickers (which it does), the box powers back on automatically instead of staying dark until someone physically presses the power button.

Getting cctk onto the Dells is straightforward. If they’re booting from a PXE server, you can pull it over the network:

curl -s -o X:\g.cmd http://192.168.1.216:8088/cctk/get-cctk.cmd & X:\g.cmd

That downloads the cctk binaries into X:\cctk\ and runs a sanity check. Then you can invoke it directly:

X:\cctk\cctk.exe --SataOperation=Ahci

Putting it together: a repeatable imaging loop

Here’s the full dance for reimaging and configuring a batch of Dells:

#!/bin/bash
# Reimage and configure four Dells, one at a time
# Running from workstation; boxes boot from PiKVM's virtual USB

for port in 0 1 2 3; do
  echo "=== Port $port: reset and wait for boot menu ==="
  python3 pikvm.py switch $port
  python3 pikvm.py atx $port reset
  python3 pikvm.py spam F12 --seconds 12
  
  echo "=== Attaching Win11 image ==="
  python3 pikvm.py msd select win11-bench.img
  python3 pikvm.py msd attach
  python3 pikvm.py key ArrowDown Enter  # select USB from boot menu
  
  echo "=== Waiting for WinPE prompt (watching /tmp/port$port.txt progress) ==="
  # Just move on; the image will be installing in the background
  # While port 0 is in DISM-apply, start port 1
  sleep 5
done

# Once all four are imaging, poll for completion
for port in 0 1 2 3; do
  python3 pikvm.py switch $port
  while true; do
    python3 pikvm.py snap -o /tmp/port${port}.jpg
    # Manually check for OOBE desktop or login screen
    # (Full automation here needs vision or WMI polling)
    sleep 30
  done
done

# Now apply BIOS settings from within each booted Windows
# (This is where you'd inject a PowerShell script that runs cctk)

In practice, I keep a smaller script that does one box at a time, watches the progress by snapping screenshots, and only advances when I verify it’s ready. The point is that nothing requires physical presence or a monitor. Everything happens over the network, and every step is logged.

The honest caveats

PiKVM is another appliance to maintain. It runs Linux, it can go down, and when it does you lose remote access to the bench. I keep the boxes themselves healthy enough that I can serial-console into them or use the occasional F12 boot-menu catch if PiKVM is wedged. Redundancy would be a second PiKVM on a different network path, which I haven’t bothered with for a homelab bench.

CCTK is Dell-specific. Other manufacturers (HP, Lenovo, etc.) have their own BIOS config tools, usually equally opaque and equally command-line. If you’re mixing brands, you’ll have to learn multiple dialects. For a homogenous bench of Dells, it’s one learning curve.

The virtual USB drive is time-shared. PiKVM has one virtual USB, shared across all ports. Once you attach it to port 0, you can’t attach it to port 1 until you switch back and unmount it. This is why the imaging loop stalls out during the DISM phase on each port — the USB is wedged to that one port until the target reboots off its own disk. After that you’re free to switch around.

Secure Boot + Setup Passwords = friction. If you’ve set a BIOS setup password on a Dell, disabling Secure Boot (required for certain PXE configurations) needs that password appended to every cctk --SecureBoot=Disabled --ValSetupPwd=<pwd> call. Not a huge deal, but one more variable to track.

Lessons

  • Automate the command-line interface, not the GUI. BIOS isn’t meant to be clicked through repeatedly. Use cctk; it’s scriptable and repeatable. The F2 GUI is a fallback for troubleshooting.
  • KVM-over-IP is worth the real estate on your network rack. Once you’ve had it, dragging a monitor around feels barbaric. This post on bench hardware goes deeper on what makes a good lab machine.
  • Snapshots + vision is better than blind automation. PiKVM snapshots (or OCR for text-mode screens) let you verify state before you act. Never chain key/type commands blind through an unfamiliar menu.
  • Wake-on-LAN + AC power recovery makes remote benches survivable. You can cut power and the boxes come back on. Circuit breaker flickers? They recover. No more “I’ll just turn it on manually next time.”
  • The bench is a playground, not a data center. I’m not running redundant PiKVM or keeping perfect backups of the BIOS settings. But I am versioning the imaging scripts and the cctk recipe in a repo, so I can recreate the state on any Dell in under an hour.

What’s next

Once I’ve worked out full-batch imaging (integrating WMI polling to detect OOBE completion and auto-advance), I want to pivot the bench to network-boot-only: partition the local disks, PXE everything, and treat the physical boxes as thin clients for the actual workload (which lives in k3s). That’s a longer project, but the PiKVM + cctk foundation is already there to support it.

Running benches without a homelab? You can still apply this pattern with cloud VMs and systems like out-of-band management on used servers. But if you don’t have physical access or a lab network, consider a single-board KVM and a used IPMI card as a stepping stone before scaling. The exact tooling changes, but the principle — “automate the firmware, not the menus” — holds everywhere. DigitalOcean’s Kubernetes service also lets you spin up bare-metal machines with API-driven provisioning if you want to sidestep the bench entirely.