Zum Inhalt springen
SharePoint Entwicklung

SharePoint: alte Versionen automatisch löschen und Speicherplatz freigeben

Alte Versionen in SharePoint Online per PowerShell und Azure Function löschen: gesteuert über eine Liste, app-only, mit Zeitbudget und Probelauf. Mit Code.

Aktualisiert am
Schema: links eine Projektliste mit dem Steuerfeld Versionen löschen und den Werten Ja, Erledigt, Nein und Fehler, in der Mitte der Wochenlauf, rechts das Ergebnis je Bibliothek mit gelöschten Versionen und freigegebenem Speicher

Ein Plan wird zwanzigmal überarbeitet, jede Fassung bleibt als Version erhalten. Bei einer 40-MB-Datei sind das 800 MB für eine einzige Zeichnung. In Projektablagen mit tausenden Plänen und Protokollen ist der Versionsverlauf schnell der größte Posten im SharePoint-Speicher, oft größer als die aktuellen Dateien selbst. Wer in den Speichermetriken einer Site nachsieht, findet ihn dort schwarz auf weiß.

Für einen Anlagenbauer mit sehr vielen Projekträumen haben wir das Löschen alter Versionen automatisiert. Dieser Beitrag zeigt, was SharePoint selbst dafür mitbringt, warum wir trotzdem eine eigene Lösung gebaut haben und wie sie im Kern funktioniert, mit Code.

Was SharePoint selbst mitbringt

Versionsgrenzen. Je Organisation, Site oder Bibliothek lässt sich festlegen, wie viele Hauptversionen erhalten bleiben oder nach wie vielen Tagen sie ablaufen. Mit der Einstellung „Automatisch“ dünnt SharePoint ältere Versionen selbst aus. Wichtig: Diese Grenzen gelten für neue Versionen. Der Bestand schrumpft erst, wenn eine Datei wieder bearbeitet wird. Dateien in abgeschlossenen Projekten werden nie mehr bearbeitet, ihr Verlauf bleibt.

Lösch-Jobs für den Bestand. Für vorhandene Versionen gibt es Batch-Jobs, die eine Site oder Bibliothek im Hintergrund bereinigen (New-SPOSiteFileVersionBatchDeleteJob, New-PnPSiteFileVersionBatchDeleteJob und die Varianten je Bibliothek). Sie laufen asynchron, werden je Site von einer Administratorin angestoßen, und die gelöschten Versionen sind laut Microsoft nicht über den Papierkorb wiederherstellbar.

Speichermetriken. Unter Websiteeinstellungen → Speichermetriken zeigt SharePoint je Ordner, wie viel Platz Versionen belegen. Ein guter Ausgangspunkt für die Frage, wo es sich lohnt.

Warum eine eigene Automatisierung

Für einzelne Sites reicht der Batch-Job. Bei sehr vielen Projekträumen fehlen drei Dinge:

  1. Die Fachabteilung entscheidet, nicht die IT. Welche Projekte bereinigt werden, weiß die Projektleitung. Sie soll es dort festlegen, wo sie ohnehin arbeitet: in der Projektliste.
  2. Ein Dienst ohne Benutzerkonto. Die Bereinigung läuft nachts als Azure Function mit Zertifikat. In unseren Tests lehnte der Batch-Lösch-Job ein solches App-only-Konto ab. Remove-PnPFileVersion je Datei funktioniert dagegen app-only.
  3. Nachvollziehbarkeit. Je Projekt soll stehen, wann was gelöscht wurde und wie viel Platz frei wurde, und am Ende soll eine Zusammenfassung in der Mailbox liegen.

Dazu kommt der Lebenszyklus: Die Versionsbereinigung ist der erste Schritt, bevor ein abgeschlossenes Projekt archiviert wird. Was ins Archiv geht, soll nicht zwanzig Fassungen jedes Plans mitschleppen.

Projektliste mit Steuerfeld, Wochenlauf und Ergebnis je Bibliothek

Gesteuert über die Projektliste

Die Projektliste bekommt zwei Felder: eine Auswahl, ob die Versionen gelöscht werden sollen, und ein mehrzeiliges Protokoll.

# Steuerfeld und Protokoll in der Projektliste anlegen (PnP PowerShell)
Add-PnPField -List "Projektliste" -InternalName "DeleteVersions" -DisplayName "Versionen löschen" `
    -Type Choice -Choices "No", "Yes", "Erledigt", "Fehler" -AddToDefaultView
Set-PnPField -List "Projektliste" -Identity "DeleteVersions" -Values @{ DefaultValue = "No"; Indexed = $true }

Add-PnPField -List "Projektliste" -InternalName "DeleteVersionsLog" -DisplayName "Versionen löschen – Protokoll" `
    -Type Note

