faitza.com
faitza.com
> blog_tech

Gérer SharePoint avec PowerShell et Microsoft Graph

Calcul... powershell sharepoint microsoft-graph microsoft-365
SharePoint + PowerShell + Microsoft Graph

Introduction

Faitza_Sharepoint.ps1 automatise les opérations courantes sur SharePoint Online via l'API Microsoft Graph : dépôt de rapports, récupération de fichiers de référence, navigation dans les bibliothèques et lecture/écriture de listes (CMDB, catalogue, registre…).

Prérequis

Toutes les fonctions utilisent Get-Faitza_Modul_Headers -resource "graph" pour obtenir un Bearer token valide. Voir l'article dédié : Get-Faitza_Modul_Headers — Token Bearer pour les APIs Microsoft.

Send-Faitza_Sharepoint_File

Droits requis — Sites.ReadWrite.All · Files.ReadWrite.All

Dépose un fichier local dans une bibliothèque SharePoint via l'endpoint /drive/root:/{path}:/content. Gère le cas de fichier verrouillé avec le switch -Force (suppression puis re-upload).

function Send-Faitza_Sharepoint_File {
    [CmdletBinding()]
    param (
        [string]$sp,
        [string]$lo_file,
        [string]$sp_folder = "",
        [switch]$Force
    )
    $response = $null
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Send-Faitza_SP_File <<<$($PSStyle.Reset)"

        if (-not $sp)      { throw "Il manque le nom du sp -sp" }
        if (-not $lo_file) { throw "Il manque le fichier local -lo_file" }

        $headers = Get-Faitza_Modul_Headers -resource "graph"
        $site = Invoke-RestMethod -Method Get -Headers $headers `
            -Uri "https://graph.microsoft.com/v1.0/sites/faitza.sharepoint.com:/sites/$sp"

        $fileName = Split-Path $lo_file -Leaf

        if ([string]::IsNullOrWhiteSpace($sp_folder)) {
            $sp_path = $fileName
        } else {
            $sp_path = "$($sp_folder.Trim("/"))/$fileName"
        }

        $uri_upload = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drive/root:/$($sp_path):/[email protected]=replace"
        $lo_file_bytes = [System.IO.File]::ReadAllBytes($lo_file)

        try {
            $response = Invoke-RestMethod -Method Put -Uri $uri_upload -Headers $headers -Body $lo_file_bytes -ContentType "application/octet-stream" -ErrorAction Stop
        } catch {
            if ($Force -and $_ -match "resourceLocked") {
                Write-Host "Fichier verrouillé. Tentative de suppression forcée avant re-upload..." -ForegroundColor Yellow
                $uri_item = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drive/root:/$($sp_path)"
                Invoke-RestMethod -Method Delete -Uri $uri_item -Headers $headers -ErrorAction Stop
                Write-Host "Fichier supprimé. Re-upload en cours..." -ForegroundColor Yellow
                $response = Invoke-RestMethod -Method Put -Uri $uri_upload -Headers $headers -Body $lo_file_bytes -ContentType "application/octet-stream" -ErrorAction Stop
            } else {
                throw "Fichier verrouillé"
            }
        }

        Write-Host "Fichier uploadé avec succès dans $sp : $($response.name)"
        return @{ url = $response.webUrl }
    } catch {
        throw "Erreur Send-Faitza_Sharepoint_File : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Send-Faitza_SP_File >>>$($PSStyle.Reset)"
    }
}
Send-Faitza_Sharepoint_File `
    -sp "Nom du sp" `
    -lo_file "chemin du fichier local" `
    -sp_folder "chemin du dossier sp"

Get-Faitza_Sharepoint_File

Droits requis — Sites.Read.All · Files.Read.All

Récupère un fichier depuis SharePoint. Trois modes disponibles : télécharger vers un fichier local, importer directement comme objet Excel (-excel via le module ImportExcel), ou parser directement comme JSON (-json). Sans flag, retourne le chemin local du fichier téléchargé.

function Get-Faitza_Sharepoint_File {
    [CmdletBinding()]
    param (
        [string]$sp,
        [string]$lo_file,
        [string]$sp_file,
        [switch]$excel,
        [switch]$json,
        [string[]]$WorkSheetName
    )
    $data = $null
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Sharepoint_File <<<$($PSStyle.Reset)"
        if (-not $sp)      { throw "Il manque le nom du sp -sp"              }
        if (-not $sp_file) { throw "Il manque le chemin SharePoint -sp_file" }

        if ($excel -and -not $lo_file) {
            $lo_file = Join-Path $env:AGENT_TEMPDIRECTORY "temp_graph_$(Get-Random).xlsx"
        } elseif ($json -and -not $lo_file) {
            $lo_file = Join-Path $env:AGENT_TEMPDIRECTORY "temp_graph_$(Get-Random).json"
        } elseif (-not $lo_file) {
            $sp_path_raw = $sp_file.Trim("/") -replace "(?i)^sites/$sp/", ""
            $rawFileName = Split-Path $sp_path_raw -Leaf
            $normalized  = $rawFileName.Normalize([System.Text.NormalizationForm]::FormD)
            $cleanName   = ($normalized.ToCharArray() | Where-Object { [System.Globalization.CharUnicodeInfo]::GetUnicodeCategory($_) -ne [System.Globalization.UnicodeCategory]::NonSpacingMark }) -join ''
            $cleanName   = $cleanName -replace '\s+', ' ' -replace '[^a-zA-Z0-9\s\.\-_]', ''
            $lo_file     = Join-Path $env:AGENT_TEMPDIRECTORY $cleanName.Trim()
        }

        $headers = Get-Faitza_Modul_Headers -resource "graph"
        $site = Invoke-RestMethod -Method Get -Headers $headers `
            -Uri "https://graph.microsoft.com/v1.0/sites/faitza.sharepoint.com:/sites/$sp"
        $sp_path_raw     = $sp_file.Trim("/") -replace "(?i)^sites/$sp/", ""
        $sp_path_encoded = ($sp_path_raw -split '/' | ForEach-Object { [uri]::EscapeDataString($_) }) -join '/'
        $uri_download    = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drive/root:/$($sp_path_encoded):/content"

        Invoke-RestMethod -Method Get -Uri $uri_download -Headers $headers -OutFile $lo_file

        if (-not (Test-Path -LiteralPath $lo_file)) { throw "Fichier non créé sur disque" }
        Write-Host "Fichier telecharge : $(Split-Path $lo_file -Leaf)"

        if ($excel) {
            if ($WorkSheetName -and $WorkSheetName.Count -gt 0) {
                if ($WorkSheetName.Count -eq 1) {
                    $data = Import-Excel -Path $lo_file -WorkSheetName $WorkSheetName[0]
                } else {
                    $data = @{}
                    foreach ($sheet in $WorkSheetName) {
                        Write-Host "Sheet Excel : $sheet"
                        $data[$sheet] = Import-Excel -Path $lo_file -WorkSheetName $sheet
                    }
                }
            } else {
                $data = Import-Excel -Path $lo_file
            }
            Remove-Item $lo_file -ErrorAction SilentlyContinue
        } elseif ($json) {
            $data = Get-Content -Path $lo_file -Raw | ConvertFrom-Json
            Remove-Item $lo_file -ErrorAction SilentlyContinue
        } else {
            $data = $lo_file
        }
        if ($data) { return $data }
    } catch {
        throw "Erreur Get-Faitza_Sharepoint_File : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Sharepoint_File >>>$($PSStyle.Reset)"
    }
}
Get-Faitza_Sharepoint_File
    -sp "Nom du sp" `
    -lo_file "chemin du fichier local" `
    -excel `
    -json `
    -sp_file "chemin du fichier sp"

Get-Faitza_Sharepoint_FolderIndex

Droits requis — Sites.Read.All · Files.Read.All

Liste récursivement le contenu d'un dossier SharePoint. S'appelle elle-même pour descendre dans les sous-dossiers. Gère la pagination @odata.nextLink à chaque niveau.

function Get-Faitza_Sharepoint_FolderIndex {
    [CmdletBinding()]
    param (
        [string]$sp,
        [string]$sp_folder,
        [string]$siteId
    )
    $isTopLevel = -not $siteId
    try {
        if ($isTopLevel) {
            Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_SP_FolderIndex (Recursive) <<<$($PSStyle.Reset)"
            if (-not $sp) { throw "Il manque le nom du sp -sp" }
            $headers = Get-Faitza_Modul_Headers -resource "graph"
            $site    = Invoke-RestMethod -Method Get -Headers $headers `
                -Uri "https://graph.microsoft.com/v1.0/sites/faitza.sharepoint.com:/sites/$sp"
            $siteId  = $site.id
        } else {
            $headers = Get-Faitza_Modul_Headers -resource "graph"
        }

        $path = $sp_folder.Trim("/")
        if ($path -eq "" -or $path -eq "Documents") {
            $uri = "https://graph.microsoft.com/v1.0/sites/$siteId/drive/root/children"
        } else {
            $uri = "https://graph.microsoft.com/v1.0/sites/$siteId/drive/root:/$($path):/children"
        }

        $results = @()
        do {
            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
            foreach ($item in $response.value) {
                if ($item.file) {
                    $results += $item
                } elseif ($item.folder) {
                    Write-Host "  -> Entrée dans : $($item.name)"
                    $results += Get-Faitza_Sharepoint_FolderIndex -sp $sp -sp_folder "$path/$($item.name)" -siteId $siteId
                }
            }
            $uri = $response.'@odata.nextLink'
        } while ($uri)

        return $results
    } catch {
        throw "Erreur Get-Faitza_Sharepoint_FolderIndex : $_"
    } finally {
        if ($isTopLevel) {
            Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_SP_FolderIndex >>>$($PSStyle.Reset)"
        }
    }
}
Get-Faitza_Sharepoint_FolderIndex `
    -sp "Nom du sp" `
    -sp_folder "chemin du dossier sp a indexer"

