Added functionnality : you can vcreate a whole folder tree by importing a CSV (see examples fodler)
This commit is contained in:
@@ -2854,6 +2854,18 @@ $script:LangDefault = @{
|
|||||||
"bulk.status.creating" = "Creating..."
|
"bulk.status.creating" = "Creating..."
|
||||||
"bulk.status.ok" = "OK"
|
"bulk.status.ok" = "OK"
|
||||||
"bulk.status.error" = "Error"
|
"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
|
$script:Lang = $null # null = use LangDefault
|
||||||
@@ -3606,7 +3618,66 @@ $btnBulkCreate.Size = New-Object System.Drawing.Size(200, 34)
|
|||||||
|
|
||||||
$tabBulk.Controls.AddRange(@($grpBulkList, $btnBulkCreate))
|
$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 ───────────────────────────────────────────────────────────────
|
# ── Progress bar ───────────────────────────────────────────────────────────────
|
||||||
$progressBar = New-Object System.Windows.Forms.ProgressBar
|
$progressBar = New-Object System.Windows.Forms.ProgressBar
|
||||||
@@ -3793,6 +3864,11 @@ $_reg = {
|
|||||||
& $_reg $script:i18nMap $btnBulkRemove "btn.bulk.remove"
|
& $_reg $script:i18nMap $btnBulkRemove "btn.bulk.remove"
|
||||||
& $_reg $script:i18nMap $btnBulkClear "btn.bulk.clear"
|
& $_reg $script:i18nMap $btnBulkClear "btn.bulk.clear"
|
||||||
& $_reg $script:i18nMap $btnBulkCreate "btn.bulk.create"
|
& $_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
|
# Tab pages
|
||||||
& $_reg $script:i18nTabs $tabPerms "tab.perms"
|
& $_reg $script:i18nTabs $tabPerms "tab.perms"
|
||||||
@@ -3802,6 +3878,7 @@ $_reg = {
|
|||||||
& $_reg $script:i18nTabs $tabDupes "tab.dupes"
|
& $_reg $script:i18nTabs $tabDupes "tab.dupes"
|
||||||
& $_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"
|
||||||
|
|
||||||
# Menu items
|
# Menu items
|
||||||
& $_reg $script:i18nMenus $menuSettings "menu.settings"
|
& $_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 $txtSrchModBy "ph.modified.by"
|
||||||
& $_reg $script:i18nPlaceholders $txtSrchLib "ph.library"
|
& $_reg $script:i18nPlaceholders $txtSrchLib "ph.library"
|
||||||
& $_reg $script:i18nPlaceholders $txtDupLib "ph.dup.lib"
|
& $_reg $script:i18nPlaceholders $txtDupLib "ph.dup.lib"
|
||||||
|
& $_reg $script:i18nPlaceholders $txtStructLib "ph.struct.library"
|
||||||
& $_reg $script:i18nPlaceholders $txtXferSrcSite "ph.xfer.site"
|
& $_reg $script:i18nPlaceholders $txtXferSrcSite "ph.xfer.site"
|
||||||
& $_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"
|
||||||
@@ -5578,6 +5656,125 @@ $btnBulkCreate.Add_Click({
|
|||||||
|
|
||||||
#endregion
|
#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 ───────────────────────────────
|
# ── Initialisation : chargement des settings ───────────────────────────────
|
||||||
$_settings = Load-Settings
|
$_settings = Load-Settings
|
||||||
$script:DataFolder = if ($_settings.dataFolder -and (Test-Path $_settings.dataFolder)) {
|
$script:DataFolder = if ($_settings.dataFolder -and (Test-Path $_settings.dataFolder)) {
|
||||||
|
|||||||
20
examples/folder_structure.csv
Normal file
20
examples/folder_structure.csv
Normal 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;
|
||||||
|
15
lang/fr.json
15
lang/fr.json
@@ -137,5 +137,18 @@
|
|||||||
"bulk.status.pending": "En attente",
|
"bulk.status.pending": "En attente",
|
||||||
"bulk.status.creating": "Création...",
|
"bulk.status.creating": "Création...",
|
||||||
"bulk.status.ok": "OK",
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user