@@ -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,61 @@ $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 + target (single row) ───────────────────────────────────────
$grpStructCsv = New-Group ( T " grp.struct.csv " ) 10 4 620 52
$lblStructDesc = New-Object System . Windows . Forms . Label
$lblStructDesc . Text = T " lbl.struct.desc "
$lblStructDesc . Location = New-Object System . Drawing . Point ( 10 , 20 )
$lblStructDesc . Size = New-Object System . Drawing . Size ( 460 , 20 )
$btnStructCsv = New-Object System . Windows . Forms . Button
$btnStructCsv . Text = T " btn.struct.csv "
$btnStructCsv . Location = New-Object System . Drawing . Point ( 490 , 18 )
$btnStructCsv . Size = New-Object System . Drawing . Size ( 118 , 26 )
$grpStructCsv . Controls . AddRange ( @ ( $lblStructDesc , $btnStructCsv ) )
# ── Preview ────────────────────────────────────────────────────────────────
$grpStructPreview = New-Group ( T " grp.struct.preview " ) 10 58 620 148
$tvStruct = New-Object System . Windows . Forms . TreeView
$tvStruct . Location = New-Object System . Drawing . Point ( 10 , 18 )
$tvStruct . Size = New-Object System . Drawing . Size ( 598 , 120 )
$tvStruct . Font = New-Object System . Drawing . Font ( " Segoe UI " , 9 )
$tvStruct . ShowLines = $true
$tvStruct . ShowPlusMinus = $true
$grpStructPreview . Controls . Add ( $tvStruct )
# ── Target + Buttons (single row) ─────────────────────────────────────────
$lblStructLib = New-Object System . Windows . Forms . Label
$lblStructLib . Text = T " lbl.struct.library "
$lblStructLib . Location = New-Object System . Drawing . Point ( 12 , 214 )
$lblStructLib . Size = New-Object System . Drawing . Size ( 110 , 20 )
$txtStructLib = New-Object System . Windows . Forms . TextBox
$txtStructLib . Location = New-Object System . Drawing . Point ( 124 , 212 )
$txtStructLib . Size = New-Object System . Drawing . Size ( 200 , 22 )
$txtStructLib . PlaceholderText = T " ph.struct.library "
$btnStructCreate = New-ActionBtn ( T " btn.struct.create " ) 340 208 ( [ System.Drawing.Color ] :: FromArgb ( 0 , 120 , 212 ) )
$btnStructCreate . Size = New-Object System . Drawing . Size ( 180 , 30 )
$btnStructClear = New-Object System . Windows . Forms . Button
$btnStructClear . Text = T " btn.struct.clear "
$btnStructClear . Location = New-Object System . Drawing . Point ( 528 , 208 )
$btnStructClear . Size = New-Object System . Drawing . Size ( 90 , 30 )
$tabStruct . Controls . AddRange ( @ ( $grpStructCsv , $grpStructPreview , $lblStructLib , $txtStructLib , $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 +3859,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 +3873,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 +3888,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 +5651,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 ) ) {