Added functionnality : you can vcreate a whole folder tree by importing a CSV (see examples fodler)

This commit is contained in:
2026-03-16 16:39:37 +01:00
parent 9dc85c8057
commit 086804edf9
3 changed files with 232 additions and 2 deletions

View File

@@ -2854,6 +2854,18 @@ $script:LangDefault = @{
"bulk.status.creating" = "Creating..."
"bulk.status.ok" = "OK"
"bulk.status.error" = "Error"
"tab.structure" = " Structure "
"grp.struct.csv" = "CSV Import"
"lbl.struct.desc" = "Import a CSV to create a folder tree. Each column represents a depth level."
"btn.struct.csv" = "Load CSV..."
"grp.struct.preview" = "Preview"
"grp.struct.target" = "Target"
"lbl.struct.library" = "Target library:"
"ph.struct.library" = "Shared Documents"
"btn.struct.create" = "Create Structure"
"btn.struct.clear" = "Clear"
"struct.col.path" = "Full Path"
"struct.col.depth" = "Depth"
}
$script:Lang = $null # null = use LangDefault
@@ -3606,7 +3618,66 @@ $btnBulkCreate.Size = New-Object System.Drawing.Size(200, 34)
$tabBulk.Controls.AddRange(@($grpBulkList, $btnBulkCreate))
$tabs.TabPages.AddRange(@($tabPerms, $tabStorage, $tabTemplates, $tabSearch, $tabDupes, $tabTransfer, $tabBulk))
# ══════════════════════════════════════════════════════════════════════════════
# Tab 8 Structure (folder tree from CSV)
# ══════════════════════════════════════════════════════════════════════════════
$tabStruct = New-Object System.Windows.Forms.TabPage
$tabStruct.Text = T "tab.structure"
# ── CSV import ─────────────────────────────────────────────────────────────
$grpStructCsv = New-Group (T "grp.struct.csv") 10 4 620 60
$lblStructDesc = New-Object System.Windows.Forms.Label
$lblStructDesc.Text = T "lbl.struct.desc"
$lblStructDesc.Location = New-Object System.Drawing.Point(10, 18)
$lblStructDesc.Size = New-Object System.Drawing.Size(460, 32)
$btnStructCsv = New-Object System.Windows.Forms.Button
$btnStructCsv.Text = T "btn.struct.csv"
$btnStructCsv.Location = New-Object System.Drawing.Point(490, 22)
$btnStructCsv.Size = New-Object System.Drawing.Size(118, 28)
$grpStructCsv.Controls.AddRange(@($lblStructDesc, $btnStructCsv))
# ── Preview ────────────────────────────────────────────────────────────────
$grpStructPreview = New-Group (T "grp.struct.preview") 10 68 620 140
$tvStruct = New-Object System.Windows.Forms.TreeView
$tvStruct.Location = New-Object System.Drawing.Point(10, 18)
$tvStruct.Size = New-Object System.Drawing.Size(598, 112)
$tvStruct.Font = New-Object System.Drawing.Font("Segoe UI", 9)
$tvStruct.ShowLines = $true
$tvStruct.ShowPlusMinus = $true
$grpStructPreview.Controls.Add($tvStruct)
# ── Target ─────────────────────────────────────────────────────────────────
$grpStructTarget = New-Group (T "grp.struct.target") 10 212 620 52
$lblStructLib = New-Object System.Windows.Forms.Label
$lblStructLib.Text = T "lbl.struct.library"
$lblStructLib.Location = New-Object System.Drawing.Point(10, 22)
$lblStructLib.Size = New-Object System.Drawing.Size(120, 20)
$txtStructLib = New-Object System.Windows.Forms.TextBox
$txtStructLib.Location = New-Object System.Drawing.Point(134, 20)
$txtStructLib.Size = New-Object System.Drawing.Size(300, 22)
$txtStructLib.PlaceholderText = T "ph.struct.library"
$grpStructTarget.Controls.AddRange(@($lblStructLib, $txtStructLib))
# ── Buttons ────────────────────────────────────────────────────────────────
$btnStructCreate = New-ActionBtn (T "btn.struct.create") 10 270 ([System.Drawing.Color]::FromArgb(0, 120, 212))
$btnStructCreate.Size = New-Object System.Drawing.Size(200, 34)
$btnStructClear = New-Object System.Windows.Forms.Button
$btnStructClear.Text = T "btn.struct.clear"
$btnStructClear.Location = New-Object System.Drawing.Point(220, 270)
$btnStructClear.Size = New-Object System.Drawing.Size(90, 34)
$tabStruct.Controls.AddRange(@($grpStructCsv, $grpStructPreview, $grpStructTarget, $btnStructCreate, $btnStructClear))
$tabs.TabPages.AddRange(@($tabPerms, $tabStorage, $tabTemplates, $tabSearch, $tabDupes, $tabTransfer, $tabBulk, $tabStruct))
# ── Progress bar ───────────────────────────────────────────────────────────────
$progressBar = New-Object System.Windows.Forms.ProgressBar
@@ -3793,6 +3864,11 @@ $_reg = {
& $_reg $script:i18nMap $btnBulkRemove "btn.bulk.remove"
& $_reg $script:i18nMap $btnBulkClear "btn.bulk.clear"
& $_reg $script:i18nMap $btnBulkCreate "btn.bulk.create"
& $_reg $script:i18nMap $lblStructDesc "lbl.struct.desc"
& $_reg $script:i18nMap $btnStructCsv "btn.struct.csv"
& $_reg $script:i18nMap $lblStructLib "lbl.struct.library"
& $_reg $script:i18nMap $btnStructCreate "btn.struct.create"
& $_reg $script:i18nMap $btnStructClear "btn.struct.clear"
# Tab pages
& $_reg $script:i18nTabs $tabPerms "tab.perms"
@@ -3802,6 +3878,7 @@ $_reg = {
& $_reg $script:i18nTabs $tabDupes "tab.dupes"
& $_reg $script:i18nTabs $tabTransfer "tab.transfer"
& $_reg $script:i18nTabs $tabBulk "tab.bulk"
& $_reg $script:i18nTabs $tabStruct "tab.structure"
# Menu items
& $_reg $script:i18nMenus $menuSettings "menu.settings"
@@ -3816,6 +3893,7 @@ $script:i18nPlaceholders = [System.Collections.Generic.Dictionary[string,object]
& $_reg $script:i18nPlaceholders $txtSrchModBy "ph.modified.by"
& $_reg $script:i18nPlaceholders $txtSrchLib "ph.library"
& $_reg $script:i18nPlaceholders $txtDupLib "ph.dup.lib"
& $_reg $script:i18nPlaceholders $txtStructLib "ph.struct.library"
& $_reg $script:i18nPlaceholders $txtXferSrcSite "ph.xfer.site"
& $_reg $script:i18nPlaceholders $txtXferSrcLib "ph.xfer.library"
& $_reg $script:i18nPlaceholders $txtXferDstSite "ph.xfer.site"
@@ -5578,6 +5656,125 @@ $btnBulkCreate.Add_Click({
#endregion
#region ===== Structure (folder tree from CSV) =====
# Store the parsed folder paths
$script:_StructPaths = @()
function Build-StructTree([string]$csvPath) {
# Auto-detect delimiter
$raw = Get-Content $csvPath -Raw
$delim = if ($raw -match ';') { ';' } else { ',' }
$rows = Import-Csv $csvPath -Delimiter $delim
$paths = [System.Collections.Generic.List[string]]::new()
foreach ($r in $rows) {
$cols = @($r.PSObject.Properties | ForEach-Object { "$($_.Value)".Trim() })
# Build path from non-empty columns
$parts = @($cols | Where-Object { $_ -ne '' })
if ($parts.Count -gt 0) {
# Add all intermediate paths to ensure parents exist
for ($i = 1; $i -le $parts.Count; $i++) {
$p = ($parts[0..($i-1)] -join '/')
if (-not $paths.Contains($p)) { $paths.Add($p) }
}
}
}
$script:_StructPaths = @($paths | Sort-Object)
return $script:_StructPaths
}
function Populate-StructTreeView([string[]]$paths) {
$tvStruct.Nodes.Clear()
$nodeMap = @{}
foreach ($p in $paths) {
$parts = $p -split '/'
$parentKey = if ($parts.Count -gt 1) { ($parts[0..($parts.Count - 2)] -join '/') } else { '' }
$name = $parts[-1]
$node = New-Object System.Windows.Forms.TreeNode($name)
$node.Tag = $p
if ($parentKey -and $nodeMap.ContainsKey($parentKey)) {
$nodeMap[$parentKey].Nodes.Add($node) | Out-Null
} else {
$tvStruct.Nodes.Add($node) | Out-Null
}
$nodeMap[$p] = $node
}
$tvStruct.ExpandAll()
}
$btnStructCsv.Add_Click({
$ofd = New-Object System.Windows.Forms.OpenFileDialog
$ofd.Filter = "CSV (*.csv)|*.csv|All (*.*)|*.*"
if ($ofd.ShowDialog($form) -ne "OK") { return }
try {
$paths = Build-StructTree $ofd.FileName
Populate-StructTreeView $paths
Write-Log "$($paths.Count) folder(s) loaded from CSV." "LightGreen"
} catch {
Write-Log "CSV error: $($_.Exception.Message)" "Red"
}
})
$btnStructClear.Add_Click({
$tvStruct.Nodes.Clear()
$script:_StructPaths = @()
Write-Log "Structure cleared." "Gray"
})
$btnStructCreate.Add_Click({
$siteUrl = $txtSiteUrl.Text.Trim()
$clientId = $txtClientId.Text.Trim()
$library = $txtStructLib.Text.Trim()
if (-not $siteUrl) { Write-Log "Site URL required." "Red"; return }
if (-not $clientId) { Write-Log "Client ID required." "Red"; return }
if (-not $library) { Write-Log "Target library required." "Red"; return }
if ($script:_StructPaths.Count -eq 0) { Write-Log "No structure loaded. Load a CSV first." "Red"; return }
$btnStructCreate.Enabled = $false
$btnStructCsv.Enabled = $false
Start-ProgressAnim
Write-Log "=== CREATING FOLDER STRUCTURE ===" "White"
Write-Log "Target: $siteUrl / $library" "Gray"
Write-Log "Folders to create: $($script:_StructPaths.Count)" "Gray"
Write-Log ("-" * 52) "DarkGray"
try {
Connect-PnPOnline -Url $siteUrl -Interactive -ClientId $clientId
# Get the library root
$list = Get-PnPList -Identity $library -ErrorAction Stop
$rf = Get-PnPProperty -ClientObject $list -Property RootFolder
$base = $rf.ServerRelativeUrl.TrimEnd('/')
$ok = 0
$err = 0
$total = $script:_StructPaths.Count
foreach ($p in $script:_StructPaths) {
$folderPath = "$base/$p"
try {
# Resolve-PnPFolder creates the full path recursively
Resolve-PnPFolder -SiteRelativePath "$library/$p" -ErrorAction Stop | Out-Null
$ok++
Write-Log " OK: $p" "LightGreen"
} catch {
$err++
Write-Log " FAIL: $p$($_.Exception.Message)" "Red"
}
}
Write-Log "=== STRUCTURE COMPLETE: $ok OK, $err error(s) ===" "White"
} catch {
Write-Log "Error: $($_.Exception.Message)" "Red"
} finally {
$btnStructCreate.Enabled = $true
$btnStructCsv.Enabled = $true
Stop-ProgressAnim
}
})
#endregion
# ── Initialisation : chargement des settings ───────────────────────────────
$_settings = Load-Settings
$script:DataFolder = if ($_settings.dataFolder -and (Test-Path $_settings.dataFolder)) {

View File

@@ -0,0 +1,20 @@
Level1;Level2;Level3;Level4
Administration;;;
Administration;Comptabilite;;
Administration;Comptabilite;Factures;
Administration;Comptabilite;Bilans;
Administration;Ressources Humaines;;
Administration;Ressources Humaines;Contrats;
Administration;Ressources Humaines;Fiches de paie;
Projets;;;
Projets;Projet Alpha;;
Projets;Projet Alpha;Documents;
Projets;Projet Alpha;Livrables;
Projets;Projet Beta;;
Projets;Projet Beta;Documents;
Communication;;;
Communication;Interne;;
Communication;Interne;Notes de service;
Communication;Externe;;
Communication;Externe;Communiques de presse;
Communication;Externe;Newsletter;
1 Level1 Level2 Level3 Level4
2 Administration
3 Administration Comptabilite
4 Administration Comptabilite Factures
5 Administration Comptabilite Bilans
6 Administration Ressources Humaines
7 Administration Ressources Humaines Contrats
8 Administration Ressources Humaines Fiches de paie
9 Projets
10 Projets Projet Alpha
11 Projets Projet Alpha Documents
12 Projets Projet Alpha Livrables
13 Projets Projet Beta
14 Projets Projet Beta Documents
15 Communication
16 Communication Interne
17 Communication Interne Notes de service
18 Communication Externe
19 Communication Externe Communiques de presse
20 Communication Externe Newsletter

View File

@@ -137,5 +137,18 @@
"bulk.status.pending": "En attente",
"bulk.status.creating": "Création...",
"bulk.status.ok": "OK",
"bulk.status.error": "Erreur"
"bulk.status.error": "Erreur",
"tab.structure": " Structure ",
"grp.struct.csv": "Import CSV",
"lbl.struct.desc": "Importez un CSV pour créer une arborescence. Chaque colonne représente un niveau de profondeur.",
"btn.struct.csv": "Charger CSV...",
"grp.struct.preview": "Aperçu",
"grp.struct.target": "Cible",
"lbl.struct.library": "Bibliothèque cible :",
"ph.struct.library": "Documents partagés",
"btn.struct.create": "Créer l'arborescence",
"btn.struct.clear": "Effacer",
"struct.col.path": "Chemin complet",
"struct.col.depth": "Profondeur"
}