Get-Faitza_Sharepoint_List

Droits requis — Sites.Read.All

Récupère tous les items d'une liste SharePoint par son nom affiché. Résout l'ID de liste dynamiquement, gère la pagination et retourne les champs fields.* aplatis avec l'ID SharePoint de chaque item.

function Get-Faitza_Sharepoint_List {
    [CmdletBinding()]
    param (
        [string]$sp,
        [string]$sp_list
    )
    $results = @()
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Sharepoint_List <<<$($PSStyle.Reset)"
        if (-not $sp)      { throw "Il manque le nom du sp -sp"            }
        if (-not $sp_list) { throw "Il manque le nom de la liste -sp_list" }
        $headers = Get-Faitza_Modul_Headers -resource "graph"
        $site = Invoke-RestMethod -Method Get -Headers $headers `
            -Uri "https://graph.microsoft.com/v1.0/sites/faitza.sharepoint.com:/sites/$sp"
        $listQueryUri = "https://graph.microsoft.com/v1.0/sites/$($site.id)/lists?`$filter=displayName eq '$sp_list'"
        $listResponse = Invoke-RestMethod -Method Get -Uri $listQueryUri -Headers $headers
        if (-not $listResponse.value) { throw "La liste '$sp_list' est introuvable sur le site $sp" }
        $listId = $listResponse.value[0].id
        $uri    = "https://graph.microsoft.com/v1.0/sites/$($site.id)/lists/$listId/items?`$expand=fields"
        do {
            $response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
            foreach ($item in $response.value) {
                $fieldData = $item.fields
                $fieldData | Add-Member -MemberType NoteProperty -Name "SharePointItemId" -Value $item.id -Force
                $results += $fieldData
            }
            $uri = $response.'@odata.nextLink'
        } while ($uri)
        Write-Host "$($results.Count) éléments récupérés avec succès dans la liste '$sp_list'"
        return $results
    } catch {
        throw "Erreur Get-Faitza_Sharepoint_List : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Sharepoint_List >>>$($PSStyle.Reset)"
    }
}
Get-Faitza_Sharepoint_List `
    -sp "Nom du sp" `
    -sp_list "Nom de la liste"

