A flow meant to copy new invoices into an archive folder ran against the whole library instead. Every PDF in the archive now has today's date, and the "same name = replace" setting did exactly what it says. Whether this is a five-minute fix or a real loss depends on one question: did the flow overwrite, or delete-then-create?
Find out which it was
Open one affected file's Version history (file → ⋯ → Version history).
- You see the earlier versions listed with the original dates: the flow overwrote. The old content is intact as a previous version. Good outcome; skip to the restore.
- Version history shows only version 1.0 with today's date: the flow deleted the file and created a new one with the same name. The original is a separate object in the recycle bin, and the new file is not related to it.
The second case is what a flow does when it uses Delete file followed by Create file, or when the target library had versioning off. Check the run history in Power Automate if the version history is ambiguous; the actions are listed.
Case 1: overwrite — restore the previous versions
One file: version history → ⋯ on the version before the flow → Restore. It becomes the new current version; nothing is deleted.
Hundreds of files: loop it, filtered to what the flow touched. The flow ran as a specific account (a service account, or the flow owner), and Editor on each file records that.
Connect-PnPOnline -Url https://contoso.sharepoint.com/sites/Finance -Interactive
$library = 'Invoices'
$flowAs = 'svc-flows@contoso.com'
$ranAt = Get-Date '2026-07-14 14:00'
Get-PnPListItem -List $library -PageSize 2000 -Fields FileRef,Modified,Editor |
Where-Object { $_['Editor'].Email -eq $flowAs -and $_['Modified'] -gt $ranAt -and $_.FileSystemObjectType -eq 'File' } |
ForEach-Object {
$prev = Get-PnPFileVersion -Url $_['FileRef'] | Where-Object Created -lt $ranAt | Sort-Object Created -Descending | Select-Object -First 1
Restore-PnPFileVersion -Url $_['FileRef'] -Identity $prev.Id -Force
}
Files the flow created rather than overwrote have no earlier version; the call fails on those, so expect red text and check them by hand.
That is the 80% version. The checks that stop it restoring the wrong thing — showing you the list first, skipping files it should not touch, and confirming afterwards — are the part we built the tool for.
Case 2: delete-then-create — restore from the recycle bin
The originals are recycle-bin entries deleted by the flow's account at the run time. The new files with the same names are in the way, so restoring will produce a name clash: SharePoint keeps the existing file and gives the restored one a suffix, or fails, depending on the version.
Cleanest sequence:
- Move (do not delete) the flow's new files to a holding folder.
- Restore the originals: recycle bin filtered by Deleted by = the flow account and the run time. In bulk,
Get-PnPRecycleBinItem | Where-Object { $_.DeletedByEmail -eq $flowAs -and $_.DeletedDate -gt $ranAt } | Restore-PnPRecycleBinItem -Force. - Decide what to do with the holding folder.
If the recycle bin is empty of them, the flow account emptied it, or the run was more than 93 days ago. Then it is the Preservation Hold Library (if a retention policy applies) or Microsoft 365 Backup.
Stop it happening again
- Scope the trigger to a folder, not the library.
- Add Get file metadata and a file exists condition before any Create file with overwrite.
- Write to a staging folder and move on success.
- Exclude the flow's own account in a Modified By condition, so a flow that edits files does not trigger itself.
- Keep versioning on. It is the only reason case 1 is a non-event.
The general version
"Restore the last good version of every file one account changed after one moment, and show me the list before you do it" is the same problem whether the account is a flow, a sync client or a person. Rollback365 does that inside the library, with a dry run that classifies each file first. For the recycle-bin half of case 2, Undelete365's search by Deleted by does the equivalent.