Ret Oprettet af og Ændret af med validateUpdateListItem
Author og Editor er skrivebeskyttede i REST-API’et, men ikke i formularens valideringskald. Sådan sættes de fra Power Automate, PnP PowerShell og JavaScript.
Oprettet af (Author) og Ændret af (Editor) sætter SharePoint selv, og et almindeligt MERGE-kald mod dem fejler med “The field ‘Author’ is read-only”. Det er et problem efter en migrering, hvor alt står som oprettet af migreringskontoen, og i flows, hvor en servicekonto opretter elementer på vegne af en bruger. validateUpdateListItem er det kald, SharePoints egne formularer bruger, og det må godt sætte de to felter.
POST _api/web/lists/getByTitle('[ListName]')/items([ItemID])/validateUpdateListItem
Fra Power Automate#
Send an HTTP request to SharePoint, metode POST:
_api/web/lists/getByTitle('Site Pages')/items(@{triggerOutputs()?['body/ID']})/validateUpdateListItem
1{
2 "Accept": "application/json;odata=nometadata",
3 "Content-Type": "application/json;odata=nometadata"
4}
1{
2 "formValues": [
3 {
4 "FieldName": "Author",
5 "FieldValue": "[{'Key':'i:0#.f|membership|user@domain.com'}]"
6 },
7 {
8 "FieldName": "Editor",
9 "FieldValue": "[{'Key':'i:0#.f|membership|user@domain.com'}]"
10 }
11 ],
12 "bNewDocumentUpdate": true
13}
formValueser en liste af felter, hver med det interne navn og en værdi.FieldValueer altid en tekst. For et personfelt er teksten et JSON-array med brugerens claim somKey.bNewDocumentUpdate: trueretter den nuværende version i stedet for at lave en ny.
Et helt flow, der sætter forfatteren på et element, som en automatisering har oprettet:
1. Trigger: When an item is created (SharePoint)
2. Action: Get user profile (Office 365 Users - Get user profile)
3. Action: Send HTTP request (Update Author/Editor)
- Uri: _api/web/lists/getByTitle('Documents')/items(@{triggerOutputs()?['body/ID']})/validateUpdateListItem
- Body:
{
"formValues": [
{
"FieldName": "Author",
"FieldValue": "[{'Key':'i:0#.f|membership|@{outputs('Get_user_profile')?['body/mail']}'}]"
}
],
"bNewDocumentUpdate": true
}
Handlingen henter selv en request digest, så det er den og ikke den generiske HTTP-handling, der skal bruges.
Fra PnP PowerShell#
1# Connect to SharePoint
2Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/sitename" -Interactive
3
4# Get the list item
5$item = Get-PnPListItem -List "Documents" -Id 123
6
7# Get target user
8$user = Get-PnPUser | Where-Object { $_.Email -eq "newauthor@domain.com" }
9
10# Prepare REST API call
11$endpoint = "_api/web/lists/getByTitle('Documents')/items($($item.Id))/validateUpdateListItem"
12$body = @"
13{
14 "formValues": [
15 {
16 "FieldName": "Author",
17 "FieldValue": "[{'Key':'$($user.LoginName)'}]"
18 },
19 {
20 "FieldName": "Editor",
21 "FieldValue": "[{'Key':'$($user.LoginName)'}]"
22 }
23 ],
24 "bNewDocumentUpdate": true
25}
26"@
27
28# Execute REST call
29Invoke-PnPSPRestMethod -Method Post -Url $endpoint -Content $body
30
31Write-Host "Author and Editor updated successfully" -ForegroundColor Green
Get-PnPUser finder kun brugere, der allerede er på webstedet. LoginName er claimet i det format, kaldet vil have.
Fra JavaScript på en side#
1function updateAuthorEditor(listName, itemId, userLoginName) {
2 const siteUrl = _spPageContextInfo.webAbsoluteUrl;
3 const endpoint = `${siteUrl}/_api/web/lists/getByTitle('${listName}')/items(${itemId})/validateUpdateListItem`;
4
5 const body = {
6 formValues: [
7 {
8 FieldName: "Author",
9 FieldValue: `[{'Key':'${userLoginName}'}]`
10 },
11 {
12 FieldName: "Editor",
13 FieldValue: `[{'Key':'${userLoginName}'}]`
14 }
15 ],
16 bNewDocumentUpdate: true
17 };
18
19 return fetch(endpoint, {
20 method: 'POST',
21 headers: {
22 'Accept': 'application/json;odata=nometadata',
23 'Content-Type': 'application/json;odata=nometadata',
24 'X-RequestDigest': document.getElementById('__REQUESTDIGEST').value
25 },
26 body: JSON.stringify(body)
27 })
28 .then(response => response.json())
29 .then(data => {
30 console.log('Author/Editor updated successfully');
31 return data;
32 })
33 .catch(error => {
34 console.error('Update failed:', error);
35 throw error;
36 });
37}
38
39// Usage
40const userClaim = "i:0#.f|membership|user@domain.com";
41updateAuthorEditor("Documents", 123, userClaim);
__REQUESTDIGEST findes på klassiske sider. Ellers hentes en digest fra _api/contextinfo:
1// Get fresh request digest
2fetch(siteUrl + '/_api/contextinfo', {
3 method: 'POST',
4 headers: {
5 'Accept': 'application/json;odata=nometadata'
6 }
7})
8.then(response => response.json())
9.then(data => {
10 const digest = data.FormDigestValue;
11 // Use digest in subsequent calls
12});
Claims#
En bruger i SharePoint Online:
i:0#.f|membership|user@domain.com
i:0#.f er præfikset, membership er udbyderen, og resten er brugerens UPN. On-premises med Windows-login er det i:0#.w|domain\username. En Entra-sikkerhedsgruppe er c:0t.c|tenant|groupid, men en gruppe kan ikke stå som forfatter, og en SharePoint-gruppe kan slet ikke bruges her.
Claimet findes på tre måder. Med ensureUser, som også lægger brugeren på webstedet:
1. Action: Get user profile (Office 365 Users)
2. Action: Send HTTP request to SharePoint
- Uri: _api/web/ensureUser
- Method: POST
- Body: {'logonName': '@{outputs('Get_user_profile')?['body/mail']}'}
3. Output: @{body('EnsureUser')?['LoginName']}
Med PnP PowerShell:
1Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/site" -Interactive
2$user = Get-PnPUser | Where-Object { $_.Email -eq "user@domain.com" }
3$user.LoginName
Eller i browserens konsol på en side på webstedet, for den bruger, der er logget ind:
1_spPageContextInfo.userLoginName
UPN og e-mail er ikke altid det samme. Claimet bygges af UPN’en, så ensureUser er den sikre vej, når der kun er en e-mail.
Brug#
Efter en migrering. Den oprindelige forfatter ligger typisk i et metadatafelt fra migreringen:
1. Trigger: Manually trigger a flow
2. Action: Get items (Documents library)
- Filter Query: MigrationSource ne null
3. Apply to each: For each migrated document
a. Parse JSON: Extract original author email from MigrationMetadata field
b. HTTP Request: EnsureUser with original author email
c. HTTP Request: validateUpdateListItem with original author claim
d. Update item: Clear MigrationSource flag
I stedet for servicekontoen. Brugerens e-mail kommer fra formularen, triggeren eller et opslag:
1{
2 "formValues": [
3 {
4 "FieldName": "Author",
5 "FieldValue": "[{'Key':'i:0#.f|membership|@{triggerBody()?['RequestorEmail']}'}]"
6 },
7 {
8 "FieldName": "Editor",
9 "FieldValue": "[{'Key':'i:0#.f|membership|@{triggerBody()?['RequestorEmail']}'}]"
10 }
11 ],
12 "bNewDocumentUpdate": true
13}
Overdrag en fratrådt medarbejders dokumenter:
1Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/site" -Interactive
2
3$oldUser = "departed@domain.com"
4$newUser = "replacement@domain.com"
5
6# Get new user claim
7$newUserObj = Get-PnPUser | Where-Object { $_.Email -eq $newUser }
8$newUserClaim = $newUserObj.LoginName
9
10# Get all items created by old user
11$items = Get-PnPListItem -List "Documents" | Where-Object {
12 $_.FieldValues.Author.Email -eq $oldUser
13}
14
15Write-Host "Found $($items.Count) items to update" -ForegroundColor Cyan
16
17foreach ($item in $items) {
18 $endpoint = "_api/web/lists/getByTitle('Documents')/items($($item.Id))/validateUpdateListItem"
19 $body = @"
20 {
21 "formValues": [
22 {
23 "FieldName": "Author",
24 "FieldValue": "[{'Key':'$newUserClaim'}]"
25 }
26 ],
27 "bNewDocumentUpdate": true
28 }
29"@
30
31 Invoke-PnPSPRestMethod -Method Post -Url $endpoint -Content $body
32 Write-Host "Updated item $($item.Id): $($item.FieldValues.FileLeafRef)" -ForegroundColor Green
33}
34
35Write-Host "Transfer complete!" -ForegroundColor Green
Kopiér forfatteren fra et websted til et andet, fil for fil efter navn:
1# Connect to source site
2Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/source" -Interactive
3$sourceItems = Get-PnPListItem -List "Documents"
4
5# Switch to destination site
6Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/destination" -Interactive
7
8foreach ($sourceItem in $sourceItems) {
9 $fileName = $sourceItem.FieldValues.FileLeafRef
10 $destItem = Get-PnPListItem -List "Documents" | Where-Object {
11 $_.FieldValues.FileLeafRef -eq $fileName
12 }
13
14 if ($destItem) {
15 $authorClaim = $sourceItem.FieldValues.Author.LoginName
16 $endpoint = "_api/web/lists/getByTitle('Documents')/items($($destItem.Id))/validateUpdateListItem"
17 $body = @"
18 {
19 "formValues": [
20 {"FieldName": "Author", "FieldValue": "[{'Key':'$authorClaim'}]"}
21 ],
22 "bNewDocumentUpdate": true
23 }
24"@
25 Invoke-PnPSPRestMethod -Method Post -Url $endpoint -Content $body
26 }
27}
Get-PnPListItem inde i løkken henter hele destinationsbiblioteket for hver fil. Ved mere end en håndfuld filer hentes det én gang før løkken. Og FieldValues.Author er en FieldUserValue, der har Email og LookupValue, men ikke LoginName — claimet skal slås op med Get-PnPUser på id’et (.LookupId) eller bygges af e-mailen.
Behold den oprindelige forfatter i et felt for sig. OriginalAuthor er et personfelt, så det vil også have et claim:
1{
2 "formValues": [
3 {
4 "FieldName": "OriginalAuthor",
5 "FieldValue": "@{triggerOutputs()?['body/Author/Email']}"
6 },
7 {
8 "FieldName": "Author",
9 "FieldValue": "[{'Key':'i:0#.f|membership|@{variables('NewAuthor')}'}]"
10 }
11 ],
12 "bNewDocumentUpdate": true
13}
Som det står, sendes e-mailen som ren tekst. Den skal pakkes ind som de andre: "[{'Key':'i:0#.f|membership|@{triggerOutputs()?['body/Author/Email']}'}]". Så kan begge felter sættes i samme kald.
Ændret-datoen#
Kaldet opdaterer Ændret til nu. Skal datoen bevares eller sættes tilbage, sendes den med i formValues sammen med Editor:
1{
2 "formValues": [
3 { "FieldName": "Editor", "FieldValue": "[{'Key':'i:0#.f|membership|user@domain.com'}]" },
4 { "FieldName": "Modified", "FieldValue": "31-12-2020 10:00" }
5 ],
6 "bNewDocumentUpdate": true
7}
Datoen er en tekst, der valideres som i en formular, så formatet følger webstedets regionale indstillinger — dansk dag-måned-år på et dansk websted, amerikansk måned/dag/år på et engelsk. Created kan sættes på samme måde. At kalde SystemUpdate() bagefter, som nogle eksempler foreslår, sætter ikke den gamle dato tilbage.
Før og efter#
Før en masseændring gemmes de nuværende værdier, så de kan sættes tilbage:
1# Export current state
2$backup = Get-PnPListItem -List "Documents" | Select-Object Id, @{
3 Name='Author'; Expression={$_.FieldValues.Author.Email}
4}, @{
5 Name='Editor'; Expression={$_.FieldValues.Editor.Email}
6}
7$backup | Export-Csv "author-backup-$(Get-Date -Format 'yyyyMMdd-HHmmss').csv"
Bagefter kontrolleres det:
1# Verify update succeeded
2$updatedItem = Get-PnPListItem -List "Documents" -Id $itemId
3if ($updatedItem.FieldValues.Author.Email -ne $expectedEmail) {
4 Write-Warning "Update verification failed for item $itemId"
5}
Mange elementer tages i portioner med pauser, så SharePoint ikke begynder at svare 429:
1# Process in batches to avoid throttling
2$batchSize = 100
3$items = Get-PnPListItem -List "Documents" -PageSize 5000
4
5for ($i = 0; $i -lt $items.Count; $i += $batchSize) {
6 $batch = $items[$i..([Math]::Min($i + $batchSize - 1, $items.Count - 1))]
7
8 foreach ($item in $batch) {
9 # Update logic here
10 Start-Sleep -Milliseconds 100 # Throttle protection
11 }
12
13 Write-Host "Processed batch $($i / $batchSize + 1)" -ForegroundColor Cyan
14 Start-Sleep -Seconds 5 # Pause between batches
15}
Fejl#
“The security validation for this page is invalid”. Request digest mangler eller er udløbet. I Power Automate: brug SharePoint-handlingen. I kode: hent en ny fra _api/contextinfo (se ovenfor).
“User cannot be found”. Brugeren er ikke på webstedet, eller claimet er skrevet forkert. ensureUser først:
POST _api/web/ensureUser
Body: {'logonName': 'user@domain.com'}
Værdien skal have klammer og Key i anførselstegn:
1✅ "FieldValue": "[{'Key':'i:0#.f|membership|user@domain.com'}]"
2❌ "FieldValue": "i:0#.f|membership|user@domain.com" // Missing brackets
3❌ "FieldValue": "[{Key:'user@domain.com'}]" // Missing 'Key' quotes
“Access denied”. Kontoen har ikke rettigheder nok. Det anføres ofte, at det kræver ejer eller webstedssamlingsadministrator, for en app Sites.FullControl.All, og at bidrage-rettigheder ikke rækker. Ikke efterprøvet.
“The field ‘Author’ does not exist”. Visningsnavnet er brugt i stedet for det interne:
1✅ "FieldName": "Author" // Internal name
2❌ "FieldName": "Created By" // Display name
3
4✅ "FieldName": "Editor" // Internal name
5❌ "FieldName": "Modified By" // Display name
1Connect-PnPOnline -Url "https://tenant.sharepoint.com/sites/site" -Interactive
2Get-PnPField -List "Documents" | Where-Object { $_.Title -eq "Created By" } | Select-Object InternalName, Title
På et dansk websted hedder visningsnavnet “Oprettet af”, men det interne er stadig Author.
Kaldet lykkes, men intet er ændret. Se svaret. validateUpdateListItem svarer 200 OK, også når et felt er afvist, og fejlen står pr. felt:
1{
2 "value": [
3 {
4 "ErrorMessage": null,
5 "FieldName": "Author",
6 "FieldValue": "...",
7 "HasException": false,
8 "ItemId": 123
9 }
10 ]
11}
Et flow eller script, der kun ser på statuskoden, melder succes, selv om intet er sket. Derfor skal HasException tjekkes på hvert felt. Den næsthyppigste årsag er FieldValue sendt som et rigtigt JSON-array i stedet for en tekst:
1✅ "FieldValue": "[{'Key':'i:0#.f|membership|user@domain.com'}]"
2❌ "FieldValue": [{"Key":"i:0#.f|membership|user@domain.com"}] // Wrong quote style
3❌ "FieldValue": "[{Key:i:0#.f|membership|user@domain.com}]" // Missing quotes
“Invalid JSON in request body”. Et specialtegn i en dynamisk værdi. I Power Automate kan dynamisk indhold bruges direkte i body:
1{
2 "formValues": [
3 {
4 "FieldName": "Author",
5 "FieldValue": "[{'Key':'i:0#.f|membership|@{variables('UserEmail')}'}]"
6 }
7 ],
8 "bNewDocumentUpdate": true
9}
Det der driller#
- 200 OK betyder ikke, at det virkede.
HasExceptionpr. felt er det eneste, der siger noget. bNewDocumentUpdatestyrer versioner, ikke om felterne må sættes.trueretter den nuværende version.falselaver en ny version, og så bliver Ændret af den, der kalder, medmindreEditorstår iformValues.- Et apostrof i et claim (en UPN som
o'brien@…) ødelægger'Key':'…'.-replace "''", "''"i PowerShell erstatter to apostroffer med to apostroffer og gør altså intet. Med dobbelte, escapede anførselstegn i stedet,"[{\"Key\":\"…\"}]", er problemet væk. - Visningsnavnene er på webstedets sprog, de interne navne er ikke.
Author,Editor,CreatedogModifieder de samme overalt.