Set-Faitza_Sharepoint_ListItem

Droits requis — Sites.ReadWrite.All

Met à jour un ou plusieurs champs d'un item de liste SharePoint via PATCH Graph. Le paramètre -sp_item_id correspond à la valeur SharePointItemId retournée par Get-Faitza_Sharepoint_List.

function Set-Faitza_Sharepoint_ListItem {
    [CmdletBinding()]
    param (
        [string]$sp,
        [string]$sp_list,
        [string]$sp_item_id,
        [hashtable]$sp_item_values
    )
    $response = $null
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Set-Faitza_Sharepoint_ListItem <<<$($PSStyle.Reset)"
        if (-not $sp)             { throw "Il manque le nom du sp -sp"                              }
        if (-not $sp_list)        { throw "Il manque le nom de la liste -sp_list"                   }
        if (-not $sp_item_id)     { throw "Il manque l'ID de l'élément -sp_item_id"                 }
        if (-not $sp_item_values) { throw "Il manque les valeurs à mettre à jour -sp_item_values"   }
        $headers = Get-Faitza_Modul_Headers -resource "graph"
        $site = Invoke-RestMethod -Method Get -Headers $headers `
            -Uri "https://graph.microsoft.com/v1.0/sites/faitza.sharepoint.com:/sites/$sp"
        $listQueryUri = "https://graph.microsoft.com/v1.0/sites/$($site.id)/lists?`$filter=displayName eq '$sp_list'"
        $listResponse = Invoke-RestMethod -Method Get -Uri $listQueryUri -Headers $headers
        if (-not $listResponse.value) { throw "La liste '$sp_list' est introuvable sur le site $sp" }
        $listId   = $listResponse.value[0].id
        $uri      = "https://graph.microsoft.com/v1.0/sites/$($site.id)/lists/$listId/items/$sp_item_id/fields"
        $jsonBody = $sp_item_values | ConvertTo-Json
        $response = Invoke-RestMethod -Method Patch -Uri $uri -Headers $headers -Body $jsonBody -ContentType "application/json"
        Write-Host "Élément $sp_item_id mis à jour avec succès dans '$sp_list'."
        return $response
    } catch {
        throw "Erreur Set-Faitza_Sharepoint_ListItem : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Set-Faitza_Sharepoint_ListItem >>>$($PSStyle.Reset)"
    }
}
Set-Faitza_Sharepoint_ListItem
    -sp "Nom du sp" `
    -sp_list "Nom de la liste" `
    -sp_item_id "ID de l'élément à modifier" `
    -sp_item_values @{"Colonne1"="Valeur1"; "Colonne2"="Valeur2"}

Remove-Faitza_Sharepoint_File

Droits requis — Sites.ReadWrite.All · Files.ReadWrite.All

Supprime un fichier depuis SharePoint via DELETE Graph. Le chemin est encodé segment par segment pour gérer les espaces et caractères spéciaux.

function Remove-Faitza_Sharepoint_File {
    [CmdletBinding()]
    param (
        [string]$sp,
        [string]$sp_file
    )
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Remove-Faitza_SP_File <<<$($PSStyle.Reset)"
        if (-not $sp)      { throw "Il manque le nom du sp -sp"              }
        if (-not $sp_file) { throw "Il manque le chemin SharePoint -sp_file" }
        $headers = Get-Faitza_Modul_Headers -resource "graph"
        $site = Invoke-RestMethod -Method Get -Headers $headers `
            -Uri "https://graph.microsoft.com/v1.0/sites/faitza.sharepoint.com:/sites/$sp"
        $sp_path_raw     = $sp_file.Trim("/") -replace "(?i)^sites/$sp/", ""
        $sp_path_encoded = ($sp_path_raw -split '/' | ForEach-Object { [uri]::EscapeDataString($_) }) -join '/'
        $uri_delete      = "https://graph.microsoft.com/v1.0/sites/$($site.id)/drive/root:/$($sp_path_encoded)"
        Invoke-RestMethod -Method Delete -Uri $uri_delete -Headers $headers
        Write-Host "Fichier supprimé avec succès dans $sp : $sp_file"
    } catch {
        throw "Erreur Remove-Faitza_Sharepoint_File : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Remove-Faitza_SP_File >>>$($PSStyle.Reset)"
    }
}
Remove-Faitza_Sharepoint_File `
    -sp "Nom du sp" `
    -sp_file "chemin/du/fichier.ext"

Pièges classiques

Encodage des chemins

Les espaces et accents dans les noms de fichiers/dossiers doivent être encodés segment par segment. La fonction utilise [uri]::EscapeDataString() sur chaque segment séparément — pas sur le chemin entier, ce qui écaserait les /.

Fichier verrouillé (resourceLocked)

SharePoint peut verrouiller un fichier ouvert en coédition. Le switch -Force de Send-Faitza_Sharepoint_File supprime le fichier existant avant de re-uploader — à utiliser uniquement en automatisation où aucun utilisateur n'est en train d'éditer le fichier.

Permissions Sites.ReadWrite.All vs Sites.Selected

Sites.ReadWrite.All donne accès à tous les sites SharePoint du tenant. Préférer Sites.Selected pour limiter l'accès aux seuls sites nécessaires.

ImportExcel requis pour -excel

Le flag -excel de Get-Faitza_Sharepoint_File utilise le module ImportExcel (Doug Finke). Il doit être installé : Install-Module ImportExcel -Scope CurrentUser.

// Commentaires

Aucun commentaire pour l'instant.