Les e-mails envoyés aux adresses de canaux Teams s'entassent en fichiers .eml dans SharePoint Online, parfois depuis des années, et restent à portée de Copilot. L'archivage au niveau fichier de Microsoft 365 Archive permet de les basculer en stockage archivé, fichier par fichier. Cet article fournit un script Microsoft Graph complet avec mode simulation, les prérequis, la vérification du résultat et les pièges à connaître avant de l'exécuter en production.
Pourquoi archiver les .eml des canaux Teams maintenant
L'archivage au niveau fichier de SharePoint Online est en préversion publique depuis mai 2026 et il est désormais en disponibilité générale. Les fichiers archivés ne sont plus disponibles pour la recherche ni la récupération : Microsoft 365 Copilot ne peut donc plus s'en servir pour construire ses réponses, pas plus que les outils d'IA tiers.
Les e-mails reçus par un canal Teams sont stockés dans un jeu de dossiers sous le dossier « Microsoft Teams Mailhook » de la bibliothèque du site, avec un nouveau dossier chaque mois. Ces messages sont rarement relus, mais ils sont indexés comme n'importe quel document. Un inventaire sur un petit tenant de test a pourtant dénombré des chiffres déjà significatifs :
11 765
Fichiers .eml trouvés dans les sites de groupes
Inventaire PowerShell sur un tenant de test
1,5 Go
Stockage occupé par ces fichiers
Inventaire PowerShell sur un tenant de test
0,05 $
Prix par Go et par mois du stockage archivé, contre 0,20 $ en standard
Microsoft Learn
L'enjeu n'est pas (d'abord) financier
Selon la documentation Microsoft Learn, les fichiers archivés ne réduisent pas le stockage total du tenant : ils passent de stockage actif à stockage archivé, et l'archive n'est facturée que si le quota du tenant est dépassé. Sur 1,5 Go, l'écart théorique entre 0,05 $ et 0,20 $ représente quelques dizaines de centimes par mois. Le vrai bénéfice est l'hygiène des données exposées à l'IA, dans la logique de notre article sur l'assainissement d'un tenant avant Copilot.
Prérequis, licences et permissions à valider
Selon la documentation Microsoft Learn, l'archivage au niveau fichier exige une facturation à l'usage (pay-as-you-go) configurée et Microsoft 365 Archive activé sur le tenant. Le niveau fichier est ensuite activé par défaut. Vous le contrôlez par Set-SPOTenant -AllowFileArchive, Set-SPOSite -AllowFileArchive et Set-SPOTenant -AllowFileArchiveOnNewSitesByDefault.
Si l'archivage manuel est désactivé au niveau du tenant, les politiques définies par l'administrateur peuvent quand même archiver les fichiers éligibles. Les fichiers déjà archivés restent réactivables.
Avant le premier run
- Facturation à l'usage rattachée à Microsoft 365 Archive, et Microsoft 365 Archive activé
- Archivage au niveau fichier non désactivé sur les sites ciblés (tenant ou site par site)
- Inscription d'application Microsoft Entra avec certificat et consentement administrateur pour les permissions d'application Files.ReadWrite.All (archivage), Sites.Read.All (lecture des sites) et GroupMember.Read.All (lecture des groupes)
- Module
Microsoft.Graph.Authenticationinstallé :Install-Module Microsoft.Graph.Authentication -Scope CurrentUser - Un fichier CSV listant les groupes Microsoft 365 liés à des canaux Teams avec adresse e-mail
Pour produire ce CSV, le script de rapport des adresses e-mail de canaux Teams publié sur GitHub fait le travail. Sans CSV, le script ci-dessous traite tous les groupes Microsoft 365 du tenant, ce qui est plus long et rarement souhaitable. Si vous cherchez d'abord à identifier les canaux dormants, notre guide pour détecter les canaux inactifs Teams avec PowerShell complète utilement ce CSV.
Files.ReadWrite.All en mode application couvre tous les sites du tenant. Protégez le certificat comme un secret d'administration et limitez son accès au compte d'exécution.
Comment le script parcourt le tenant
La logique suit la chaîne groupe, site, bibliothèque, dossier, fichier. Seuls les fichiers portant l'extension voulue, situés sous un dossier nommé « Microsoft Teams Mailhook » et plus anciens que le seuil (trois ans par défaut) sont envoyés à l'API d'archivage.
| Étape | Appel Microsoft Graph | Rôle |
|---|---|---|
| Site du groupe | GET /v1.0/groups/{id}/sites/root | Retrouver le site SharePoint lié au groupe |
| Bibliothèques | GET /v1.0/sites/{id}/drives | Lister les bibliothèques, hors Preservation Hold Library et Site Assets |
| Énumération | GET /beta/drives/{id}/root/children puis /items/{id}/children | Descendre l’arborescence et lire lastModifiedDateTime et archiveStatus |
| Archivage | POST /beta/drives/{id}/items/{id}/archive | Archiver un fichier éligible |
Le guide développeur Microsoft Learn précise que les API d'énumération renvoient les éléments archivés avec leurs seules métadonnées, et que archiveStatus est vide pour un fichier actif. Le script s'appuie sur ce comportement pour ignorer ce qui est déjà archivé, ce qui rend les exécutions suivantes beaucoup plus courtes.
Autre point utile pour dimensionner le traitement : pour un fichier unique en requête synchrone, l'archivage est immédiat (réponse 204). L'en-tête Prefer: respond-async n'est pas pris en charge pour un fichier. Pour un dossier, l'API renvoie un 202 avec une URL de suivi. Archiver directement chaque dossier mensuel est donc une piste à tester si le volume d'appels pose problème, mais le script ci-dessous reste volontairement au niveau fichier pour garder le contrôle sur le seuil d'âge.
Mise en œuvre
Le script ci-dessous est autonome. Il utilise uniquement Connect-MgGraph et Invoke-MgGraphRequest du module Microsoft.Graph.Authentication, avec gestion de la pagination et de la limitation de débit (nouvelle tentative sur les erreurs transitoires). Il produit un résumé à l'écran et un rapport CSV détaillant chaque fichier traité.
API beta : simulez d'abord, et relisez l'endpoint
La page de référence driveItem: archive reste en version beta, et Microsoft indique que les API beta ne sont pas prises en charge en production. Comparez le chemin de la variable $ArchiveUriTemplate avec cette page avant de l'exécuter. Lancez toujours une première passe avec -DryRun : une fois archivé, un fichier réactivé ne peut plus être archivé pendant 120 jours, et la réactivation peut prendre jusqu'à 24 heures.
1# Prérequis : Install-Module Microsoft.Graph.Authentication -Scope CurrentUser2# Permissions d'application : Files.ReadWrite.All, Sites.Read.All, GroupMember.Read.All3# Sortie : résumé à l'écran + rapport CSV (une ligne par fichier ou par erreur de groupe)4[CmdletBinding()]5param(6 [Parameter(Mandatory)][string]$TenantId,7 [Parameter(Mandatory)][string]$ClientId,8 [Parameter(Mandatory)][string]$CertificateThumbprint,9 [string]$GroupCsvPath,10 [string]$GroupIdColumn = 'GroupId',11 [int]$AgeYears = 3,12 [string]$MailFolderName = 'Microsoft Teams Mailhook',13 [string]$FileExtension = '.eml',14 [string[]]$ExcludedDrives = @('Preservation Hold Library', 'Site Assets'),15 [string]$ReportPath = './ArchiveTeamsMailFiles.csv',16 [switch]$DryRun17)18 19$ErrorActionPreference = 'Stop'20$GraphV1 = 'https://graph.microsoft.com/v1.0'21$GraphBeta = 'https://graph.microsoft.com/beta'22# Chemin de l'appel d'archivage : à comparer avec la page driveItem: archive (beta)23$ArchiveUriTemplate = $GraphBeta + '/drives/{0}/items/{1}/archive'24 25$Cutoff = (Get-Date).ToUniversalTime().AddYears(-$AgeYears)26$Stats = [ordered]@{ Groups = 0; Drives = 0; Scanned = 0; Archived = 0; WouldArchive = 0; AlreadyArchived = 0; TooRecent = 0; Errors = 0 }27$Report = [System.Collections.Generic.List[object]]::new()28 29# Appel Graph avec nouvelle tentative sur les erreurs transitoires (limitation, indisponibilité)30function Invoke-GraphWithRetry {31 param([string]$Method = 'GET', [string]$Uri)32 for ($try = 1; $try -le 4; $try++) {33 try {34 return Invoke-MgGraphRequest -Method $Method -Uri $Uri35 }36 catch {37 $transient = $_.Exception.Message -match '429|503|504|TooManyRequests|serviceNotAvailable'38 if (-not $transient -or $try -eq 4) { throw }39 Start-Sleep -Seconds (5 * $try * $try)40 }41 }42}43 44# Lecture paginée : suit @odata.nextLink jusqu'à la dernière page45function Get-GraphPages {46 param([string]$Uri)47 while ($Uri) {48 $page = Invoke-GraphWithRetry -Uri $Uri49 $page['value']50 $Uri = $page['@odata.nextLink']51 }52}53 54# Parcours récursif d'une bibliothèque ; ne traite que les fichiers situés sous le dossier Mailhook55function Search-Folder {56 param([string]$DriveId, [string]$ChildrenUri, [bool]$InMailFolder, [string]$Path, [string]$Label)57 foreach ($item in Get-GraphPages -Uri $ChildrenUri) {58 $itemPath = $Path + '/' + $item['name']59 if ($item.ContainsKey('folder')) {60 $subUri = '{0}/drives/{1}/items/{2}/children?$top=200' -f $GraphBeta, $DriveId, $item['id']61 $inside = $InMailFolder -or ($item['name'] -eq $MailFolderName)62 Search-Folder -DriveId $DriveId -ChildrenUri $subUri -InMailFolder $inside -Path $itemPath -Label $Label63 }64 elseif ($InMailFolder -and $item['name'].EndsWith($FileExtension, [System.StringComparison]::OrdinalIgnoreCase)) {65 $Stats['Scanned']++66 $modified = ([datetime]$item['lastModifiedDateTime']).ToUniversalTime()67 $action = ''68 $detail = ''69 if ($item['archiveStatus']) {70 # archiveStatus est vide pour un fichier actif : toute valeur signale un fichier déjà archivé71 $action = 'AlreadyArchived'72 $Stats['AlreadyArchived']++73 }74 elseif ($modified -ge $Cutoff) {75 $action = 'TooRecent'76 $Stats['TooRecent']++77 }78 elseif ($DryRun) {79 $action = 'WouldArchive'80 $Stats['WouldArchive']++81 }82 else {83 try {84 # Fichier unique, requête synchrone : 204 attendu, archivage immédiat85 Invoke-GraphWithRetry -Method 'POST' -Uri ($ArchiveUriTemplate -f $DriveId, $item['id']) | Out-Null86 $action = 'Archived'87 $Stats['Archived']++88 }89 catch {90 $action = 'Error'91 $detail = $_.Exception.Message92 $Stats['Errors']++93 }94 }95 $Report.Add([pscustomobject]@{ Site = $Label; Path = $itemPath; Modified = $modified; Action = $action; Detail = $detail })96 }97 }98}99 100# Connexion en mode application avec certificat101Connect-MgGraph -TenantId $TenantId -ClientId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome102 103# Groupes à traiter : CSV fourni, sinon tous les groupes Microsoft 365104if ($GroupCsvPath) {105 $groupIds = Import-Csv -Path $GroupCsvPath | ForEach-Object { $_.$GroupIdColumn } | Where-Object { $_ } | Sort-Object -Unique106}107else {108 $groupsUri = $GraphV1 + '/groups?$filter=groupTypes/any(c:c eq ''Unified'')&$select=id,displayName&$top=100'109 $groupIds = Get-GraphPages -Uri $groupsUri | ForEach-Object { $_['id'] }110}111 112foreach ($groupId in $groupIds) {113 $Stats['Groups']++114 try {115 $site = Invoke-GraphWithRetry -Uri ('{0}/groups/{1}/sites/root' -f $GraphV1, $groupId)116 $drives = @(Get-GraphPages -Uri ('{0}/sites/{1}/drives' -f $GraphV1, $site['id']) | Where-Object { $ExcludedDrives -notcontains $_['name'] })117 foreach ($drive in $drives) {118 $Stats['Drives']++119 Write-Host ('Site {0} - bibliothèque {1}' -f $site['webUrl'], $drive['name'])120 $rootUri = '{0}/drives/{1}/root/children?$top=200' -f $GraphBeta, $drive['id']121 Search-Folder -DriveId $drive['id'] -ChildrenUri $rootUri -InMailFolder $false -Path '' -Label $site['webUrl']122 }123 }124 catch {125 $Stats['Errors']++126 $Report.Add([pscustomobject]@{ Site = $groupId; Path = ''; Modified = $null; Action = 'GroupError'; Detail = $_.Exception.Message })127 Write-Warning ('Groupe {0} ignoré : {1}' -f $groupId, $_.Exception.Message)128 }129}130 131# Résumé et rapport132[pscustomobject]$Stats | Format-List133$Report | Export-Csv -Path $ReportPath -NoTypeInformation -Encoding UTF8134Write-Host ('Rapport écrit dans {0}' -f $ReportPath)135Disconnect-MgGraph | Out-NullLancez d'abord la simulation sur un CSV réduit, puis relancez sans -DryRun une fois le rapport relu :
1./Archive-TeamsMailFiles.ps1 -TenantId 'contoso.onmicrosoft.com' -ClientId '00000000-0000-0000-0000-000000000000' -CertificateThumbprint 'THUMBPRINT_DU_CERTIFICAT' -GroupCsvPath ./teams-email.csv -DryRunVérifier que l'archivage a fonctionné
Trois contrôles se complètent : les compteurs du script, l'état des fichiers dans la bibliothèque et le stockage archivé du site.
Ouvrez la bibliothèque du site, puis un dossier mensuel sous « Microsoft Teams Mailhook » : les messages archivés y sont signalés comme tels, et leur contenu n'est plus consultable tant qu'ils ne sont pas réactivés.
La propriété ArchivedFileDiskUsed de Get-SPOSite indique le stockage occupé par les fichiers archivés du site. Une seconde exécution avec -DryRun doit aussi afficher AlreadyArchived en hausse et WouldArchive à zéro.


Exécuter le script dans Azure Automation
Ce type de traitement se prête bien à un runbook Azure Automation. La limite documentée est de trois heures par exécution de runbook, et la première passe est la plus longue. Le niveau gratuit d'Azure Automation offre 500 minutes d'exécution par mois, au-delà desquelles l'usage est facturé.
Dans les grands tenants, découpez le CSV en plusieurs fichiers pour répartir la charge sur plusieurs tâches, et commencez par un petit périmètre. Dans un runbook, remplacez l'authentification par certificat par Connect-MgGraph -Identity si vous utilisez une identité managée, en lui attribuant les mêmes permissions d'application.
Limites et pièges à anticiper
L'API d'archivage est en beta, ce qui est le principal frein à un usage industrialisé. Les points suivants viennent de la documentation Microsoft Learn :
- un fichier réactivé ne peut pas être archivé à nouveau pendant 120 jours ;
- les fichiers OneNote, les pages SharePoint, les agents SharePoint et la bibliothèque Site Assets ne sont pas pris en charge ;
- Teams, OneDrive et SharePoint mobile ne gèrent pas encore l'archivage au niveau fichier et peuvent afficher des erreurs trompeuses à l'ouverture d'un fichier archivé ;
- la réactivation peut prendre jusqu'à 24 heures.
Le dernier point mérite une communication préalable : un membre d'équipe qui ouvre un e-mail archivé depuis Teams peut croire à une panne.
| Symptôme | Cause probable | Résolution |
|---|---|---|
| Erreur d’autorisation sur l’appel d’archivage | Consentement administrateur manquant sur Files.ReadWrite.All, ou prérequis de facturation et d’activation non remplis | Vérifier le consentement de l’application, la facturation à l’usage et l’activation de Microsoft 365 Archive |
| Groupe signalé GroupError dans le rapport | Groupe sans site SharePoint provisionné, ou identifiant erroné dans le CSV | Contrôler la colonne GroupId du CSV et l’existence du site du groupe |
| Le runbook s’arrête avant la fin | Limite de trois heures par exécution dépassée | Découper le CSV en plusieurs lots et relancer : les fichiers déjà archivés sont ignorés |
| Erreurs répétées de type 429 ou 503 | Limitation de débit côté Microsoft Graph | Le script réessaie automatiquement ; réduire le périmètre par exécution si cela persiste |
| Un fichier ne peut pas être archivé de nouveau | Fichier réactivé il y a moins de 120 jours | Attendre la fin du délai de 120 jours |
| Aucun fichier détecté dans une bibliothèque | Nom de bibliothèque localisé ou dossier Mailhook absent du site | Adapter ExcludedDrives et MailFolderName, puis relancer en simulation |
Script ou étiquette de rétention : que choisir ?
L'alternative est une stratégie d'étiquetage automatique appliquant une étiquette de rétention dont les paramètres incluent une étape d'archivage. Cette capacité est en cours de déploiement et a connu plusieurs reports, et l'étiquetage automatique requiert des licences E5.
| Critère | Script Graph | Étiquette de rétention automatique |
|---|---|---|
| Disponibilité | Immédiate, mais API beta | Déploiement en cours, déjà reporté plusieurs fois |
| Licence | Facturation à l’usage et Microsoft 365 Archive activé | Licences E5 pour l’étiquetage automatique |
| Contrôle du périmètre | Total : groupes, dossiers, extension, seuil d’âge | Défini par la stratégie, moins de visibilité sur le détail |
| Maintenance | À votre charge (API beta, planification) | Portée par Microsoft |
Si votre organisation dispose d'E5 et peut attendre, l'étiquette sera à terme plus durable, sans code à maintenir. Si vous devez agir tout de suite sur un périmètre précis, comme les dossiers Mailhook, le script est plus direct. Il reste à garder comme solution transitoire tant que l'API est en beta. Cette démarche s'intègre dans la checklist technique de préparation à Copilot.
Questions fréquentes
L'archivage au niveau fichier réduit-il le stockage consommé par le tenant ?
Non. Les fichiers archivés restent comptés dans le stockage total du tenant : ils sont reclassés de stockage actif en stockage archivé. Le tarif indiqué est de 0,05 $/Go/mois pour l'archive contre 0,20 $/Go/mois en standard, et l'archive n'est facturée que si le quota du tenant est dépassé.
Peut-on revenir en arrière après l'archivage ?
Oui, les fichiers archivés restent réactivables, même si l'archivage manuel est désactivé au niveau du tenant. La réactivation peut prendre jusqu'à 24 heures, et un fichier réactivé ne peut pas être archivé à nouveau pendant 120 jours.
Faut-il des licences E5 pour archiver par script ?
L'étiquetage automatique par rétention requiert des licences E5. Pour l'archivage par script, les prérequis documentés sont la facturation à l'usage et l'activation de Microsoft 365 Archive sur le tenant.
Peut-on utiliser ce script en production ?
L'API driveItem: archive est toujours documentée en version beta, et Microsoft indique que les API beta ne sont pas prises en charge en production. Exécutez-le en simulation, sur un périmètre restreint, avec l'accord de votre gouvernance, en sachant que le contrat de l'API peut évoluer.
Par où commencer : trois décisions avant le premier run
Prochaines actions
- Valider la facturation à l'usage et l'activation de Microsoft 365 Archive, puis vérifier que les sites ciblés autorisent l'archivage au niveau fichier
- Produire le CSV des groupes avec canaux à adresse e-mail et choisir le seuil d'âge (trois ans dans le script)
- Exécuter le script avec
-DryRunsur un lot réduit, relire le rapport CSV, puis lancer l'archivage réel et mesurerArchivedFileDiskUsed
Si votre tenant approche de son quota SharePoint, l'archivage a un effet financier direct. Sinon, il sert surtout à retirer des contenus périmés de la portée de Copilot : traitez alors les dossiers Mailhook comme un premier chantier, avant les bibliothèques de documents métier.
Pour aller plus loin
- Overview of Microsoft 365 Archive : le périmètre, les limites et les types de fichiers non pris en charge
- Manage Microsoft 365 Archive : les commandes PowerShell pour activer ou désactiver l'archivage au niveau fichier et mesurer l'usage
- Developer guidance for Microsoft 365 Archive : les API Graph recommandées pour énumérer, archiver et réactiver des fichiers
- driveItem: archive (Microsoft Graph beta) : la référence de l'appel d'archivage, avec ses codes de réponse
- Frequently asked questions about Microsoft 365 Archive : prérequis, impact sur le stockage et tarifs
