SharePoint 2013 On-Premises to SharePoint Online Migration via C# CSOM: Fetching Historical File Versions via URL/REST returns 404 / Bad Request

1 day ago 1
ARTICLE AD BOX

Scenario:
I am building a custom migration tool in C# using the SharePoint 2013 Client-Side Object Model (CSOM v15 SDK) to migrate a document library from an on-premises SharePoint 2013 farm to a modern SharePoint Online environment.

Requirements:

Migrate all documents along with their full chronological version history (e.g., v1.0, v2.0, v3.0).

Migrate all associated custom metadata properties (column values) mapped sequentially to each version iteration.

The utility is executed from a local machine/migration workstation authenticated via on-premises network credentials (NTLM/Windows Auth).


The Problem:

While migrating the current/latest version of files works perfectly using File.OpenBinaryDirect(), downloading historical versions fails continuously. When iterating through ListItem.File.Versions, the FileVersion.Url returns a relative virtual layout path (e.g., _vti_history/512/Folder/Document.docx). No matter what .NET protocol or web request architecture is utilized, fetching the byte stream of these historical records consistently returns an HTTP 404 Not Found, a 400 Bad Request, or authentication/compilation failures.


What We Have Tried (and why they failed):

Approach 1: ListItem.Versions (CSOM standard)

Result: Threw The field or property 'versions' does not exist.

Reason: Discovered that ListItem.Versions is exclusive to the v16 cloud SDK and doesn't exist on the SharePoint 2013 (v15) ListItem object. We must use File.Versions.

Approach 2: SP.File.OpenBinaryDirect with _vti_history URL

Result: Threw Specified argument was out of the range of valid values. Parameter name: serverRelativeUrl.

Reason: OpenBinaryDirect strictly expects an active, modern server-relative path and completely rejects the virtual structure of _vti_history URLs.

Approach 3: SharePoint 2013 REST API with File GUID/Item ID
We attempted targeting the direct file stream via endpoints such as:
{site-url}/_api/web/GetFileById('{guid}')/versions({id})/$value and {site-url}/_api/web/lists(guid'{list-id}')/items({id})/versions({id})/$value

Result: Failed with 400 Bad Request or 404 Not Found.

Reason: Discovered that the /$value binary extraction endpoint on historical versions was not fully supported/exposed in early on-premises SharePoint 2013 REST engine rollouts.

Approach 4: HttpWebRequest & HttpClient via modern .NET with NTLM credentials
We constructed absolute URLs to the virtual _vti_history endpoints (e.g., https://myfarm.com).

Result: Consistently returns 404 Not Found or 401 Unauthorized.

Reason: The IIS pipeline or authentication handler appears to drop the Windows/NTLM authentication context when an external client console maps directly to isolated SharePoint internal system folders like _vti_history. We also ran into framework compilation exceptions attempting to use legacy hooks like UnsafeAuthenticatedConnectionSharing because our console app runs on a modern .NET environment where WebRequestHandler is deprecated.


Current Blockers / Constraints:

The source file system paths contain special characters, application-specific file extensions (such as .aspx layout reference templates), and deep folder structures.

Third-party migration software or the official Microsoft SPMT tool cannot be utilized due to internal infrastructure restrictions and data governance policies. We must achieve this via a custom C# solution.

Question:

How can an external C# application safely authenticate and extract the raw binary stream of historical document versions from a SharePoint 2013 On-Premises environment using the v15 CSOM SDK or standard web endpoints without running into virtual folder 404 Not Found barriers? Is there a legacy SOAP web service configuration (lists.asmx) or an alternative stream provider that natively supports this setup?

Any code samples, guidance, or pointer to missing dependencies would be highly appreciated.


Read Entire Article