Home
»
SharePoint
»
Automatiser la création de bibliothèques SharePoint à l’aide de PowerShell et EXCEL
,

Automatiser la création de bibliothèques SharePoint à l’aide de PowerShell et EXCEL

Une réorganisation documentaire, trois sites SharePoint, une quarantaine de bibliothèques et près de trois cents dossiers à créer. À la main, dans l’interface, c’est deux jours de clics et au moins une faute de frappe qui remontera six mois plus tard, quand quelqu’un cherchera Procédures qualité dans un dossier orthographié Procedures qualite. Ce guide montre comment déléguer ce travail à un script PowerShell piloté par un simple fichier Excel — celui que le métier remplit déjà.

Le résultat : la structure documentaire complète est provisionnée en quelques minutes, elle est rejouable à l’identique sur un autre tenant, et la source de vérité reste un classeur que n’importe quel chef de projet sait modifier.

Points clés

  • Une ligne Excel = une bibliothèque + son arborescence complète de dossiers.
  • Depuis septembre 2024, PnP.PowerShell exige votre propre application Entra ID : l’app multi-tenant PnP Management Shell a été supprimée.
  • Resolve-PnPFolder crée toute la hiérarchie manquante en un appel — il remplace à lui seul la boucle récursive que la plupart des scripts réinventent.
  • SharePoint Online n’interdit que neuf caractères (" * : < > ? / \ |). Nettoyer plus que ça abîme les noms métier pour rien.
  • Le chemin décodé complet est plafonné à 400 caractères, nom de domaine exclu : à vérifier avant de créer, pas après.

Ce que le script automatise

Le principe tient en trois blocs : un classeur Excel décrit la cible, PnP.PowerShell traduit chaque ligne en appels SharePoint, et le tenant se retrouve provisionné. Aucune interface, aucun clic, et surtout aucune divergence entre ce que le métier a validé dans le tableau et ce qui existe réellement dans SharePoint.

De la matrice Excel aux bibliothèques SharePoint SOURCE Matrice Excel SiteUrl · LibraryName Description · Folders MOTEUR PnP.PowerShell New-PnPList Resolve-PnPFolder CIBLE SharePoint Online Bibliothèques Arborescence de dossiers Le classeur reste la source de vérité : on rejoue le script, on obtient la même structure.


Prérequis

Comptez une vingtaine de minutes pour la mise en place initiale, dont l’essentiel part dans l’enregistrement de l’application Entra ID. Les exécutions suivantes ne demandent plus qu’une authentification interactive.

  • PowerShell 7.4 ou supérieur. PnP.PowerShell v3 repose sur .NET 8 et refuse de se charger sur Windows PowerShell 5.1.
  • Module PnP.PowerShell 3.x.
  • Module ImportExcel 7.8.x de Douglas Finke — il lit les fichiers .xlsx sans qu’Excel soit installé sur la machine.
  • Un compte disposant du rôle permettant de créer des listes sur les sites cibles (propriétaire de site suffit).
  • Le droit d’enregistrer une application dans Entra ID, ou un administrateur qui le fera pour vous.
POWERSHELL
# Vérifier la version : 7.4 minimum pour PnP.PowerShell v3
$PSVersionTable.PSVersion

Install-Module -Name PnP.PowerShell -Scope CurrentUser
Install-Module -Name ImportExcel    -Scope CurrentUser

Import-Module PnP.PowerShell
Import-Module ImportExcel

Si Install-Module échoue sur la politique d’exécution, lancez d’abord Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.

Étape 1 — Enregistrer votre application Entra ID

Cette étape n’est plus optionnelle. L’application multi-tenant PnP Management Shell, qui servait d’identité par défaut à tous les scripts PnP, a été supprimée le 9 septembre 2024 pour des raisons de sécurité. Depuis, chaque organisation doit enregistrer sa propre application Entra ID et passer son identifiant à Connect-PnPOnline (documentation PnP PowerShell). Un script qui appelle Connect-PnPOnline -Url … -Interactive sans -ClientId ne fonctionne plus.

Bonne nouvelle : PnP fournit le cmdlet qui fait le travail à votre place.

POWERSHELL
# À lancer une seule fois par tenant, avec un compte habilité
Register-PnPEntraIDAppForInteractiveLogin `
    -ApplicationName 'PnP.PowerShell.Provisioning' `
    -Tenant 'contoso.onmicrosoft.com'

# Le cmdlet retourne l'AppId : conservez-le, c'est votre -ClientId

Par défaut, l’application demande le consentement pour quatre permissions déléguées : AllSites.FullControl, Group.ReadWrite.All, User.ReadWrite.All et TermStore.ReadWrite.All. Pour un usage limité au provisionnement de bibliothèques, restreignez-les via les paramètres -SharePointDelegatePermissions et -GraphDelegatePermissions : AllSites.Manage couvre la création de listes et de dossiers sans donner le contrôle total du tenant.

Étape 2 — Construire la matrice Excel

Quatre colonnes suffisent, et c’est volontaire : plus le classeur est simple, plus le métier le remplit sans erreur. La colonne Folders porte toute l’intelligence de l’arborescence, avec deux séparateurs à retenir.

ColonneRôleExemple
SiteUrlSite SharePoint ciblehttps://contoso.sharepoint.com/sites/Operations
LibraryNameTitre de la bibliothèqueQUALITE
DescriptionDescription affichée (facultative)Documentation du système qualité
FoldersArborescence — ; sépare les branches, / les niveauxAudits/Rapports;Procédures/Modes opératoires

Une ligne comme Audits/Rapports;Audits/Plans d'action;Procédures/Modes opératoires produit deux dossiers racine (Audits et Procédures) et trois sous-dossiers. Inutile de déclarer les dossiers parents séparément : ils sont créés au passage.

Attention au point-virgule. Si vos noms de dossiers métier contiennent déjà des points-virgules, changez le séparateur dans le script (par exemple |) plutôt que de demander au métier de modifier sa nomenclature.

Étape 3 — Lire le classeur et créer les bibliothèques

La lecture du classeur tient en une ligne avec Import-Excel, qui renvoie un objet PowerShell par ligne, avec les en-têtes comme propriétés. Le point important vient juste après : on regroupe les lignes par site pour n’ouvrir qu’une seule connexion par site, et on inventorie les listes existantes une bonne fois pour toutes.

POWERSHELL
$rows = Import-Excel -Path $ExcelPath -WorksheetName $WorksheetName |
    Where-Object { $_.SiteUrl -and $_.LibraryName }   # ignore les lignes vides

foreach ($group in $rows | Group-Object -Property SiteUrl) {

    Connect-PnPOnline -Url $group.Name -Interactive -ClientId $ClientId

    # Un seul appel réseau, réutilisé pour toutes les lignes du site
    $existingLists = @((Get-PnPList).Title)

    foreach ($row in $group.Group) {
        $libName = ConvertTo-SharePointSafeName -Name $row.LibraryName

        if ($existingLists -contains $libName) {
            Write-Host "  = $libName (existe déjà)"
            continue
        }

        $newList = New-PnPList -Title $libName -Template DocumentLibrary -OnQuickLaunch
        if ($row.Description) {
            Set-PnPList -Identity $newList.Title -Description ([string]$row.Description)
        }
        $existingLists += $libName
    }

    Disconnect-PnPOnline
}

Le filtre Where-Object mérite son existence : les classeurs remplis par des humains contiennent presque toujours des lignes fantômes en bas de tableau, invisibles à l’écran mais bien présentes pour Import-Excel. Sans ce filtre, le script tente de se connecter à une URL vide et s’arrête net.

Étape 4 — Créer l’arborescence en un seul appel

C’est ici que la plupart des scripts trouvés en ligne se compliquent inutilement. Ils découpent le chemin, testent l’existence de chaque niveau, reconstruisent le chemin parent, appellent Add-PnPFolder niveau par niveau. Une trentaine de lignes de logique récursive, avec les bugs de découpage qui vont avec.

Resolve-PnPFolder fait tout cela en un appel. La documentation PnP est explicite : le cmdlet « retourne un dossier à partir d’un chemin relatif au site, et le crée s’il n’existe pas » — hiérarchie complète incluse (Resolve-PnPFolder, PnP PowerShell). Il est idempotent par nature : rejoué sur une arborescence existante, il ne fait rien et ne lève pas d’erreur.

POWERSHELL
foreach ($rawPath in ([string]$row.Folders -split ';')) {

    if ([string]::IsNullOrWhiteSpace($rawPath)) { continue }

    # Chaque niveau est nettoyé séparément, puis le chemin est reconstruit
    $segments = $rawPath.Split('/') |
        ForEach-Object { ConvertTo-SharePointSafeName -Name $_ } |
        Where-Object   { $_ }

    $target = "$libName/$($segments -join '/')"

    # Crée toute la hiérarchie manquante, ne fait rien si elle existe
    Resolve-PnPFolder -SiteRelativePath $target | Out-Null
}

Nettoyer chaque segment avant de rejoindre le chemin, et non le chemin entier, est le détail qui évite le bug le plus vicieux de cet exercice : si la fonction de nettoyage remplace les /, l’appliquer au chemin complet écrase la structure de dossiers et produit un unique dossier au nom absurde.

Étape 5 — Nettoyer les noms sans les mutiler

Les scripts de provisionnement héritent souvent d’une liste de caractères interdits recopiée d’un article de 2013. Résultat : Facturation 50% devient Facturation 50_, et le métier ne retrouve plus son dossier. La liste réelle est bien plus courte.

Microsoft documente neuf caractères refusés dans les noms de fichiers et de dossiers OneDrive et SharePoint : " * : < > ? / \ |, auxquels s’ajoutent les espaces en début et fin de nom, la séquence _vti_ et une poignée de noms réservés (CON, PRN, AUX, NUL, COM0 à COM9, LPT0 à LPT9, desktop.ini, .lock) — voir la page Restrictions et limitations dans OneDrive et SharePoint.

CaractèreStatut réelCe qu’il faut faire
" * : < > ? / \ |RefusésRemplacer par un tiret
# et %Autorisés dans les bibliothèques de documents depuis 2017Ne rien toucher
. (point)Autorisé, sauf en première ou dernière positionRogner les points aux extrémités uniquement
~ & { } 'AutorisésNe rien toucher
Espace initial ou finalRefusé.Trim()

Le support de # et % dans les noms de fichiers et de dossiers a été annoncé par Microsoft en 2017 et activé par défaut pour les nouveaux tenants dès juin 2017 (Microsoft Community Hub). Concrètement : si votre fonction de nettoyage remplace encore ces deux caractères, elle dégrade des noms parfaitement valides depuis presque dix ans. La restriction subsiste en dehors des bibliothèques de documents — noms de sites et objets web — mais pas pour les dossiers que vous créez ici.

POWERSHELL
function ConvertTo-SharePointSafeName {
    [OutputType([string])]
    param([Parameter(Mandatory)][AllowEmptyString()][string]$Name)

    if ([string]::IsNullOrWhiteSpace($Name)) { return '' }

    # 1. Les neuf caractères réellement refusés
    $clean = $Name -replace '["*:<>?/\\|]', '-'

    # 2. Espaces et points en début / fin
    $clean = $clean.Trim().Trim('.').Trim()

    # 3. Séquence _vti_ interdite partout
    $clean = $clean -replace '_vti_', 'vti'

    # 4. Noms réservés hérités de MS-DOS
    if ($clean -match '^(\.lock|CON|PRN|AUX|NUL|COM[0-9]|LPT[0-9]|desktop\.ini)$') {
        $clean = "$clean-1"
    }

    # 5. Un segment ne dépasse pas 255 caractères
    if ($clean.Length -gt 255) { $clean = $clean.Substring(0, 255).Trim() }

    return $clean
}

Le budget des 400 caractères

La contrainte qui casse les provisionnements ambitieux n’est pas la liste des caractères : c’est la longueur du chemin. Le chemin décodé complet — nom du site, nom de la bibliothèque, dossiers et nom de fichier — ne peut pas dépasser 400 caractères pour OneDrive et SharePoint dans Microsoft 365, le nom de domaine n’étant pas compté. Chaque nom de fichier ou de dossier est par ailleurs limité à 255 caractères.

Le compte monte vite. Un chemin de site, une bibliothèque au nom explicite et trois niveaux de sous-dossiers consomment facilement une centaine de caractères avant même qu’un document soit déposé. Il ne reste alors plus grand-chose pour les noms de fichiers que produiront les utilisateurs — et ce sont eux qui déclencheront l’erreur, six mois après votre livraison. D’où l’intérêt de contrôler la longueur avant d’appeler Resolve-PnPFolder : mieux vaut refuser le dossier et le tracer dans le journal que laisser une arborescence provisionnée à moitié.

Les cinq pièges du script qui « marche quand même »

La version initiale de ce script tournait correctement en démonstration, sur trois bibliothèques et neuf dossiers. Passée sur une matrice de production, elle a révélé cinq défauts qu’on retrouve dans la quasi-totalité des scripts de provisionnement partagés en ligne. Les voici, du plus sournois au plus visible.

1. La variable écrasée au milieu de la boucle

Une ligne $libName = $libName.Replace("'","") placée à l’intérieur de la boucle de création des dossiers, pour contourner un problème d’apostrophe dans les URL. Le problème : la variable est modifiée de façon persistante. Le premier dossier est créé dans Audits d'exploitation, les suivants dans Audits dexploitation — une bibliothèque qui n’existe pas. L’erreur ne se déclenche que sur les noms contenant une apostrophe et seulement à partir du deuxième dossier, ce qui la rend très difficile à reproduire.

Correctif : ne jamais réassigner une variable de boucle externe depuis une boucle interne. Nettoyez le nom une seule fois, au moment de le lire.

2. Une reconnexion par ligne de tableau

Connect-PnPOnline appelé dans la boucle principale rouvre une session pour chaque bibliothèque, y compris quand quarante lignes visent le même site. En authentification interactive, cela peut déclencher une invite de connexion par ligne. Le Group-Object -Property SiteUrl de l’étape 3 supprime le problème.

3. Get-PnPList appelé pour chaque bibliothèque

Tester l’existence d’une liste avec Get-PnPList | Where-Object { $_.Title -eq $libName } télécharge l’intégralité des listes du site à chaque itération. Sur un site qui en compte cinquante, c’est cinquante allers-retours réseau pour une information qu’on pouvait récupérer une fois. Le même raisonnement vaut pour Get-PnPFolderItem utilisé comme test d’existence de dossier : Resolve-PnPFolder rend ce test inutile.

4. Aucune gestion d’erreur

Sans try/catch, une seule ligne problématique — un site supprimé, un droit manquant, un nom trop long — interrompt tout le traitement. Vous découvrez alors que vingt bibliothèques sur quarante ont été créées, sans savoir lesquelles. Un bloc try/catch par ligne, plus un journal CSV en sortie, transforme un incident en simple relance : le script étant idempotent, il reprend là où il en était.

5. Pas de mode simulation

Un script qui crée des objets sur un tenant de production sans possibilité de tourner à blanc est une invitation à l’accident. [CmdletBinding(SupportsShouldProcess)] et un appel à $PSCmdlet.ShouldProcess() avant chaque création donnent gratuitement le paramètre -WhatIf. Une matrice de quarante lignes se relit alors en dix secondes, avant d’engager quoi que ce soit.

Réflexe à prendre : avant toute exécution sur un tenant client, lancez le script avec -WhatIf et faites relire la sortie par la personne qui a rempli le classeur. C’est le moment le moins coûteux pour corriger une nomenclature.

Assembler la version de production

Les extraits des étapes 3 à 5 se combinent en un script unique. La version consolidée ajoute six garde-fous : regroupement des lignes par site, inventaire des listes en un appel, nettoyage conforme aux règles réelles de SharePoint, contrôle de la longueur de chemin avant création, bloc try/catch par ligne et journal CSV en sortie. Déclarez-la avec [CmdletBinding(SupportsShouldProcess)] et encadrez chaque création d’un $PSCmdlet.ShouldProcess() : vous héritez gratuitement de -WhatIf.

L’ordre d’exécution est ensuite toujours le même : une passe à blanc, relue par la personne qui a rempli le classeur, puis la passe réelle.

POWERSHELL
# 1. Simulation : rien n'est créé, tout est affiché
.\New-SPOLibraryStructure.ps1 -ExcelPath .\Matrice.xlsx `
    -ClientId '00000000-0000-0000-0000-000000000000' -WhatIf

# 2. Exécution réelle, avec journal horodaté
.\New-SPOLibraryStructure.ps1 -ExcelPath .\Matrice.xlsx `
    -WorksheetName 'Bibliotheques' `
    -ClientId '00000000-0000-0000-0000-000000000000'

Vérifier le résultat

Le journal CSV donne le premier niveau de contrôle : une ligne par objet, avec son statut. Pour une vérification côté tenant, deux commandes suffisent.

POWERSHELL
Connect-PnPOnline -Url 'https://contoso.sharepoint.com/sites/Operations' `
    -Interactive -ClientId $ClientId

# Les bibliothèques créées (BaseTemplate 101 = bibliothèque de documents)
Get-PnPList | Where-Object BaseTemplate -eq 101 |
    Select-Object Title, ItemCount, DefaultViewUrl

# L'arborescence d'une bibliothèque
Get-PnPFolderItem -FolderSiteRelativeUrl 'QUALITE' -ItemType Folder -Recursive |
    Select-Object Name, ServerRelativeUrl

Vérifiez aussi que les bibliothèques apparaissent bien dans le lancement rapide si vous avez utilisé -OnQuickLaunch, et que les descriptions ont été appliquées : Set-PnPList échoue silencieusement si la valeur passée est $null, d’où le transtypage [string] dans le script.

Dépannage : les erreurs les plus fréquentes

SymptômeCauseCorrectif
AADSTS700016: Application not found in the directory-ClientId absent ou pointant vers l’ancienne app PnP Management ShellEnregistrer votre application avec Register-PnPEntraIDAppForInteractiveLogin
The term 'Connect-PnPOnline' is not recognizedSession lancée dans Windows PowerShell 5.1Ouvrir pwsh (PowerShell 7.4+)
Import-Excel: Worksheet 'Feuil1' not foundNom de feuille différent, souvent lié à la langue d’ExcelPasser le vrai nom via -WorksheetName
File Not Found sur Resolve-PnPFolderLe nom de bibliothèque utilisé dans le chemin ne correspond pas au titre réelReconstruire le chemin à partir de $newList.Title, pas de la valeur Excel brute
The specified name is already in useUne liste porte déjà ce titre après nettoyage (deux noms Excel différents ont convergé)Vérifier les doublons dans la matrice avant exécution
Vue de bibliothèque en erreur au-delà de 5 000 élémentsSeuil d’affichage des listes SharePoint OnlineFiltrer et indexer les vues ; le seuil de 5 000 n’est pas modifiable dans SharePoint Online

Ce dernier point mérite une précision : une bibliothèque SharePoint Online peut stocker jusqu’à 30 millions d’éléments, mais une vue ne peut en afficher que 5 000 à la fois (Microsoft Support). Concevoir l’arborescence, c’est donc aussi anticiper la répartition des documents : un dossier prévu pour recevoir 20 000 factures a besoin d’un sous-découpage par année ou par mois, décidé dans le classeur Excel plutôt que six mois après la mise en service.

Questions fréquentes

Peut-on créer des bibliothèques SharePoint sans droits d’administrateur global ?

Oui. La création de listes et de dossiers relève des permissions du site : être propriétaire des sites cibles suffit. En revanche, l’enregistrement initial de l’application Entra ID demande un rôle habilité à créer et consentir des applications dans l’annuaire. Faites réaliser cette étape une fois par un administrateur, puis réutilisez l’AppId indéfiniment.

Faut-il Excel installé sur la machine qui exécute le script ?

Non. Le module ImportExcel de Douglas Finke lit et écrit les fichiers .xlsx sans dépendance à Microsoft Excel — sa description officielle sur PowerShell Gallery est explicite : « import/export Excel spreadsheets, without Excel ». Le script tourne donc aussi bien sur un serveur d’administration que dans un conteneur.

Le script peut-il tourner sans interaction, dans une tâche planifiée ?

Pas dans cette version : -Interactive ouvre une fenêtre d’authentification. Pour un usage non supervisé, remplacez la connexion par une authentification par certificat (Connect-PnPOnline -ClientId … -Tenant … -CertificatePath …) et attribuez les permissions d’application, et non déléguées, lors de l’enregistrement Entra ID. Le reste du script est inchangé.

Faut-il créer des dossiers ou utiliser des métadonnées ?

Les métadonnées restent préférables pour le classement et la recherche. Les dossiers gardent deux avantages concrets : ils portent des permissions spécifiques et ils correspondent au modèle mental des utilisateurs qui arrivent d’un lecteur réseau. En migration, une arborescence reconnaissable réduit fortement la résistance au changement — quitte à ajouter des colonnes de métadonnées dans un second temps.

Comment gérer les bibliothèques qui existent déjà ?

Le script les détecte et passe à la suite, sans les modifier, puis complète leur arborescence si des dossiers manquent. C’est ce qui le rend rejouable : ajoutez une ligne à la matrice, relancez, seul le delta est créé. Pour forcer une mise à jour de description ou de paramètres sur les bibliothèques existantes, ajoutez un appel Set-PnPList dans la branche « existe déjà ».

Pour aller plus loin

Ce script résout un problème précis : transformer une décision d’architecture documentaire, prise dans un tableau, en structure SharePoint réelle et reproductible. Sa vraie valeur n’est pas le gain de temps sur la première exécution — c’est la capacité à rejouer la même structure sur un site de recette, puis en production, puis chez le client suivant, sans écart.

  • Ajoutez une colonne ContentType à la matrice pour associer un type de contenu à chaque bibliothèque.
  • Complétez avec Set-PnPListPermission pour provisionner aussi les droits, à partir d’une colonne Groupe.
  • Versionnez le classeur avec le script : la matrice devient la documentation de l’architecture documentaire.

Vous automatisez Microsoft 365 au quotidien ? Recevez les prochains scripts et retours de terrain directement par mail — un envoi, pas de bruit. [CTA : inscription newsletter powerplatform-addict.com]

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

un + onze =

Suggestions : PowerShell Syntex Migration Échap pour fermer