Two WIM files can be internally healthy and still represent different deployment decisions. In the reproduced lab for this article, wimlib-imagex verify accepted both Windows Server 2025 Approved and Windows Server 2025 Canary. Only the image name and an extracted release sentinel exposed that the second archive was the wrong intact image.
To verify WIM image integrity before deployment, ask two independent questions: are these bytes structurally consistent, and are they the exact approved release? Run the internal verifier, check a trusted external SHA-256 manifest, inspect indexed metadata, and extract an operator-owned sentinel. A green verifier result without the identity evidence is not a deployment approval.
This guide gives an agency operator or Windows image custodian a disposable Linux-side admission lab. It never mounts, applies, services, boots or publishes a WIM. The source files are read-only inputs; every mutable artifact stays under one marker-owned path.
The current wimverify manual describes a read-only verification process that parses the WIM, checks its integrity table when present, validates image metadata and resource references, and decompresses resources to confirm their checksums. Those are strong corruption checks. They do not know which release your change ticket approved.
This distinction appears in other artifact types. An OCI index can be valid while selecting the wrong platform, which is why operators bind platform identity before a pull. Likewise, a green container test has a narrow boundary: it establishes what the format can prove, not every property of the restored payload.
| Candidate state | Internal verifier | Trusted identity evidence | Admission decision |
|---|---|---|---|
| Approved WIM | pass | manifest, image name and sentinel match | eligible for the next deployment stage |
| Wrong intact WIM | pass | name or sentinel differs | quarantine as wrong release |
| Byte-corrupted copy | fail | not evaluated for deployment | reject as damaged |
| Approved source after lab | unchanged | cleanup receipt present | retain evidence and continue |
An external hash also needs provenance. A SHA-256 value copied from the same untrusted directory as the WIM detects accidental changes after that moment, but does not authenticate the publisher. Obtain the expected hash or signed release record through an independently controlled channel.
Microsoft uses related but not identical controls. Its DISM image-management reference documents /CheckIntegrity and /Verify on Windows imaging operations. This lab uses Debian’s wimtools so the entire source-preserving test is reproducible without a Windows deployment host. Keep the two-question policy whichever toolchain executes it.
Begin with three approved facts before the lab creates an archive: the expected indexed image name, the expected release marker, and the expected hash of an operator-owned sentinel inside the image. In a real pipeline, these values should come from the release record, not from the candidate WIM being judged.
Run all eight tested-input blocks in order in the same Bash process. The first block refuses an existing path, requires every tool and creates a marker before later cleanup can run.
set -Eeuo pipefail
LAB=/var/tmp/voxfor-wim-lab-190
MARKER="$LAB/.voxfor-wim-lab-190"
APPROVED_TREE="$LAB/source-approved"
WRONG_TREE="$LAB/source-wrong"
APPROVED_WIM="$LAB/windows-server-approved.wim"
WRONG_WIM="$LAB/windows-server-wrong.wim"
CORRUPT_WIM="$LAB/windows-server-corrupt-control.wim"
EXTRACT_DIR="$LAB/extracted"
RECEIPT_OUT=/tmp/voxfor-wim-admission-190.txt
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
owned_cleanup() {
if [[ -d "$LAB" ]]; then
[[ "$LAB" == /var/tmp/voxfor-wim-lab-190 ]] || fail "unexpected lab path"
[[ -f "$MARKER" ]] || fail "refusing cleanup without ownership marker"
rm -rf -- "$LAB"
fi
}
trap 'rc=$?; if [[ $rc -ne 0 ]]; then owned_cleanup || true; fi; exit $rc' EXIT
for tool in wimlib-imagex sha256sum install stat dd awk grep cmp; do
command -v "$tool" >/dev/null || fail "missing tool: $tool"
done
[[ ! -e "$LAB" ]] || fail "lab path already exists"
[[ ! -e "$RECEIPT_OUT" ]] || fail "receipt path already exists"
install -d -m 0750 "$LAB"
install -m 0600 /dev/null "$MARKER"
This exact path is disposable. Do not point LAB at a download collection, image repository or broad temporary root. For a real candidate, keep the original outside the lab and copy it into an owned working directory only after the synthetic controls pass.
Our approved fixture contains a release sentinel and a small deployment manifest. Fixed timestamps keep payload identity stable. Its sentinel hash becomes the trusted fixture expectation.
install -d -m 0750 \
"$APPROVED_TREE/Windows/System32" \
"$APPROVED_TREE/ProgramData/Voxfor"
printf '%s\n' \
'release-id=windows-server-2025-approved' \
'channel=production' \
'build=26100.1742' \
> "$APPROVED_TREE/Windows/System32/VoxforBuild.txt"
printf '%s\n' \
'{"image":"windows-server-2025-approved","channel":"production","build":"26100.1742"}' \
> "$APPROVED_TREE/ProgramData/Voxfor/deployment.json"
touch -d '2026-08-16 00:00:00 UTC' \
"$APPROVED_TREE/Windows/System32/VoxforBuild.txt" \
"$APPROVED_TREE/ProgramData/Voxfor/deployment.json"
APPROVED_SENTINEL_SHA="$(
sha256sum "$APPROVED_TREE/Windows/System32/VoxforBuild.txt" | awk '{print $1}'
)"
[[ "$APPROVED_SENTINEL_SHA" == \
ee33309c1d282f8c97afaab49b7611da1314876007f150626ca2c13d65bb1661 ]]
Now build a canary with a different release ID, channel and build. It is deliberately valid. If the gate rejects it only because verification fails, the identity policy has not been tested.
install -d -m 0750 \
"$WRONG_TREE/Windows/System32" \
"$WRONG_TREE/ProgramData/Voxfor"
printf '%s\n' \
'release-id=windows-server-2025-canary' \
'channel=canary' \
'build=26100.1882' \
> "$WRONG_TREE/Windows/System32/VoxforBuild.txt"
printf '%s\n' \
'{"image":"windows-server-2025-canary","channel":"canary","build":"26100.1882"}' \
> "$WRONG_TREE/ProgramData/Voxfor/deployment.json"
touch -d '2026-08-16 00:00:00 UTC' \
"$WRONG_TREE/Windows/System32/VoxforBuild.txt" \
"$WRONG_TREE/ProgramData/Voxfor/deployment.json"
WRONG_SENTINEL_SHA="$(
sha256sum "$WRONG_TREE/Windows/System32/VoxforBuild.txt" | awk '{print $1}'
)"
[[ "$APPROVED_SENTINEL_SHA" != "$WRONG_SENTINEL_SHA" ]]
Capture both archives with no compression so the later byte-flip control can target known fixture text. --check writes an integrity table; --no-acls avoids making this synthetic Linux fixture pretend to preserve Windows ACL semantics.
wimlib-imagex capture "$APPROVED_TREE" "$APPROVED_WIM" \
"Windows Server 2025 Approved" "Production deployment image" \
--compress=none --check --no-acls >"$LAB/capture-approved.log"
wimlib-imagex capture "$WRONG_TREE" "$WRONG_WIM" \
"Windows Server 2025 Canary" "Wrong but intact control image" \
--compress=none --check --no-acls >"$LAB/capture-wrong.log"
APPROVED_WIM_SHA="$(sha256sum "$APPROVED_WIM" | awk '{print $1}')"
WRONG_WIM_SHA="$(sha256sum "$WRONG_WIM" | awk '{print $1}')"
[[ "$APPROVED_WIM_SHA" != "$WRONG_WIM_SHA" ]]
printf '%s %s\n' "$APPROVED_WIM_SHA" "$(basename "$APPROVED_WIM")" \
> "$LAB/approved.sha256"
( cd "$LAB" && sha256sum -c approved.sha256 ) \
> "$LAB/manifest-check.log"
A real artifact manifest should be produced by the release pipeline and checked after transfer. Do not substitute an object-store label without understanding it: an S3 multipart ETag is not automatically file MD5. Compute or verify a cryptographic digest over the actual WIM bytes.
Run the same verifier against both intact archives, then read image index 1. The Debian wimlib-imagex manual covers capture, info, extract and verify in the same tool family.
wimlib-imagex verify "$APPROVED_WIM" > "$LAB/verify-approved.log"
wimlib-imagex verify "$WRONG_WIM" > "$LAB/verify-wrong.log"
wimlib-imagex info "$APPROVED_WIM" 1 > "$LAB/info-approved.txt"
wimlib-imagex info "$WRONG_WIM" 1 > "$LAB/info-wrong.txt"
APPROVED_NAME="$(
awk -F': *' '$1 == "Name" {print $2; exit}' "$LAB/info-approved.txt"
)"
WRONG_NAME="$(
awk -F': *' '$1 == "Name" {print $2; exit}' "$LAB/info-wrong.txt"
)"
[[ "$APPROVED_NAME" == "Windows Server 2025 Approved" ]]
[[ "$WRONG_NAME" == "Windows Server 2025 Canary" ]]
A WIM name is useful metadata, not a cryptographic signature. Pair it with an operator-owned sentinel whose expected hash is stored in the trusted release record. Extract only that file to an isolated directory, and read the same path from the wrong control without applying either image.
install -d -m 0750 "$EXTRACT_DIR"
wimlib-imagex extract "$APPROVED_WIM" 1 \
Windows/System32/VoxforBuild.txt \
--dest-dir="$EXTRACT_DIR" --preserve-dir-structure --no-acls \
> "$LAB/extract-approved.log"
EXTRACTED_SENTINEL="$EXTRACT_DIR/Windows/System32/VoxforBuild.txt"
[[ -f "$EXTRACTED_SENTINEL" ]]
EXTRACTED_SENTINEL_SHA="$(sha256sum "$EXTRACTED_SENTINEL" | awk '{print $1}')"
[[ "$EXTRACTED_SENTINEL_SHA" == "$APPROVED_SENTINEL_SHA" ]]
WRONG_MARKER="$(
wimlib-imagex extract "$WRONG_WIM" 1 \
Windows/System32/VoxforBuild.txt --to-stdout --no-acls 2>/dev/null |
awk -F= '$1 == "release-id" {print $2}'
)"
[[ "$WRONG_MARKER" == windows-server-2025-canary ]]
Store the expected sentinel in a controlled baseline. The same principle applies when you maintain a trusted file-integrity baseline: a digest is meaningful only when its approved state and change owner are known.
For multi-index WIMs, repeat identity checks for the exact index intended by the deployment job. Do not accept “the file contains the right edition somewhere” when automation will apply a numbered index.
A wrong-but-intact WIM proves the identity gate can reject a structurally valid artifact. A different control must prove that the internal verifier rejects damaged bytes. Copy the approved WIM, locate the uncompressed sentinel text, overwrite one byte without truncation, and preserve the file size.
cp --reflink=auto "$APPROVED_WIM" "$CORRUPT_WIM"
ORIGINAL_SIZE="$(stat -c %s "$APPROVED_WIM")"
CORRUPT_OFFSET="$(
LC_ALL=C grep -aob -m1 \
'release-id=windows-server-2025-approved' "$CORRUPT_WIM" |
awk -F: '{print $1}'
)"
[[ "$CORRUPT_OFFSET" =~ ^[0-9]+$ ]]
printf 'X' | dd of="$CORRUPT_WIM" bs=1 seek="$CORRUPT_OFFSET" \
conv=notrunc status=none
[[ "$(stat -c %s "$CORRUPT_WIM")" -eq "$ORIGINAL_SIZE" ]]
set +e
wimlib-imagex verify "$CORRUPT_WIM" >"$LAB/verify-corrupt.log" 2>&1
CORRUPT_VERIFY_EXIT=$?
set -e
[[ "$CORRUPT_VERIFY_EXIT" -ne 0 ]]
grep -Eiq 'failed verification|invalid|corrupt|ERROR' \
"$LAB/verify-corrupt.log"
This byte-location method is deliberately specific to the uncompressed synthetic fixture. It is not a recipe for modifying a compressed production WIM. The only production-safe lesson is to retain the source, corrupt a disposable lab copy when testing the gate, and require a nonzero verifier result.
A format check also has a boundary. The adjacent virtual-disk case must separate virtual-disk format checks from guest integrity. Here, WIM verification does not establish whether capture occurred from a consistent application state.
wimlib-imagex verify actually check?It parses the WIM, validates its metadata and referenced resources, uses the integrity table when present, and decompresses resources to check their checksums. It establishes internal consistency for the bytes examined. It does not know which release your organization approved.
Yes. Both intact fixture WIMs passed in this run, but their indexed names and release sentinels differed. Treat verifier success and approved identity as separate mandatory gates.
WIM integrity tables detect covered internal byte corruption. A trusted external manifest binds the entire received file to a release record and can be checked before WIM parsing. The manifest must come through a controlled channel; a candidate and self-supplied hash can be wrong together.
The wimlib-imagex verify operation is read-only and does not mount the image. The lab writes only separate fixtures, logs, an extracted sentinel and a copied corruption control under its owned path.
Do not silently treat it as equivalent to this lab. The verifier can still inspect metadata, resources and checksums, but the optional whole-file integrity-table stage is absent. Record that reduced coverage, rely on a trusted external hash, and decide whether release policy requires the image to be rebuilt with an integrity table.
No. It does not prove capture-time consistency, signature trust, driver compatibility, storage layout, application state, apply success or boot success. For VM sources, prove snapshot consistency at capture time separately, then test apply and boot on representative isolated infrastructure.
Finish by proving the approved source WIM is unchanged, emitting the decision facts, and removing only the marker-owned lab. The receipt remains outside that directory for the change record.
[[ "$(sha256sum "$APPROVED_WIM" | awk '{print $1}')" == "$APPROVED_WIM_SHA" ]]
[[ "$(stat -c %s "$APPROVED_WIM")" -eq "$ORIGINAL_SIZE" ]]
{
printf 'wimlib_version=%s\n' "$(wimlib-imagex --version | sed -n '1p')"
printf 'approved_name=%s\n' "$APPROVED_NAME"
printf 'approved_verify=pass\n'
printf 'approved_wim_sha256=%s\n' "$APPROVED_WIM_SHA"
printf 'manifest_check=pass\n'
printf 'sentinel_sha256=%s\n' "$EXTRACTED_SENTINEL_SHA"
printf 'wrong_name=%s\n' "$WRONG_NAME"
printf 'wrong_verify=pass\n'
printf 'wrong_marker=%s\n' "$WRONG_MARKER"
printf 'corrupt_control_exit=%s\n' "$CORRUPT_VERIFY_EXIT"
printf 'corrupt_control_result=rejected\n'
printf 'approved_source_unchanged=yes\n'
} | tee "$RECEIPT_OUT"
owned_cleanup
trap - EXIT
[[ ! -e "$LAB" ]]
printf 'cleanup=absent\n' | tee -a "$RECEIPT_OUT"
The representative Debian run produced:
wimlib_version=wimlib-imagex 1.14.4-1.1+b3 (using wimlib 1.14.4-1.1+b3)
approved_name=Windows Server 2025 Approved
approved_verify=pass
approved_wim_sha256=d7d96642428cfac6c4a1ae320126b8018062994ad3341ca8341fe9fb01b0f6fd
manifest_check=pass
sentinel_sha256=ee33309c1d282f8c97afaab49b7611da1314876007f150626ca2c13d65bb1661
wrong_name=Windows Server 2025 Canary
wrong_verify=pass
wrong_marker=windows-server-2025-canary
corrupt_control_exit=13
corrupt_control_result=rejected
approved_source_unchanged=yes
cleanup=absent
Capture metadata can make a newly generated fixture WIM hash differ between runs. The requirement is that the manifest created for one received WIM matches that same file and that the source hash remains unchanged through the decision. The stable fixture sentinel hash and logical outcomes should remain the same.
Admit the WIM to the next stage only when its internal verification passes, the trusted external manifest matches, the intended index name and release sentinel match the approved record, the wrong-but-intact control is rejected by identity policy, the same-size corrupted copy is rejected by the verifier, and the source remains unchanged. This receipt proves artifact admission only; apply, boot and workload validation remain mandatory downstream gates.
If any gate fails, do not apply or service the candidate. Preserve the original outside the disposable scope, quarantine it under a new non-executable name, retain the failed receipt and verifier log, and remove only /var/tmp/voxfor-wim-lab-190 after confirming .voxfor-wim-lab-190 exists. Never broaden cleanup to a parent directory or overwrite the failed candidate with a replacement.
The useful decision is precise: these exact bytes are internally sound and match this approved release identity. If either half is missing, the WIM remains quarantined.