Die Projektleitung setzt „Yes“. Der Dienst verarbeitet nur diese Einträge und setzt danach „Erledigt“ oder „Fehler“. Wird ein Lauf durch das Zeitbudget unterbrochen, bleibt „Yes“ stehen, und der nächste Lauf setzt fort.

Die Kandidaten finden, auch in großen Listen

Eine Projektliste mit vielen tausend Einträgen liegt über dem Listenschwellenwert von 5.000. Eine Abfrage auf das Steuerfeld funktioniert dann nur mit Index, und einen Index nachträglich anzulegen, verweigert SharePoint bei solchen Listen. Deshalb gibt es einen Rückfallweg: seitenweise laden und im Skript filtern.

$caml = "<View><ViewFields><FieldRef Name='Title'/><FieldRef Name='URL'/><FieldRef Name='DeleteVersions'/></ViewFields>" +
        "<Query><Where><Eq><FieldRef Name='DeleteVersions'/><Value Type='Choice'>Yes</Value></Eq></Where></Query></View>"
try {
    $candidates = @(Get-PnPListItem -List "Projektliste" -Query $caml -Connection $root -ErrorAction Stop)
}
catch {
    # Ohne Index scheitert die Abfrage am Listenschwellenwert: seitenweise laden, im Skript filtern
    $candidates = @(Get-PnPListItem -List "Projektliste" -Fields "Title", "URL", "DeleteVersions" -PageSize 1000 -Connection $root |
        Where-Object { [string]$_["DeleteVersions"] -eq "Yes" })
}

Der Kern: Versionen je Datei löschen

Je Projektraum geht der Dienst durch die Dokumentbibliotheken, ohne Systembibliotheken wie Websiteobjekte oder Formularvorlagen, und dort durch die Dateien. Dateien mit nur einer Version werden ohne weitere Abfrage übersprungen; das hält Folgeläufe billig.

function Remove-OldFileVersions {
    param($Connection, $Library, [int]$KeepHistorical = 0, [datetime]$Deadline, [switch]$DryRun)

    $deleted = 0; $bytes = [int64]0
    $items = Get-PnPListItem -List $Library -Fields "FileRef", "FSObjType", "ContentVersion" -PageSize 500 -Connection $Connection

    foreach ($item in $items) {
        if ((Get-Date) -ge $Deadline) { return @{ Deleted = $deleted; Bytes = $bytes; TimedOut = $true } }
        if ([string]$item["FSObjType"] -ne "0") { continue }          # Ordner überspringen
        if ([int]$item["ContentVersion"] -le 1) { continue }         # keine Altversionen

        $fileRef  = [string]$item["FileRef"]
        $versions = @(Get-PnPFileVersion -Url $fileRef -Connection $Connection)   # alt nach neu
        if ($versions.Count -le $KeepHistorical) { continue }

        $toDelete = $versions | Select-Object -First ($versions.Count - $KeepHistorical)
        $bytes   += ($toDelete | Measure-Object -Property Size -Sum).Sum

        if (-not $DryRun) {
            if ($KeepHistorical -eq 0) {
                Remove-PnPFileVersion -Url $fileRef -All -Force -Connection $Connection
            } else {
                $toDelete | ForEach-Object { Remove-PnPFileVersion -Url $fileRef -Identity $_.Id -Force -Connection $Connection }
            }
        }
        $deleted += $toDelete.Count
    }
    return @{ Deleted = $deleted; Bytes = $bytes; TimedOut = $false }
}

Die aktuelle Version einer Datei taucht in Get-PnPFileVersion nicht auf, sie bleibt immer erhalten. KeepHistorical legt fest, wie viele ältere Fassungen zusätzlich bleiben; beim Anlagenbauer ist es keine.

Zeitbudget statt Abbruch

Eine Azure Function hat ein festes Zeitlimit. Statt mitten in einer Datei abgebrochen zu werden, rechnet der Dienst mit einem eigenen Budget, standardmäßig 150 Minuten bei einem Funktionslimit von drei Stunden. Ist es erreicht, schreibt er das Protokoll, lässt den Status auf „Yes“ und hört sauber auf. Weil bereits bereinigte Dateien nur noch eine Version haben, kostet der nächste Lauf für sie fast nichts.

{
  "bindings": [
    { "name": "Timer", "type": "timerTrigger", "direction": "in", "schedule": "0 0 23 * * 5" }
  ]
}

Der Wochenlauf startet freitags um 23:00 UTC, ein zweiter Lauf für Gruppenseiten samstags früh, jeweils mit eigenem Zeitbudget.

