Skip to content
WindowsDeep Dive Published Updated 8 min readViews unavailable

Windows ReplaceFileW: Metadata Preservation and Failure-State Recovery

Stage and validate a replacement, preserve the intended Windows file metadata, and reconcile ReplaceFileW failures without assuming a universal transaction.

Replacing a configuration file, index, or generated artifact has two distinct phases: produce and validate the new bytes, then change which file is reachable through the published name. Writing directly to the published path mixes those phases. A reader can encounter incomplete data, and a failed write can destroy the last usable version.

Win32 ReplaceFileW is a specialized operation for replacing an existing file, optionally retaining the old file at a backup name. It combines the replacement steps and has metadata-merging behavior that a plain copy does not express. Its failure contract is more important than the fact that it is one API call: some documented errors can leave names or metadata in intermediate states. Treat the call as a file-system operation that needs recovery logic, not as a database transaction or a universal crash-atomicity guarantee.

Choose the replacement API for the required contract

Use ReplaceFileW when the destination already exists and the application wants Windows to merge selected metadata from that destination into the replacement. If the destination does not exist, handle first publication as a separate create-new operation; ReplaceFileW is not an open-or-create API.

MoveFileExW is a different primitive. MOVEFILE_REPLACE_EXISTING requests replacement of an existing destination subject to ACL requirements, but it does not document the same metadata-merging list as ReplaceFileW. More importantly, MOVEFILE_COPY_ALLOWED can simulate a cross-volume move with a copy followed by a delete. If the copy succeeds and deleting the source fails, MoveFileExW can still report success while leaving the source in place. That is not an atomic rename and should not be used when the invariant is “either the old published name or the new one, never a copy/delete transition.” Across volumes, MoveFileExW also assigns the destination directory’s default security descriptor instead of carrying the source descriptor.

Do not choose between these functions by name alone. Write down whether the operation must preserve the destination’s DACL or named streams, whether a backup is needed, whether all paths can remain on one volume, and how a partial failure will be detected and repaired.

Stage a complete file on the destination volume

The replaced file, replacement file, and optional backup must all reside on the same volume. A practical design creates a uniquely named staging file in a protected application directory on the destination volume. Use create-new semantics so a stale stage file cannot be silently truncated, restrict write access to the publisher, and keep untrusted users from swapping path components between validation and replacement.

Write the complete payload, close every handle to the stage file, and validate the closed artifact before publication. Validation may include the expected length, a cryptographic digest, a signature, a parser-level check, or application-specific invariants. The name of a stage file is not proof that its contents are complete or trustworthy. Also close readers or writers whose share modes would prevent the replacement operation from opening or renaming either file. Windows sharing compatibility is evaluated across existing handles; a handle that omitted FILE_SHARE_DELETE can block a later delete-or-rename request even when the caller has ordinary write access.

If the application’s durability policy requires flushing the staged file before publication, call FlushFileBuffers on its open handle with GENERIC_WRITE access and check the result before closing it. That API flushes buffered information for the specified file. It is separate from namespace publication: ReplaceFileW defines no supported write-through flag (REPLACEFILE_WRITE_THROUGH is documented as unsupported), so a successful replacement call alone should not be reported as a complete power-loss transaction. Define and test the storage and recovery policy separately.

Understand which metadata is merged

On success, ReplaceFileW preserves documented properties from the old destination, including its creation time, short name, object identifier, DACL, security resource attributes, encryption, compression, and named streams that are not already present in the replacement. The replacement’s data is not a request to preserve every old property: for example, if the replacement is encrypted and the old destination is not, the result is not encrypted. Security resource attributes are not preserved on Windows versions before Windows 8 and Windows Server 2012.

The default flags are deliberate when preserving these properties matters. REPLACEFILE_IGNORE_MERGE_ERRORS can let the call succeed despite attribute or ACL merge failures; REPLACEFILE_IGNORE_ACL_ERRORS can let it succeed without preserving ACLs when the caller lacks WRITE_DAC. Do not use either flag to turn a policy failure into a green deployment. REPLACEFILE_WRITE_THROUGH is not supported. Keep lpExclude and lpReserved null because Microsoft reserves them for future use.

