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)) {