Protokoll, Status und Zusammenfassung

Je Projekt entsteht ein Protokolleintrag mit Zeitpunkt, Bibliotheken, gelöschten Versionen und freigegebenem Speicher. Er wird zusammen mit dem Status in einem Schritt geschrieben:

$log = @"
=== $((Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss'))Z - Wöchentlicher Lauf ===
Behalte: aktuelle Version + $KeepHistorical Altversion(en) pro Datei
  OK     03 Pläne - Versionen gelöscht: 1284, frei: 2,1 GB
Status: Erledigt
"@
$previous = [string](Get-PnPListItem -List "Projektliste" -Id $itemId -Connection $root)["DeleteVersionsLog"]
Set-PnPListItem -List "Projektliste" -Identity $itemId -Connection $root -Values @{
    DeleteVersions    = $status            # "Erledigt", "Fehler" oder unverändert "Yes"
    DeleteVersionsLog = ($log + "`n`n" + $previous).Trim()
} | Out-Null

Nach jedem Lauf geht eine Zusammenfassung an die Fachabteilung: Projekt, freigegebener Speicher, Status. Scheitert der Lauf, kommt stattdessen eine Fehlermail.

Probelauf vor dem Ernstfall

Gelöschte Versionen sind weg. Deshalb gibt es neben dem Wochenlauf einen manuellen Aufruf über HTTP, der einzelne Einträge der Projektliste verarbeitet und mit dryRun nur rechnet, was frei würde:

{
  "onlyListItemIds": [109],
  "runLabel": "Probelauf Projekt 109",
  "maxRunMinutes": 5,
  "dryRun": true,
  "skipLogWrite": true
}

Für die Frage vorab, wo sich die Bereinigung lohnt, gibt es zusätzlich ein Berichtsskript, das je Bibliothek die Größe der Altversionen in eine CSV-Datei schreibt.

Vorbeugen statt nur aufräumen

Manche Bibliotheken brauchen gar keine Versionen: Importbibliotheken, in die ein Dienst Bestellungen oder Tagesberichte als PDF ablegt, werden nie bearbeitet. Beim Anlegen neuer Projekträume schaltet das Provisioning die Versionierung dort ab. Was nicht entsteht, muss später niemand löschen.

Was Sie vorher klären sollten

  • Aufbewahrung. Liegt eine Aufbewahrungsrichtlinie oder ein Legal Hold auf der Site, gehen Aufbewahrungspflichten vor. Klären Sie mit Compliance, welche Projekte bereinigt werden dürfen.
  • Wie viele Fassungen bleiben. Nur die aktuelle oder zusätzlich die letzte Freigabe? Bei Plänen mit Index-Logik genügt oft die aktuelle Datei.
  • Wer entscheidet. Projektleitung, Fachabteilung oder ein festes Kriterium wie „Projektphase Archiv“.
  • Wann. Am Projektende als Schritt vor der Archivierung, oder regelmäßig für laufende Projekte.

Wie ein Projektraum seine Phasen vom Angebot bis zum Abschluss durchläuft, beschreibt der Beitrag Projektphasen in SharePoint. Den nächsten Schritt, das nachweisbare Archiv im eigenen Azure, übernimmt Smarter Archiving. Die Versionsbereinigung ist ein Baustein von Smarter Project Portal.

  • Smarter Archiving
  • SharePoint Online
  • PowerShell
  • PnP PowerShell
  • Azure Functions
  • Versionierung
  • Speicherplatz

Ähnliche Beiträge

SharePoint Intranet Telefonbuch mit Skills Feature
SharePoint Intranet Telefonbuch mit Skills Feature

SharePoint Intranet Telefonbuch mit Skills Feature

SharePoint Personensuche und Kompetenzsuche mit Microsoft 365 Skills, Skill-Katalog, SPFx, SharePoint Search und Microsoft Graph.

Weiterlesen
Provisioning im Großformat: PnP-Templates, Throttling

Provisioning im Großformat: PnP-Templates, Throttling und Race Conditions bei tausenden Bibliotheken

PnP-Provisioning für hunderte Sites und tausende Bibliotheken: 429-Throttling, Idempotenz und ETag-Lösung für Race Conditions.

Weiterlesen
Versionshistorie intelligent migrieren: Weniger

Versionshistorie intelligent migrieren: Weniger Speicher, gleiche Nachvollziehbarkeit

Wie Hash-basierte Versions-Deduplizierung bei einer 25-TB-Migration SharePoint-Online-Speicher spart und Migration-API-Bugs umgeht.

Weiterlesen

Fragen zu diesem Thema?

Wir unterstützen Sie gerne bei der Umsetzung in Ihrer Umgebung.