Overcoming Dataverse FileType Limitations with Persistence & Debugging
Managing files in Microsoft Dataverse can be tricky, especially when dealing with FileType columns. Unlike traditional attachments, FileType columns cannot be migrated using standard tools, making automation a challenge.
I recently worked on an automation process that required retrieving files from SharePoint and uploading them into Dataverse. What seemed like a straightforward task turned into a deep debugging exercise due to the way Dataverse handles FileType columns.
The Challenge
Dataverse supports two types of file storage:
- Notes Attachments (annotation) – Can be migrated with tools like Power Automate and KingswaySoft.
- File Columns (FileType) – Not supported by most migration tools and require API-based uploads.
At first, my automation failed repeatedly, even though I was following best practices. It took a lot of testing and API debugging to understand that Dataverse FileType columns require a very specific upload process, which standard ETL tools don’t support.
After spending time troubleshooting, I identified the right approach:
- Query SharePoint for the correct file paths
- Download files locally before processing
- Read files as binary data (ensuring correct encoding)
- Upload files to Dataverse via Web API with the right headers and structure
This wasn’t a simple fix—it required digging into API responses, testing different content types, and adjusting request formats.
The Solution: PowerShell + SharePoint + Dataverse API
After multiple iterations, I finalized a working solution that automates the process from start to finish. Here’s a simplified version of the approach (with dummy data).
# Authenticate with Dataverse
$conn = Connect-CrmOnlineDiscovery -InteractiveMode
$token = $conn.CurrentAccessToken
$SiteUrl = "https://example.crm.dynamics.com"
# Fetch Matching Files from SharePoint
$fetchXml = @"
<fetch mapping="logical">
<entity name="sharepointdocumentlocation">
<attribute name="relativeurl" />
<link-entity name="custom_table"
from="recordid"
to="regardingobjectid"
link-type="inner"
alias="record">
<attribute name="record_name" />
<attribute name="record_number" />
<attribute name="language" />
</link-entity>
</entity>
</fetch>
"@
$fetchXmlEncoded = [System.Web.HttpUtility]::UrlEncode($fetchXml)
$apiUrl = "$SiteUrl/api/data/v9.2/sharepointdocumentlocations?fetchXml=$fetchXmlEncoded"
$headers = @{
"Authorization" = "Bearer $token"
"Accept" = "application/json"
"OData-Version" = "4.0"
}
$response = Invoke-RestMethod -Uri $apiUrl -Method GET -Headers $headers
$Results = $response.value
# Connect to SharePoint
Connect-PnPOnline -Url "https://example.sharepoint.com/sites/documents" -UseWebLogin
# Define Download Path
$DownloadPath = "C:\temp\Documents"
if (!(Test-Path $DownloadPath)) {
New-Item -ItemType Directory -Path $DownloadPath -Force
}
foreach ($item in $Results) {
$relativeUrl = $item.relativeurl
$recordName = $item."record.record_name"
$recordNumber = $item."record.record_number"
$languageCode = $item."record.language"
$isFrench = $languageCode -eq "fr"
$folderPath = "/sites/documents/folder/$relativeUrl"
if ($isFrench) {
$file1 = "$folderPath/${recordName}_${recordNumber}_Document_FR.pdf"
$file2 = "$folderPath/${recordName}_${recordNumber}_Supplement_FR.pdf"
} else {
$file1 = "$folderPath/${recordName}_${recordNumber}_Document_EN.pdf"
$file2 = "$folderPath/${recordName}_${recordNumber}_Supplement_EN.pdf"
}
# Download Files
$localFile1 = "$DownloadPath\${recordName}_${recordNumber}_Document.pdf"
$localFile2 = "$DownloadPath\${recordName}_${recordNumber}_Supplement.pdf"
try {
Get-PnPFile -Url $file1 -Path $DownloadPath -AsFile -Force
} catch { Write-Host "File not found: $file1" }
try {
Get-PnPFile -Url $file2 -Path $DownloadPath -AsFile -Force
} catch { Write-Host "File not found: $file2" }
# Upload to Dataverse
if (Test-Path $localFile1) {
$fileBytes1 = [System.IO.File]::ReadAllBytes($localFile1)
$FileUrl1 = "$SiteUrl/api/data/v9.2/custom_table($recordNumber)/document_file/$value"
$uploadHeaders = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/octet-stream"
"x-ms-file-name" = "${recordName}_${recordNumber}_Document.pdf"
}
try {
Invoke-RestMethod -Uri $FileUrl1 -Method PUT -Headers $uploadHeaders -Body $fileBytes1
Write-Host "Uploaded Document File"
} catch {
Write-Host "Error uploading Document File: $_"
}
}
if (Test-Path $localFile2) {
$fileBytes2 = [System.IO.File]::ReadAllBytes($localFile2)
$FileUrl2 = "$SiteUrl/api/data/v9.2/custom_table($recordNumber)/supplement_file/$value"
$uploadHeaders = @{
"Authorization" = "Bearer $token"
"Content-Type" = "application/octet-stream"
"x-ms-file-name" = "${recordName}_${recordNumber}_Supplement.pdf"
}
try {
Invoke-RestMethod -Uri $FileUrl2 -Method PUT -Headers $uploadHeaders -Body $fileBytes2
Write-Host "Uploaded Supplement File"
} catch {
Write-Host "Error uploading Supplement File: $_"
}
}
}
The Impact
This automation completely eliminated manual work, ensuring that files are automatically pulled from SharePoint and stored correctly in Dataverse. It also resolved major API-related issues that were blocking the standard migration process.
This experience also reinforced that Dataverse FileType columns require specialized handling—something that often misunderstood.
Comments