Move a file using the CreateCopyJobs APIs
First step is to send post request with payload about the file to the CreateCopyJobs endpoint i.e. /_api/site/CreateCopyJobs (POST)
https://my_tenant.sharepoint.com/sites/my_site/_api/site/CreateCopyJobs
Here is how my post request payload looks like
// url: https://my_tenant.sharepoint.com/sites/my_site/_api/site/CreateCopyJobs
// headers: Authentication: "Bearer your_access_token"
// body (json):
{
"exportObjectUris":[
"https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/Test.docx"
],
"destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
"options":{
"IgnoreVersionHistory":true,
"IsMoveMode":true
}
}
Here is the response we get from the HTTP call
{
"d":{
"CreateCopyJobs":{
"__metadata":{
"type":"Collection(SP.CopyMigrationInfo)"
},
"results":[
{
"EncryptionKey":"JjjU0CBBg3NmTI6SqxcV/zlSgYStNF4KeR+0MLVUtCw=",
"JobId":"bdc524a5-7277-4699-9af3-64a06f5fb2ed",
"JobQueueUri":"https://sposn1bn1m021pr.queue.core.windows.net:443/1247pq20181029-6c611e06b4ae499d9e59e3590b193ba2?sv=2017-07-29&sig=KnsThUylL671QsV%2F8lh10LtsApEBdcli80b0%2FDyKZao%3D&st=2018-10-28T07%3A00%3A00Z&se=2018-11-19T07%3A00%3A00Z&sp=rap",
"SourceListItemUniqueIds":{
"__metadata":{
"type":"Collection(Edm.Guid)"
},
"results":[
]
}
}
]
}
}
}
Once the request is sent, it goes in a queue and we can check the status of the queue by calling another SharePoint endpoint i.e. /_api/site/GetCopyJobProgress (GET).
https://my_tenant.sharepoint.com/sites/my_site/_api/site/GetCopyJobProgress
So, we have to construct a request so the /_api/site/GetCopyJobProgress to call the right Azure queue instance using the response data from the CreateCopyJobs APIs above. Here is the JSON payload for the /_api/site/GetCopyJobProgress GET request. We can see how the data is mapped comparing the above response data with the below request payload:
{
"copyJobInfo":{
"EncryptionKey":"JjjU0CBBg3NmTI6SqxcV/zlSgYStNF4KeR+0MLVUtCw=",
"JobId":"bdc524a5-7277-4699-9af3-64a06f5fb2ed",
"JobQueueUri":"https://sposn1bn1m021pr.queue.core.windows.net:443/1247pq20181029-6c611e06b4ae499d9e59e3590b193ba2?sv=2017-07-29&sig=KnsThUylL671QsV%2F8lh10LtsApEBdcli80b0%2FDyKZao%3D&st=2018-10-28T07%3A00%3A00Z&se=2018-11-19T07%3A00%3A00Z&sp=rap",
"SourceListItemUniqueIds":{
"__metadata":{
"type":"Collection(Edm.Guid)"
},
"results":[
]
}
}
}
From the payload is very obvious that my copy job was hosted to a server where the JobQueueUri is the server, then I have the Id or the job JobId and also a key EncryptionKey to be able to authenticate and check the job status.
GetCopyJobProgress API - Different statuses and responses
So, we have to poll the GetCopyJobProgress API until we get job status success, but here is first the response you'd expect when the job is in progress.
Job in the queue, the "JobState" is 4
From my observation when you get "JobState" = 4, then it is still in the queue and in progress.
{
"d":{
"GetCopyJobProgress":{
"__metadata":{
"type":"SP.CopyJobProgress"
},
"JobState":4,
"Logs":{
"__metadata":{
"type":"Collection(Edm.String)"
},
"results":[
]
}
}
}
}
Note, pay attention on the results node, sometimes there is a log of actions happened and that information might be of interest to you.
Job success, the "JobState" is 0, but should not have any errors in the results node of the response
{
"d":{
"GetCopyJobProgress":{
"__metadata":{
"type":"SP.CopyJobProgress"
},
"JobState":0,
"Logs":{
"__metadata":{
"type":"Collection(Edm.String)"
},
"results":[
...
"{\r\n \"Event\": \"JobWarning\",\r\n \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n \"Time\": \"10/29/2018 22:15:41.652\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"Import\",\r\n \"ObjectType\": \"ListItem\",\r\n \"Url\": \"Shared Documents/Test2.docx\",\r\n \"Id\": \"eb14999e-0e81-4726-be44-e9eb51ef0562\",\r\n \"SourceListItemIntId\": \"255\",\r\n \"TargetListItemIntId\": \"255\",\r\n \"Message\": \"Field HasCar cannot be found. Field value will not be imported\",\r\n \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
"{\r\n \"Event\": \"JobEnd\",\r\n \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n \"Time\": \"10/29/2018 22:15:42.970\",\r\n \"FilesCreated\": \"3\",\r\n \"BytesProcessed\": \"63874\",\r\n \"ObjectsProcessed\": \"2\",\r\n \"TotalExpectedSPObjects\": \"2\",\r\n \"TotalErrors\": \"0\",\r\n \"TotalWarnings\": \"18\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"Import\",\r\n \"WaitTimeOnSqlThrottlingMilliseconds\": \"0\",\r\n \"CreatedOrUpdatedFileStatsBySize\": \"{\\\"10-100K\\\":{\\\"Count\\\":3,\\\"TotalSize\\\":63874,\\\"TotalDownloadTime\\\":0,\\\"TotalCreationTime\\\":2986}}\",\r\n \"ObjectsStatsByType\": \"{\\\"SPUser\\\":{\\\"Count\\\":1,\\\"TotalTime\\\":0,\\\"AccumulatedVersions\\\":0,\\\"ObjectsWithVersions\\\":0},\\\"SPFile\\\":{\\\"Count\\\":1,\\\"TotalTime\\\":3015,\\\"AccumulatedVersions\\\":3,\\\"ObjectsWithVersions\\\":1},\\\"SPListItem\\\":{\\\"Count\\\":1,\\\"TotalTime\\\":5532,\\\"AccumulatedVersions\\\":3,\\\"ObjectsWithVersions\\\":1}}\",\r\n \"TotalExpectedBytes\": \"63874\",\r\n \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
"{\r\n \"Event\": \"JobFinishedObjectInfo\",\r\n \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n \"Time\": \"10/29/2018 22:15:42.970\",\r\n \"SourceObjectFullUrl\": \"https://tenant1.sharepoint.com/sites/site1/Shared Documents/Test.docx\",\r\n \"TargetServerUrl\": \"https://tenant1.sharepoint.com\",\r\n \"TargetSiteId\": \"45fdccd8-47f3-4870-bcb0-7aaabfcd52f9\",\r\n \"TargetWebId\": \"d6d96969-217f-4306-b15b-fe35b6b754cc\",\r\n \"TargetListId\": \"c1880f57-266c-47cd-b5ae-cc111ca2e8ce\",\r\n \"TargetObjectUniqueId\": \"a20fe3a1-9201-427b-9eb4-9b2c03c5710a\",\r\n \"TargetObjectSiteRelativeUrl\": \"Shared Documents/Test2.docx\",\r\n \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
"{\r\n \"Event\": \"JobStart\",\r\n \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n \"Time\": \"10/29/2018 22:15:42.996\",\r\n \"SiteId\": \"692102df-335d-41e2-aa44-425b626037ea\",\r\n \"WebId\": \"f7fb12c3-ca68-4060-b1b0-c27a6bfffeb2\",\r\n \"DBId\": \"eb30ff26-a12c-431e-bb10-68fdac21ce28\",\r\n \"FarmId\": \"cd8ae8dc-97d0-4042-8197-64044cfecae0\",\r\n \"ServerId\": \"f7e14548-d5a5-4108-a544-2f667ea1f7fc\",\r\n \"SubscriptionId\": \"ea1787c6-7ce2-4e71-be47-5e0deb30f9e4\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"MoveCleanup\",\r\n \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}",
"{\r\n \"Event\": \"JobEnd\",\r\n \"JobId\": \"9e0e880b-4425-4107-ae8d-5d0fcf298aa2\",\r\n \"Time\": \"10/29/2018 22:15:43.012\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"MoveCleanup\",\r\n \"CorrelationId\": \"24419d9e-c090-7000-f596-3288e6054183\"\r\n}"
]
}
}
}
}
Job done with errors, the "JobState" is still 0, but there is a JobError in the results node of the response
Here is the response data and the "JobState" is still 0, but we have to pay attention on the results log where we have errors. So, "JobState" equals 0, does not mean successfully completed.
{
"d":{
"GetCopyJobProgress":{
"__metadata":{
"type":"SP.CopyJobProgress"
},
"JobState":0,
"Logs":{
"__metadata":{
"type":"Collection(Edm.String)"
},
"results":[
"{\r\n \"Event\": \"JobStart\",\r\n \"JobId\": \"bdc524a5-7277-4699-9af3-64a06f5fb2ed\",\r\n \"Time\": \"10/29/2018 22:12:22.769\",\r\n \"SiteId\": \"692102df-335d-41e2-aa44-425b626037ea\",\r\n \"WebId\": \"f7fb12c3-ca68-4060-b1b0-c27a6bfffeb2\",\r\n \"DBId\": \"eb30ff26-a12c-431e-bb10-68fdac21ce28\",\r\n \"FarmId\": \"cd8ae8dc-97d0-4042-8197-64044cfecae0\",\r\n \"ServerId\": \"c327d744-babf-486e-944d-e2723b999830\",\r\n \"SubscriptionId\": \"ea1787c6-7ce2-4e71-be47-5e0deb30f9e4\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"MoveCleanup\",\r\n \"CorrelationId\": \"f6409d9e-c040-7000-5b4f-0367f426565b\"\r\n}",
"{\r\n \"Event\": \"JobError\",\r\n \"JobId\": \"bdc524a5-7277-4699-9af3-64a06f5fb2ed\",\r\n \"Time\": \"10/29/2018 22:12:22.769\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"MoveCleanup\",\r\n \"ObjectType\": \"File\",\r\n \"Url\": \"Shared Documents/Test.docx\",\r\n \"Id\": \"3e1cdc58-149b-40f3-a705-b1217161e70c\",\r\n \"SourceListItemIntId\": \"255\",\r\n \"ErrorCode\": \"-2147024713\",\r\n \"Message\": \"A file or folder with the name 'Test.docx' already exists at the destination.\",\r\n \"CorrelationId\": \"f6409d9e-c040-7000-5b4f-0367f426565b\"\r\n}",
"{\r\n \"Event\": \"JobEnd\",\r\n \"JobId\": \"bdc524a5-7277-4699-9af3-64a06f5fb2ed\",\r\n \"Time\": \"10/29/2018 22:12:22.769\",\r\n \"TotalRetryCount\": \"0\",\r\n \"MigrationType\": \"Copy\",\r\n \"MigrationDirection\": \"MoveCleanup\",\r\n \"CorrelationId\": \"f6409d9e-c040-7000-5b4f-0367f426565b\"\r\n}"
]
}
}
}
}
Note, we will have to parse the result node to JSON and try find any errors. In this scenario we have the following error: "A file or folder with the name 'Test.docx' already exists at the destination."
Here is the MigrationJobState from the Microsoft.SharePoint.Client DLL:
namespace Microsoft.SharePoint.Client
{
public enum MigrationJobState
{
None = 0,
Queued = 2,
Processing = 4,
}
}
Options that we can specify in a CreateCopyJobs request
I used the SharePoint OData metadata and the CSOM dll to get some more information and following options are available at the moment:
<ComplexType Name="CopyMigrationOptions">
<Property Name="AllowSchemaMismatch" Type="Edm.Boolean" Nullable="false"/>
<Property Name="AllowSmallerVersionLimitOnDestination" Type="Edm.Boolean" Nullable="false"/>
<Property Name="IgnoreVersionHistory" Type="Edm.Boolean" Nullable="false"/>
<Property Name="IsMoveMode" Type="Edm.Boolean" Nullable="false"/>
<Property Name="NameConflictBehavior" Type="Edm.Int32" Nullable="false"/>
</ComplexType>
Here is the enum for NameConflictBehavior pulled from the Microsoft.SharePoint.Client dll:
namespace Microsoft.SharePoint.Client
{
public enum MigrationNameConflictBehavior
{
Fail,
Replace,
Rename,
}
}
AllowSchemaMismatch: allows file move with missing or mismatched field definition on the destination list for source field. More clarification bellow.
IgnoreVersionHistory: ignores version history when moves the file and will create only one version (1.0) on the target location
IsMoveMode: true moves the file, false creates a copy of the file
NameConflictBehavior: 1 = REPLACE and 2 = KEEP BOTH
Copy a file using the CreateCopyJobs API
Copying a file follows the same rules to call CreateCopyJobs endpoint i.e. /_api/site/CreateCopyJobs (POST), but the payload send to the API has set the "IsMoveMode" to false or not specified at all. Here is how my post request payload looks like:
{
"exportObjectUris":[
"https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/Test.docx"
],
"destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
"options":{
"IgnoreVersionHistory":true,
"IsMoveMode":false
}
}
That is followed by polling the /_api/site/GetCopyJobProgress to get the status of the job. The same process as the described above for file move.
Copy or move a folder using the CreateCopyJobs API
The exact same process applies when moving or copying folder, but the request payload differs where we have to specify folder instead of file. Here is an example:
{
"exportObjectUris":[
"https://tenant1.sharepoint.com/sites/site1/Shared%20Documents/TestFolder1"
],
"destinationUri":"https://tenant1.sharepoint.com/sites/site2/Shared%20Documents",
"options":{
"IgnoreVersionHistory":true,
"IsMoveMode":true,
"AllowSchemaMismatch":true
}
}
Important: Folder cannot be moved using MigrationNameConflictBehavior = Replace, but just with MigrationNameConflictBehavior = Rename