File identity needs careful wording. The destination path takes the replacement, but the resulting file has the file ID of the replacement file, not the file ID of the file it replaced. A monitor that correlates events by handle identity should reopen the destination and query it after publication rather than assuming the old object’s ID persists. File IDs are file-system scoped identifiers, not durable application version numbers.

Call the API and retain the exact error

The helper below expects three application-owned paths: an existing destination, a completed staging file, and a unique backup path. The caller must arrange for all three to be on the same volume. It uses zero flags so merge failures are not explicitly ignored, returns the captured Win32 error, and leaves path reconciliation to the caller rather than deleting files speculatively.

#include <windows.h>
#include <string>

DWORD replace_staged_file(const std::wstring& destination,
                          const std::wstring& staged,
                          const std::wstring& backup)
{
    if (destination.empty() || staged.empty() || backup.empty())
        return ERROR_INVALID_PARAMETER;

    if (ReplaceFileW(destination.c_str(), staged.c_str(), backup.c_str(),
                     0, nullptr, nullptr))
        return ERROR_SUCCESS;

    // GetLastError must be captured before logging or making another API call.
    return GetLastError();
}

The backup name should be unique to this publication attempt and should not be exposed to untrusted writers. The wrapper does not prove that paths are on the same volume, that the staging bytes passed validation, or that the caller has the necessary access. It also does not make the call safe to retry automatically. Those checks belong to the surrounding operation and its recovery record.

Treat failure as a state to inspect, not a no-op

After a FALSE result, capture GetLastError immediately. Microsoft documents several errors with different path outcomes:

Error Documented state to account for
ERROR_UNABLE_TO_REMOVE_REPLACED (1175) The replaced and replacement files retain their original names.
ERROR_UNABLE_TO_MOVE_REPLACEMENT (1176) With a backup path, the replaced and replacement files retain their original names. Without one, the replaced file no longer exists and the replacement retains its original name.
ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 (1177) The replacement remains at its original name but has inherited streams and attributes; the old destination is at a different name, which is the backup name when one was supplied.
Other errors The original names are retained, but there is no backup and Microsoft does not guarantee that the replacement inherited all old attributes and streams.

Therefore, do not interpret every failure as “nothing changed,” delete the stage unconditionally, or blindly retry against the same names. Record the error and operation identifier, inspect only paths the application owns, compare expected content and file identities, and reconcile to one explicit state: old version active, new version active, or recovery required. A backup path improves the documented recovery states, but it is not a substitute for verifying its existence and contents after the call.

Permissions are a separate part of the contract. The caller needs write access to the file being replaced, and rename/delete operations require either delete permission on the file or delete-child permission on its parent. The replacement file is opened with no sharing mode while ReplaceFileW works with it. If another component keeps a conflicting handle open, report the access or sharing error and coordinate a retry; do not weaken ACLs or terminate unrelated processes as an automatic fallback.

Validate the whole publication workflow

Exercise this workflow only in a disposable directory first. Cover at least these cases:

  1. Replace an existing file with and without a backup; verify the destination’s bytes, the backup’s bytes, and the expected metadata after success.
  2. In a disposable test, attempt a cross-volume replacement, record the returned error, and inspect the destination, stage, and backup paths; do not silently switch to copy-and-delete.
  3. Open the destination with a handle that denies delete sharing, then confirm the publisher reports and records the sharing failure without removing its stage.
  4. Test a caller that cannot preserve the destination ACL. Confirm that default flags fail rather than allowing an unreviewed ACL downgrade, and verify that ignore-merge flags are absent from the production call.
  5. Inject failures at write, validation, replacement, and post-replacement verification boundaries. On every error path, inventory the destination, stage, and backup before cleanup.

Record Windows version, file system, local versus SMB location, caller identity, relevant ACLs, and the exact Win32 error. Microsoft documents that the replaced, replacement, and optional backup files must share one volume, but the ReplaceFileW reference does not enumerate file-system or SMB-provider support. Treat provider, server, and version behavior as an explicit deployment-matrix question rather than assuming the local NTFS result generalizes. Do not infer crash consistency from a successful API return.

ReplaceFileW is useful when its metadata-merging behavior matches the application’s policy and the destination already exists. Safe use still depends on a complete same-volume stage, compatible sharing and ACLs, a retained error code, and explicit reconciliation of the documented failure states.

Related:

Sources:

Comments