Automatiser les ACL NTFS en PowerShell : corriger l’erreur « No flags can be set » (InheritanceFlags)

Vous tentez d’automatiser l’attribution d’ACL NTFS à tout un volume en PowerShell, mais vous tombez sur l’erreur « No flags can be set. Parameter name: inheritanceFlags ». Voici une explication claire des causes et un script robuste prêt à l’emploi pour corriger le problème sans casser l’héritage existant.

Sommaire

Problème principal

Automatiser, avec PowerShell, l’attribution d’ACL (droits NTFS) — ajout de User1 et Group1 — à tous les sous‑dossiers et fichiers d’un volume. L’auteur obtient l’exception :

Exception calling "AddAccessRule" with "1" argument(s):
"No flags can be set. Parameter name: inheritanceFlags"

Analyse des causes

Fichiers vs dossiers

  • Les indicateurs ContainerInherit (CI) et ObjectInherit (OI) n’ont de sens que pour les conteneurs (dossiers).
  • Quand la boucle tombe sur un fichier, lui appliquer ces indicateurs provoque l’erreur ci‑dessus : un fichier ne contient pas d’objets enfants, donc les flags d’héritage ne sont pas acceptés.

Composition des indicateurs

  • L’écriture suivante crée deux valeurs indépendantes plutôt qu’une composition binaire attendue par le constructeur :
[InheritanceFlags]::"ContainerInherit", "ObjectInherit"

Il faut combiner les indicateurs avec l’opérateur -bor ou en les listant dans une unique chaîne convertible :

