Added version cleanup feature
This commit is contained in:
@@ -2866,6 +2866,20 @@ $script:LangDefault = @{
|
|||||||
"btn.struct.clear" = "Clear"
|
"btn.struct.clear" = "Clear"
|
||||||
"struct.col.path" = "Full Path"
|
"struct.col.path" = "Full Path"
|
||||||
"struct.col.depth" = "Depth"
|
"struct.col.depth" = "Depth"
|
||||||
|
"tab.versions" = " Versions "
|
||||||
|
"grp.ver.keep" = "Versions to Keep"
|
||||||
|
"lbl.ver.count" = "Number of versions to keep:"
|
||||||
|
"chk.ver.date" = "Also filter by date"
|
||||||
|
"rad.ver.before" = "Keep versions before:"
|
||||||
|
"rad.ver.after" = "Keep versions after:"
|
||||||
|
"grp.ver.scope" = "Scope"
|
||||||
|
"lbl.ver.library" = "Library / Folder:"
|
||||||
|
"ph.ver.library" = "Shared Documents"
|
||||||
|
"chk.ver.recursive" = "Include subfolders (recursive)"
|
||||||
|
"chk.ver.subsites" = "Include subsites"
|
||||||
|
"chk.ver.dryrun" = "Dry run (preview only, no deletion)"
|
||||||
|
"btn.ver.run" = "Clean Versions"
|
||||||
|
"btn.ver.open" = "Open Report"
|
||||||
}
|
}
|
||||||
|
|
||||||
$script:Lang = $null # null = use LangDefault
|
$script:Lang = $null # null = use LangDefault
|
||||||
@@ -3672,7 +3686,83 @@ $btnStructClear.Size = New-Object System.Drawing.Size(90, 30)
|
|||||||
|
|
||||||
$tabStruct.Controls.AddRange(@($grpStructCsv, $grpStructPreview, $lblStructLib, $txtStructLib, $btnStructCreate, $btnStructClear))
|
$tabStruct.Controls.AddRange(@($grpStructCsv, $grpStructPreview, $lblStructLib, $txtStructLib, $btnStructCreate, $btnStructClear))
|
||||||
|
|
||||||
$tabs.TabPages.AddRange(@($tabPerms, $tabStorage, $tabTemplates, $tabSearch, $tabDupes, $tabTransfer, $tabBulk, $tabStruct))
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Tab 9 – Version Cleanup
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
$tabVersions = New-Object System.Windows.Forms.TabPage
|
||||||
|
$tabVersions.Text = T "tab.versions"
|
||||||
|
$tabVersions.BackColor = [System.Drawing.Color]::WhiteSmoke
|
||||||
|
|
||||||
|
# ── Versions to keep ─────────────────────────────────────────────────────────
|
||||||
|
$grpVerKeep = New-Group (T "grp.ver.keep") 10 4 620 110
|
||||||
|
|
||||||
|
$lblVerCount = New-Object System.Windows.Forms.Label
|
||||||
|
$lblVerCount.Text = T "lbl.ver.count"
|
||||||
|
$lblVerCount.Location = New-Object System.Drawing.Point(10, 22)
|
||||||
|
$lblVerCount.Size = New-Object System.Drawing.Size(220, 20)
|
||||||
|
|
||||||
|
$nudVerCount = New-Object System.Windows.Forms.NumericUpDown
|
||||||
|
$nudVerCount.Location = New-Object System.Drawing.Point(235, 20)
|
||||||
|
$nudVerCount.Size = New-Object System.Drawing.Size(70, 22)
|
||||||
|
$nudVerCount.Minimum = 0
|
||||||
|
$nudVerCount.Maximum = 500
|
||||||
|
$nudVerCount.Value = 5
|
||||||
|
|
||||||
|
$chkVerDate = New-Check (T "chk.ver.date") 10 50 250 $false
|
||||||
|
|
||||||
|
$radVerBefore = New-Radio (T "rad.ver.before") 30 74 200 $true
|
||||||
|
$radVerBefore.Enabled = $false
|
||||||
|
$radVerAfter = New-Radio (T "rad.ver.after") 30 96 200 $false
|
||||||
|
$radVerAfter.Enabled = $false
|
||||||
|
|
||||||
|
$dtpVer = New-Object System.Windows.Forms.DateTimePicker
|
||||||
|
$dtpVer.Location = New-Object System.Drawing.Point(235, 74)
|
||||||
|
$dtpVer.Size = New-Object System.Drawing.Size(150, 22)
|
||||||
|
$dtpVer.Format = [System.Windows.Forms.DateTimePickerFormat]::Short
|
||||||
|
$dtpVer.Enabled = $false
|
||||||
|
|
||||||
|
$chkVerDate.Add_CheckedChanged({
|
||||||
|
$on = $chkVerDate.Checked
|
||||||
|
$radVerBefore.Enabled = $on
|
||||||
|
$radVerAfter.Enabled = $on
|
||||||
|
$dtpVer.Enabled = $on
|
||||||
|
})
|
||||||
|
|
||||||
|
$grpVerKeep.Controls.AddRange(@($lblVerCount, $nudVerCount, $chkVerDate, $radVerBefore, $radVerAfter, $dtpVer))
|
||||||
|
|
||||||
|
# ── Scope ─────────────────────────────────────────────────────────────────────
|
||||||
|
$grpVerScope = New-Group (T "grp.ver.scope") 10 118 620 76
|
||||||
|
|
||||||
|
$lblVerLib = New-Object System.Windows.Forms.Label
|
||||||
|
$lblVerLib.Text = T "lbl.ver.library"
|
||||||
|
$lblVerLib.Location = New-Object System.Drawing.Point(10, 22)
|
||||||
|
$lblVerLib.Size = New-Object System.Drawing.Size(150, 20)
|
||||||
|
|
||||||
|
$txtVerLib = New-Object System.Windows.Forms.TextBox
|
||||||
|
$txtVerLib.Location = New-Object System.Drawing.Point(164, 20)
|
||||||
|
$txtVerLib.Size = New-Object System.Drawing.Size(230, 22)
|
||||||
|
$txtVerLib.PlaceholderText = T "ph.ver.library"
|
||||||
|
|
||||||
|
$chkVerRecursive = New-Check (T "chk.ver.recursive") 10 48 260 $true
|
||||||
|
$chkVerSubsites = New-Check (T "chk.ver.subsites") 280 48 200 $false
|
||||||
|
|
||||||
|
$grpVerScope.Controls.AddRange(@($lblVerLib, $txtVerLib, $chkVerRecursive, $chkVerSubsites))
|
||||||
|
|
||||||
|
# ── Options + Buttons ─────────────────────────────────────────────────────────
|
||||||
|
$chkVerDryRun = New-Check (T "chk.ver.dryrun") 12 200 350 $true
|
||||||
|
|
||||||
|
$btnVerRun = New-ActionBtn (T "btn.ver.run") 10 228 ([System.Drawing.Color]::FromArgb(180, 60, 20))
|
||||||
|
$btnVerRun.Size = New-Object System.Drawing.Size(180, 30)
|
||||||
|
|
||||||
|
$btnVerOpen = New-Object System.Windows.Forms.Button
|
||||||
|
$btnVerOpen.Text = T "btn.ver.open"
|
||||||
|
$btnVerOpen.Location = New-Object System.Drawing.Point(200, 228)
|
||||||
|
$btnVerOpen.Size = New-Object System.Drawing.Size(130, 30)
|
||||||
|
$btnVerOpen.Enabled = $false
|
||||||
|
|
||||||
|
$tabVersions.Controls.AddRange(@($grpVerKeep, $grpVerScope, $chkVerDryRun, $btnVerRun, $btnVerOpen))
|
||||||
|
|
||||||
|
$tabs.TabPages.AddRange(@($tabPerms, $tabStorage, $tabTemplates, $tabSearch, $tabDupes, $tabTransfer, $tabBulk, $tabStruct, $tabVersions))
|
||||||
|
|
||||||
# ── Progress bar ───────────────────────────────────────────────────────────────
|
# ── Progress bar ───────────────────────────────────────────────────────────────
|
||||||
$progressBar = New-Object System.Windows.Forms.ProgressBar
|
$progressBar = New-Object System.Windows.Forms.ProgressBar
|
||||||
@@ -3864,6 +3954,19 @@ $_reg = {
|
|||||||
& $_reg $script:i18nMap $lblStructLib "lbl.struct.library"
|
& $_reg $script:i18nMap $lblStructLib "lbl.struct.library"
|
||||||
& $_reg $script:i18nMap $btnStructCreate "btn.struct.create"
|
& $_reg $script:i18nMap $btnStructCreate "btn.struct.create"
|
||||||
& $_reg $script:i18nMap $btnStructClear "btn.struct.clear"
|
& $_reg $script:i18nMap $btnStructClear "btn.struct.clear"
|
||||||
|
# Version Cleanup tab
|
||||||
|
& $_reg $script:i18nMap $lblVerCount "lbl.ver.count"
|
||||||
|
& $_reg $script:i18nMap $chkVerDate "chk.ver.date"
|
||||||
|
& $_reg $script:i18nMap $radVerBefore "rad.ver.before"
|
||||||
|
& $_reg $script:i18nMap $radVerAfter "rad.ver.after"
|
||||||
|
& $_reg $script:i18nMap $lblVerLib "lbl.ver.library"
|
||||||
|
& $_reg $script:i18nMap $chkVerRecursive "chk.ver.recursive"
|
||||||
|
& $_reg $script:i18nMap $chkVerSubsites "chk.ver.subsites"
|
||||||
|
& $_reg $script:i18nMap $chkVerDryRun "chk.ver.dryrun"
|
||||||
|
& $_reg $script:i18nMap $btnVerRun "btn.ver.run"
|
||||||
|
& $_reg $script:i18nMap $btnVerOpen "btn.ver.open"
|
||||||
|
& $_reg $script:i18nMap $grpVerKeep "grp.ver.keep"
|
||||||
|
& $_reg $script:i18nMap $grpVerScope "grp.ver.scope"
|
||||||
|
|
||||||
# Tab pages
|
# Tab pages
|
||||||
& $_reg $script:i18nTabs $tabPerms "tab.perms"
|
& $_reg $script:i18nTabs $tabPerms "tab.perms"
|
||||||
@@ -3874,6 +3977,7 @@ $_reg = {
|
|||||||
& $_reg $script:i18nTabs $tabTransfer "tab.transfer"
|
& $_reg $script:i18nTabs $tabTransfer "tab.transfer"
|
||||||
& $_reg $script:i18nTabs $tabBulk "tab.bulk"
|
& $_reg $script:i18nTabs $tabBulk "tab.bulk"
|
||||||
& $_reg $script:i18nTabs $tabStruct "tab.structure"
|
& $_reg $script:i18nTabs $tabStruct "tab.structure"
|
||||||
|
& $_reg $script:i18nTabs $tabVersions "tab.versions"
|
||||||
|
|
||||||
# Menu items
|
# Menu items
|
||||||
& $_reg $script:i18nMenus $menuSettings "menu.settings"
|
& $_reg $script:i18nMenus $menuSettings "menu.settings"
|
||||||
@@ -3893,6 +3997,7 @@ $script:i18nPlaceholders = [System.Collections.Generic.Dictionary[string,object]
|
|||||||
& $_reg $script:i18nPlaceholders $txtXferSrcLib "ph.xfer.library"
|
& $_reg $script:i18nPlaceholders $txtXferSrcLib "ph.xfer.library"
|
||||||
& $_reg $script:i18nPlaceholders $txtXferDstSite "ph.xfer.site"
|
& $_reg $script:i18nPlaceholders $txtXferDstSite "ph.xfer.site"
|
||||||
& $_reg $script:i18nPlaceholders $txtXferDstLib "ph.xfer.library"
|
& $_reg $script:i18nPlaceholders $txtXferDstLib "ph.xfer.library"
|
||||||
|
& $_reg $script:i18nPlaceholders $txtVerLib "ph.ver.library"
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -5768,6 +5873,199 @@ $btnStructCreate.Add_Click({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# ── Version Cleanup handlers ─────────────────────────────────────────────────
|
||||||
|
$script:_VerReport = $null
|
||||||
|
|
||||||
|
$btnVerOpen.Add_Click({
|
||||||
|
if ($script:_VerReport -and (Test-Path $script:_VerReport)) {
|
||||||
|
Start-Process $script:_VerReport
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$btnVerRun.Add_Click({
|
||||||
|
# --- Gather all selected site URLs ---
|
||||||
|
$siteUrls = @()
|
||||||
|
if ($script:_CachedSites -and $script:_CachedSites.Count -gt 0) {
|
||||||
|
foreach ($s in $script:_CachedSites) {
|
||||||
|
if ($s.Checked) { $siteUrls += $s.Url }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($siteUrls.Count -eq 0) {
|
||||||
|
$single = $txtSiteUrl.Text.Trim()
|
||||||
|
if ($single) { $siteUrls = @($single) }
|
||||||
|
}
|
||||||
|
if ($siteUrls.Count -eq 0) { Write-Log "Site URL required." "Red"; return }
|
||||||
|
|
||||||
|
$clientId = $txtClientId.Text.Trim()
|
||||||
|
if (-not $clientId) { Write-Log "Client ID required." "Red"; return }
|
||||||
|
|
||||||
|
$keepCount = [int]$nudVerCount.Value
|
||||||
|
$useDate = $chkVerDate.Checked
|
||||||
|
$dateBefore = $radVerBefore.Checked # true = keep before, false = keep after
|
||||||
|
$cutoffDate = $dtpVer.Value
|
||||||
|
$library = $txtVerLib.Text.Trim()
|
||||||
|
$recursive = $chkVerRecursive.Checked
|
||||||
|
$subsites = $chkVerSubsites.Checked
|
||||||
|
$dryRun = $chkVerDryRun.Checked
|
||||||
|
|
||||||
|
$btnVerRun.Enabled = $false
|
||||||
|
Start-ProgressAnim
|
||||||
|
$modeLabel = if ($dryRun) { "DRY RUN" } else { "LIVE" }
|
||||||
|
Write-Log "=== VERSION CLEANUP ($modeLabel) ===" "White"
|
||||||
|
Write-Log "Keep: $keepCount version(s)" "Gray"
|
||||||
|
if ($useDate) {
|
||||||
|
$dir = if ($dateBefore) { "before" } else { "after" }
|
||||||
|
Write-Log "Date filter: keep versions $dir $($cutoffDate.ToString('yyyy-MM-dd'))" "Gray"
|
||||||
|
}
|
||||||
|
Write-Log ("-" * 52) "DarkGray"
|
||||||
|
|
||||||
|
$report = [System.Collections.Generic.List[object]]::new()
|
||||||
|
$totalDeleted = 0
|
||||||
|
$totalKept = 0
|
||||||
|
$totalErrors = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($siteUrl in $siteUrls) {
|
||||||
|
Write-Log "Connecting to $siteUrl ..." "Gray"
|
||||||
|
Connect-PnPOnline -Url $siteUrl -Interactive -ClientId $clientId
|
||||||
|
|
||||||
|
# Collect site URLs to process (main + subsites)
|
||||||
|
$sitesToProcess = @($siteUrl)
|
||||||
|
if ($subsites) {
|
||||||
|
try {
|
||||||
|
$subs = Get-PnPSubWeb -Recurse -ErrorAction SilentlyContinue
|
||||||
|
foreach ($sw in $subs) { $sitesToProcess += $sw.Url }
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($currentSite in $sitesToProcess) {
|
||||||
|
if ($currentSite -ne $siteUrl) {
|
||||||
|
try { Connect-PnPOnline -Url $currentSite -Interactive -ClientId $clientId } catch {
|
||||||
|
Write-Log " Cannot connect to subsite $currentSite — skipped" "DarkOrange"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Log "Processing site: $currentSite" "White"
|
||||||
|
|
||||||
|
# Get target lists
|
||||||
|
$lists = @()
|
||||||
|
if ($library) {
|
||||||
|
try { $lists = @(Get-PnPList -Identity $library -ErrorAction Stop) } catch {
|
||||||
|
Write-Log " Library '$library' not found — skipped" "DarkOrange"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$lists = Get-PnPList | Where-Object { $_.BaseTemplate -eq 101 -and $_.Hidden -eq $false }
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($list in $lists) {
|
||||||
|
Write-Log " Library: $($list.Title)" "Gray"
|
||||||
|
try {
|
||||||
|
$camlQuery = "<View Scope='RecursiveAll'><Query></Query><RowLimit>5000</RowLimit></View>"
|
||||||
|
if (-not $recursive) {
|
||||||
|
$camlQuery = "<View><Query></Query><RowLimit>5000</RowLimit></View>"
|
||||||
|
}
|
||||||
|
$items = Get-PnPListItem -List $list.Title -Query $camlQuery -ErrorAction Stop |
|
||||||
|
Where-Object { $_.FileSystemObjectType -eq "File" }
|
||||||
|
} catch {
|
||||||
|
Write-Log " Error listing files: $($_.Exception.Message)" "Red"
|
||||||
|
$totalErrors++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($item in $items) {
|
||||||
|
try {
|
||||||
|
$file = $item.FieldValues["FileRef"]
|
||||||
|
$versions = Get-PnPFileVersion -Url $file -ErrorAction Stop
|
||||||
|
|
||||||
|
if ($versions.Count -le $keepCount) { continue }
|
||||||
|
|
||||||
|
# Sort versions oldest first (by VersionLabel numeric)
|
||||||
|
$sorted = $versions | Sort-Object { [double]$_.VersionLabel }
|
||||||
|
|
||||||
|
# Determine which versions to delete
|
||||||
|
$toDelete = @()
|
||||||
|
foreach ($v in $sorted) {
|
||||||
|
# Always keep the last $keepCount versions
|
||||||
|
$idx = [array]::IndexOf($sorted, $v)
|
||||||
|
$remaining = $sorted.Count - $idx
|
||||||
|
if ($remaining -le $keepCount) { break }
|
||||||
|
|
||||||
|
# Apply date filter if enabled
|
||||||
|
if ($useDate) {
|
||||||
|
$vDate = [datetime]$v.Created
|
||||||
|
if ($dateBefore) {
|
||||||
|
# Keep versions before cutoff → delete versions ON or AFTER cutoff
|
||||||
|
if ($vDate -lt $cutoffDate) { continue }
|
||||||
|
} else {
|
||||||
|
# Keep versions after cutoff → delete versions BEFORE cutoff
|
||||||
|
if ($vDate -ge $cutoffDate) { continue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$toDelete += $v
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($toDelete.Count -eq 0) { continue }
|
||||||
|
|
||||||
|
$fileName = Split-Path $file -Leaf
|
||||||
|
foreach ($v in $toDelete) {
|
||||||
|
if ($dryRun) {
|
||||||
|
Write-Log " [DRY] Would delete v$($v.VersionLabel) of $fileName ($($v.Created))" "DarkOrange"
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
Remove-PnPFileVersion -Url $file -Identity $v.Id -Force -ErrorAction Stop
|
||||||
|
Write-Log " Deleted v$($v.VersionLabel) of $fileName" "LightGreen"
|
||||||
|
} catch {
|
||||||
|
Write-Log " Error deleting v$($v.VersionLabel) of $fileName — $($_.Exception.Message)" "Red"
|
||||||
|
$totalErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$totalDeleted++
|
||||||
|
}
|
||||||
|
|
||||||
|
$kept = $sorted.Count - $toDelete.Count
|
||||||
|
$totalKept += $kept
|
||||||
|
|
||||||
|
$report.Add([PSCustomObject]@{
|
||||||
|
Site = $currentSite
|
||||||
|
Library = $list.Title
|
||||||
|
File = $file
|
||||||
|
TotalVer = $sorted.Count
|
||||||
|
Deleted = $toDelete.Count
|
||||||
|
Kept = $kept
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
$totalErrors++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Export CSV report
|
||||||
|
if ($report.Count -gt 0) {
|
||||||
|
$outDir = $txtOutput.Text.Trim()
|
||||||
|
if (-not $outDir) { $outDir = if ($PSScriptRoot) { $PSScriptRoot } else { $PWD.Path } }
|
||||||
|
if (-not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir | Out-Null }
|
||||||
|
$stamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||||
|
$prefix = if ($dryRun) { "VersionCleanup_DryRun" } else { "VersionCleanup" }
|
||||||
|
$csvFile = Join-Path $outDir "${prefix}_$stamp.csv"
|
||||||
|
$report | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8
|
||||||
|
$script:_VerReport = $csvFile
|
||||||
|
$btnVerOpen.Enabled = $true
|
||||||
|
Write-Log "Report: $csvFile" "White"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "=== VERSION CLEANUP COMPLETE: $totalDeleted deleted, $totalKept kept, $totalErrors error(s) ===" "White"
|
||||||
|
} catch {
|
||||||
|
Write-Log "Error: $($_.Exception.Message)" "Red"
|
||||||
|
} finally {
|
||||||
|
$btnVerRun.Enabled = $true
|
||||||
|
Stop-ProgressAnim
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
# ── Initialisation : chargement des settings ───────────────────────────────
|
# ── Initialisation : chargement des settings ───────────────────────────────
|
||||||
|
|||||||
17
lang/fr.json
17
lang/fr.json
@@ -150,5 +150,20 @@
|
|||||||
"btn.struct.create": "Créer l'arborescence",
|
"btn.struct.create": "Créer l'arborescence",
|
||||||
"btn.struct.clear": "Effacer",
|
"btn.struct.clear": "Effacer",
|
||||||
"struct.col.path": "Chemin complet",
|
"struct.col.path": "Chemin complet",
|
||||||
"struct.col.depth": "Profondeur"
|
"struct.col.depth": "Profondeur",
|
||||||
|
|
||||||
|
"tab.versions": " Versions ",
|
||||||
|
"grp.ver.keep": "Versions à conserver",
|
||||||
|
"lbl.ver.count": "Nombre de versions à garder :",
|
||||||
|
"chk.ver.date": "Filtrer aussi par date",
|
||||||
|
"rad.ver.before": "Garder les versions avant le :",
|
||||||
|
"rad.ver.after": "Garder les versions après le :",
|
||||||
|
"grp.ver.scope": "Périmètre",
|
||||||
|
"lbl.ver.library": "Bibliothèque / Dossier :",
|
||||||
|
"ph.ver.library": "Documents partagés",
|
||||||
|
"chk.ver.recursive": "Inclure les sous-dossiers (récursif)",
|
||||||
|
"chk.ver.subsites": "Inclure les sous-sites",
|
||||||
|
"chk.ver.dryrun": "Simulation (aperçu uniquement, aucune suppression)",
|
||||||
|
"btn.ver.run": "Nettoyer les versions",
|
||||||
|
"btn.ver.open": "Ouvrir le rapport"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user