$InheritanceFlags = [System.Security.AccessControl.InheritanceFlags] "ContainerInherit,ObjectInherit"
# ou
$InheritanceFlags = [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit

Signature du constructeur

  • En passant un SID ou des paramètres mal typés, PowerShell ne trouve plus la bonne surcharge :
Cannot find an overload for 'FileSystemAccessRule' and the argument count: 5

Vérifier les types et l’ordre :

FileSystemAccessRule(
  string identity,
  FileSystemRights rights,
  InheritanceFlags inheritance,
  PropagationFlags propagation,
  AccessControlType type
)

Solution : script robuste

Ce script gère la distinction fichier/dossier, compose correctement les flags et applique des règles propres. Adaptez DOMAIN\User1 et DOMAIN\Group1 à votre environnement.

# Parcours récursif
Get-ChildItem -Path "D:\Test\Vol2" -Recurse | ForEach-Object {

    $path       = $_.FullName
    $acl        = Get-Acl -Path $path
    $rightsRead = [FileSystemRights]::Read
    $rightsFull = [FileSystemRights]::FullControl
    $propFlags  = [PropagationFlags]::None
    $accType    = [AccessControlType]::Allow

    if ($_.PSIsContainer) {
        # Dossier : on autorise l’héritage descendant
        $inhFlags = [InheritanceFlags]::ContainerInherit -bor `
                     [InheritanceFlags]::ObjectInherit

        $acl.AddAccessRule( [FileSystemAccessRule]::new("DOMAIN\User1" , $rightsRead , $inhFlags , $propFlags , $accType) )
        $acl.AddAccessRule( [FileSystemAccessRule]::new("DOMAIN\Group1", $rightsFull , $inhFlags , $propFlags , $accType) )
    }
    else {
        # Fichier : pas d’indicateurs d’héritage
        $inhFlags = [InheritanceFlags]::None

        $acl.AddAccessRule( [FileSystemAccessRule]::new("DOMAIN\User1" , $rightsRead , $inhFlags , $propFlags , $accType) )
        $acl.AddAccessRule( [FileSystemAccessRule]::new("DOMAIN\Group1", $rightsFull , $inhFlags , $propFlags , $accType) )
    }

    Set-Acl -Path $path -AclObject $acl
}

Points clés du correctif

AspectExplication
Détection fichier/dossier$_.PSIsContainer permet d’appliquer des règles distinctes.
Indicateurs d’héritageNone pour les fichiers ; ContainerInherit + ObjectInherit pour les dossiers.
Protection de l’héritage existantSetAccessRuleProtection($true,$false) bloque l’héritage depuis le dossier parent si nécessaire ; inverser les booléens pour le réactiver.
Combinaison des flagsUtiliser -bor ou une chaîne unique, jamais deux arguments séparés.

Explications pas à pas

  • Lecture ACL : Get-Acl retourne la DACL actuelle du chemin traité (fichier ou dossier).
  • Choix des droits : [FileSystemRights]::Read et ::FullControl couvrent la plupart des scénarios. Remplacez au besoin par ::Modify ou des combinaisons fines.
  • Propagation : [PropagationFlags]::None indique qu’on ne force ni l’application uniquement aux enfants (InheritOnly) ni l’arrêt à un seul niveau (NoPropagateInherit).
  • Type d’accès : [AccessControlType]::Allow crée des ACE de type autorisation (vs Deny que l’on évite sauf cas très spécifique).
  • Dossiers : on compose CI + OI pour propager vers sous‑dossiers et fichiers.
  • Fichiers : on met InheritanceFlags::None pour éviter l’exception « No flags can be set ».
  • Écriture ACL : Set-Acl persiste les nouvelles ACE sur l’objet courant.

Méthode alternative : fonction réutilisable (avec WhatIf)

Pour industrialiser, voici une fonction paramétrable, compatible Windows PowerShell 5.1 et PowerShell 7+, qui gère l’inclusion du dossier racine, l’exclusion des points de jonction et une option pour désactiver l’héritage parent.

function Set-NtfsAclRecursive {
  [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
  param(
    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$Path,

    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$User,      # ex. "DOMAIN\User1"

    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$Group,     # ex. "DOMAIN\Group1"

    [System.Security.AccessControl.FileSystemRights]$UserRights  = [FileSystemRights]::Read,
    [System.Security.AccessControl.FileSystemRights]$GroupRights = [FileSystemRights]::FullControl,

    [switch]$DisableParentInheritance,  # désactive l’héritage parent sur les dossiers traités
    [switch]$IncludeRoot                # inclut le dossier racine lui‑même
  )

  if (-not (Test-Path -LiteralPath $Path)) {
    throw "Chemin introuvable : $Path"
  }

  $propFlags = [PropagationFlags]::None
  $accType   = [AccessControlType]::Allow

  # Construit la liste des cibles : racine (optionnel) + contenu récursif
  $targets = @()
  if ($IncludeRoot) { $targets += Get-Item -LiteralPath $Path -Force }

  # Exclut par défaut les répertoires de type jonction/symlink pour éviter les boucles
  $targets += Get-ChildItem -LiteralPath $Path -Recurse -Force -Attributes !ReparsePoint

  foreach ($item in $targets) {
    try {
      $acl = Get-Acl -Path $item.FullName

      if ($DisableParentInheritance -and $item.PSIsContainer) {
        if ($PSCmdlet.ShouldProcess($item.FullName, 'SetAccessRuleProtection')) {
          $acl.SetAccessRuleProtection($true, $false)  # bloque l’héritage parent, sans conserver les ACE héritées
        }
      }

      $inhFlags = if ($item.PSIsContainer) {
        [InheritanceFlags]::ContainerInherit -bor [InheritanceFlags]::ObjectInherit
      } else {
        [InheritanceFlags]::None
      }

      # Utilise New-Object pour compatibilité maximale avec 5.1
      $ruleUser  = New-Object System.Security.AccessControl.FileSystemAccessRule($User,  $UserRights,  $inhFlags, $propFlags, $accType)
      $ruleGroup = New-Object System.Security.AccessControl.FileSystemAccessRule($Group, $GroupRights, $inhFlags, $propFlags, $accType)

      $null = $acl.AddAccessRule($ruleUser)
      $null = $acl.AddAccessRule($ruleGroup)

      if ($PSCmdlet.ShouldProcess($item.FullName, 'Set-Acl')) {
        Set-Acl -Path $item.FullName -AclObject $acl
      }
    } catch {
      Write-Warning ("ACL non appliquée à {0} : {1}" -f $item.FullName, $_.Exception.Message)
    }
  }
}

Exemples d’usage :

# Simulation (aucun changement) :
Set-NtfsAclRecursive -Path 'D:\Partage' -User 'CONTOSO\User1' -Group 'CONTOSO\Group1' -IncludeRoot -WhatIf

# Exécution réelle en bloquant l’héritage parent sur les dossiers visités :

Set-NtfsAclRecursive -Path 'D:\Partage' -User 'CONTOSO\User1' -Group 'CONTOSO\Group1' -IncludeRoot -DisableParentInheritance 

Astuce : Si votre hôte PowerShell n’accepte pas la syntaxe [Type]::new(), remplacez‑la systématiquement par New-Object Type ... comme dans la fonction ci‑dessus.

Choisir les bons drapeaux : aide‑mémoire pratique

ObjectifInheritanceFlagsPropagationFlagsEffet
Appliquer au dossier et à tout son contenu (sous‑dossiers + fichiers)ContainerInherit + ObjectInheritNoneAutorise sur le dossier courant et propage aux descendants.
Uniquement au dossier courantNoneNoneAucune propagation vers les enfants.
Uniquement aux enfants (pas au dossier courant)ContainerInherit + ObjectInheritInheritOnlyACE héritables non appliquées au conteneur lui‑même.
Uniquement aux fichiers (pas aux sous‑dossiers)ObjectInheritInheritOnlyIdéal pour donner un droit sur les fichiers sans l’accorder aux dossiers.
Limiter l’héritage à un seul niveauContainerInherit + ObjectInheritNoPropagateInheritLes enfants héritent, mais pas les petits‑enfants.

Validation et contrôle après exécution

Validez que les ACE ont bien été ajoutées aux cibles attendues et qu’aucune erreur silencieuse n’est passée inaperçue.

# Vérifier un dossier et un fichier échantillons
$testFolder = 'D:\Test\Vol2\SousDossier'
$testFile   = 'D:\Test\Vol2\SousDossier\exemple.txt'

(Get-Acl -Path $testFolder).Access |
Where-Object IdentityReference -match 'DOMAIN\(User1|Group1)' |
Format-Table IdentityReference, FileSystemRights, IsInherited, InheritanceFlags, PropagationFlags

(Get-Acl -Path $testFile).Access |
Where-Object IdentityReference -match 'DOMAIN\(User1|Group1)' |
Format-Table IdentityReference, FileSystemRights, IsInherited, InheritanceFlags, PropagationFlags 

Pro tip : pour auditer rapidement un grand ensemble, exportez en CSV :

Get-ChildItem -Path 'D:\Test\Vol2' -Recurse -File |
  ForEach-Object {
    $acl = Get-Acl $_.FullName
    foreach ($ace in $acl.Access) {
      [pscustomobject]@{
        Path              = $_.FullName
        IdentityReference = $ace.IdentityReference
        Rights            = $ace.FileSystemRights
        Inherited         = $ace.IsInherited
        InheritanceFlags  = $ace.InheritanceFlags
        PropagationFlags  = $ace.PropagationFlags
      }
    }
  } | Export-Csv -NoTypeInformation -Encoding UTF8 'D:\rapport-acl.csv'

Performance et robustesse sur grands volumes

  • Itérations distinctes : effectuer deux parcours peut être plus rapide et plus simple à raisonner : un pour les dossiers (-Directory) puis un pour les fichiers (-File), au lieu de tester PSIsContainer à chaque tour.
  • Exclure les reparse points : -Attributes !ReparsePoint évite boucles et volumes montés accidentels.
  • Forcer l’inclusion : -Force parcourt aussi les éléments cachés/système.
  • 64 bits : préférez PowerShell 64 bits pour éviter des limites de mémoire sur d’énormes hiérarchies.
  • PS 7+ : ForEach-Object -Parallel peut accélérer, mais n’écrivez pas en parallèle sur une même DACL ; segmentez par sous‑arbres.
  • Chemins longs : si nécessaire, préfixez \\?\ pour dépasser 260 caractères (Long Path) sur des OS qui le permettent.

Journalisation et reprise

Ajoutez une journalisation légère pour relancer proprement en cas d’erreur non bloquante.

$log = 'D:\acl.log'
Get-ChildItem -Path 'D:\Test\Vol2' -Recurse -Attributes !ReparsePoint | ForEach-Object {
  try {
    # ... récupération ACL, construction des ACE, etc.
    # $acl.AddAccessRule(...); Set-Acl ...
  }
  catch {
    Add-Content -Path $log -Value ("{0};{1}" -f $_.FullName, $_.Exception.Message)
  }
}

Cas particuliers et pièges

  • Héritage parent : si le parent accorde déjà un droit plus large, votre nouvelle ACE Allow ne restreindra rien. Pour verrouiller, activez SetAccessRuleProtection($true,$false) sur les dossiers cibles, puis définissez explicitement toutes les ACE requises.
  • ACE Deny : elles prennent effet avant les Allow. N’en ajoutez qu’en dernier recours et avec parcimonie, sinon vous bloquez des accès légitimes.
  • Ordre canonique : Windows réordonne parfois les ACE. C’est normal ; ne tentez pas de forcer un ordre « visuel » arbitraire.
  • Propriété/SACL : Get-Acl/Set-Acl manipule la DACL. Modifier le propriétaire ou la SACL nécessite des privilèges élevés spécifiques.
  • Résolution d’identités : si le domaine est lent ou indisponible, préférez utiliser des NTAccount pré‑résolues (ou SIDs) et vérifiez la portée de confiance inter‑domaines.

Variantes utiles

  • Groupes dynamiques : si le groupe cible dépend du nom de dossier (ex. Local_MonDossier_W), générez le nom avant d’ajouter les règles : $grp = 'Local_{0}_W' -f $_.Name, puis créez l’ACE avec $grp. La logique « fichier/dossier » reste identique.
  • Affiner les droits : pour une écriture sans suppression, combinez les droits : [FileSystemRights] "Read,Write".
  • Compatibilité : si vous ciblez des hôtes anciens, remplacez tous les [Type]::new par New-Object.

FAQ rapide

« AddAccessRule », « SetAccessRule » ou « ResetAccessRule » ?
AddAccessRule ajoute une ACE supplémentaire (risque de doublons). SetAccessRule remplace une ACE existante correspondante (identité/type/héritage) ou l’ajoute si absente. ResetAccessRule supprime toutes les ACE de l’identité puis ajoute la nouvelle (plus intrusif).

Pourquoi l’erreur « No flags can be set » n’apparaît qu’avec les fichiers ?
Parce que les fichiers ne sont pas des conteneurs : ils ne peuvent pas porter de ContainerInherit/ObjectInherit.

Puis‑je tout faire avec icacls ?
Oui, mais PowerShell offre un contrôle objet fin et des tests -WhatIf au même endroit que vos autres automatisations.

Annexe : équivalent icacls (à titre indicatif)

# Autoriser User1 (Lecture) et Group1 (Contrôle total) sur dossiers + fichiers descendants
icacls "D:\Test\Vol2" /grant "DOMAIN\User1:(OI)(CI)(RX)" "DOMAIN\Group1:(OI)(CI)(F)" /T
# (OI) = ObjectInherit, (CI) = ContainerInherit, (RX) = Read & execute, (F) = Full control

Résumé

Le message « No flags can be set » traduit l’application de flags d’héritage à un fichier. Corrigez la logique en distinguant fichiers et dossiers, composez correctement les InheritanceFlags avec -bor, vérifiez la signature FileSystemAccessRule, puis sécurisez l’héritage selon vos besoins. Les scripts fournis ci‑dessus sont prêts à l’emploi et adaptables à des volumes très larges, avec options de simulation et de journalisation pour une mise en production sereine.

Informations complémentaires utiles

  • Performance : Exécuter le script en mode 64 bits et préférer des parcours dédiés -Directory/-File peut réduire le temps sur de très grands volumes. Excluez ReparsePoint pour éviter les boucles.
  • Journalisation : Ajoutez Try/Catch et une écriture fichier (ou Write-EventLog) pour tracer les erreurs non bloquantes.
  • Groupes dynamiques : Si les groupes varient selon le nom du dossier (Local_MonDossier_W), la même logique « fichier/dossier » reste applicable ; adaptez simplement le calcul du groupe avant l’ajout des règles.
Sommaire