faitza.com
faitza.com
> blog_tech

Active Directory — Gestion des utilisateurs en PowerShell

Calcul... powershell active-directory automation on-premise
Active Directory PowerShell

Introduction

Faitza_AD.ps1 regroupe les fonctions de gestion du cycle de vie des comptes Active Directory on-premise : création avec home directory et ACL, recherche par OU ou email, contrôle de l'expiration du mot de passe, dernière connexion, désactivation et suppression.

Prérequis

Le module ActiveDirectory doit être disponible :

Import-Module ActiveDirectory

Le script doit s'exécuter depuis un serveur membre du domaine ou un poste avec les RSAT installés. Les fonctions nécessitent les droits de gestion d'utilisateurs dans les OUs ciblées.

New-Faitza_AD_user

Crée un compte AD à partir d'un objet PSCustomObject contenant les propriétés utilisateur. La fonction calcule le SAMAccountName, crée le répertoire home, applique les ACL, génère un mot de passe cryptographiquement sûr via New-Faitza_AD_Password et ajoute l'utilisateur aux groupes spécifiés.

function New-Faitza_AD_user {
    [CmdletBinding()]
    param (
        [PSCustomObject]$user_base,
        [string[]]$groupe_liste
    )

    function New-Faitza_AD_Password {
        [CmdletBinding()]
        param([Alias("longueur")][int]$pass_longueur = 64)
        try {
            Write-Host "$($PSStyle.Background.Red)>>> New-Faitza_AD_Password <<<$($PSStyle.Reset)"
            if ($pass_longueur -lt 8) { throw "longueur minimale = 8 (recu : $pass_longueur)" }
            $caracteres_min = "abcdefghijklmnopqrstuvwxyz"
            $caracteres_maj = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            $caracteres_nom = "0123456789"
            $caracteres_spe = "!#%&()*+,-./:;<=>?@[]^_{|}~"
            $caracteres_reqs = @($caracteres_min, $caracteres_maj, $caracteres_nom, $caracteres_spe)
            $caracteres_alls = $caracteres_min + $caracteres_maj + $caracteres_nom + $caracteres_spe
            $crypto_rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
            $crypto_byte = New-Object byte[] 4
            $pass = [System.Collections.Generic.List[char]]::new()
            try {
                foreach ($set in $caracteres_reqs) { $crypto_rng.GetBytes($crypto_byte); $pass.Add($set[[System.BitConverter]::ToUInt32($crypto_byte, 0) % $set.Length]) }
                for ($k = $pass.Count; $k -lt $pass_longueur; $k++) { $crypto_rng.GetBytes($crypto_byte); $pass.Add($caracteres_alls[[System.BitConverter]::ToUInt32($crypto_byte, 0) % $caracteres_alls.Length]) }
                for ($i = $pass.Count - 1; $i -gt 0; $i--) { $crypto_rng.GetBytes($crypto_byte); $j = [int]([System.BitConverter]::ToUInt32($crypto_byte, 0) % ($i + 1)); if ($j -ne $i) { $tmp = $pass[$i]; $pass[$i] = $pass[$j]; $pass[$j] = $tmp } }
            } finally { $crypto_rng.Dispose() }
            return -join $pass
        } catch {
            throw "Erreur New-Faitza_AD_Password : $_"
        } finally {
            Write-Host "$($PSStyle.Background.Red)<<< New-Faitza_AD_Password >>>$($PSStyle.Reset)"
        }
    }

    try {
        Write-Host "$($PSStyle.Background.Red)>>> New-Faitza_AD_user <<<$($PSStyle.Reset)"
        if (-not $user_base) { throw "parametre -user_base manquant" }
        if ([string]::IsNullOrWhiteSpace($user_base.Mail)) { throw "propriete Mail manquante dans user_base" }
        if ([string]::IsNullOrWhiteSpace($user_base.Surname)) { throw "propriete Surname manquante dans user_base" }
        if ([string]::IsNullOrWhiteSpace($user_base.GivenName)) { throw "propriete GivenName manquante dans user_base" }
        if ([string]::IsNullOrWhiteSpace($user_base.Departement)) { throw "propriete Departement manquante dans user_base" }
        if ([string]::IsNullOrWhiteSpace($user_base.Profil)) { throw "propriete Profil manquante dans user_base" }

        $groupe_erreur = @()
        $user_mapping = Get-Faitza_AD_Mapping -departement $user_base.Departement -profil $user_base.Profil
        $user_base_Departement = $user_mapping.departement
        $user_base_Profil = $user_mapping.profil
        $user_ou = $user_mapping.ou

        if ($user_base_Profil -match "Fonction Support|Assistant|Paralegal") {
            $groupe_liste += "GRP_LICENCE_E5_AND_SHAREPOINT"
            if (-not $user_base.Externe) { $groupe_liste += "Salaries" }
            if ($user_base_Profil -eq "Assistant") { $groupe_liste += @("ASSISTANTES", "Secretaires$($user_base.Groupe)") }
            if ($user_base_Profil -eq "Paralegal") { $groupe_liste += "Corporate-Paralegal" }
        } else {
            $groupe_liste += "GRP_LICENCE_E5"
            if ($user_base.Groupe -match "Groupe[1-3]") { $groupe_liste += "Corporate-$($user_base.Groupe)" }
            $groupe_liste += "$user_base_Departement-$($user_base_Profil)s"
        }

        function Remove-Diacritics ([string]$Text) {
            if ([string]::IsNullOrWhiteSpace($Text)) { return $Text }
            return ($Text.Normalize([System.Text.NormalizationForm]::FormD) -replace '\p{Mn}', '')
        }

        $user_sam = ((Remove-Diacritics ($user_base.GivenName + $user_base.Surname)) -replace '[^a-zA-Z0-9]', '').ToLower()
        if ($user_sam.Length -gt 20) {
            $initiale = if ($user_base.GivenName.Length -gt 0) { $user_base.GivenName.Substring(0, 1) } else { "" }
            $user_sam = ((Remove-Diacritics ($initiale + $user_base.Surname)) -replace '[^a-zA-Z0-9]', '').ToLower()
        }
        if ($user_sam.Length -gt 20) { $user_sam = $user_sam.Substring(0, 20) }
        if ([string]::IsNullOrWhiteSpace($user_sam)) { throw "impossible de calculer un SAMAccountName valide" }

        $user_homedir = "\\srv-faitza-files01.faitza.local\UTILISATEURS\$(($user_base.Mail).Split('@')[0])"
        $givenFormatted = ($user_base.GivenName) -replace '^\w|\s\w', { $_.Value.ToUpper() }
        $surnameFormatted = ($user_base.Surname).ToUpper()
        $user_params = @{
            Name = "$surnameFormatted $givenFormatted"
            DisplayName = "$surnameFormatted $givenFormatted"
            GivenName = $givenFormatted
            Surname = $surnameFormatted
            SamAccountName = $user_sam
            Description = $user_base.Description
            UserPrincipalName = $user_base.Mail
            EmailAddress = $user_base.Mail
            Enabled = $false
            Company = "Faitza"
            Country = "FR"
            HomeDirectory = $user_homedir
            HomeDrive = "H:"
            Path = $user_ou
            OtherAttributes = @{
                extensionAttribute15 = "CmnExcludeFromDirSync"
                mAPIRecipient = $true
                mDBStorageQuota = 6144000
                mDBUseDefaults = $false
                msExchHomeServerName = "/o=FAITZAORG/ou=Exchange Administrative Group/cn=Configuration/cn=Servers/cn=MAIL01"
                msExchUserAccountControl = 0
            }
        }

        try {
            if (-not (Test-Path $user_homedir)) {
                New-Item -Path $user_homedir -ItemType Directory -Force | Out-Null
                Write-Host "Repertoire home cree : $user_homedir"
            }
        } catch { throw "erreur creation home '$user_homedir' : $_" }

        $user_password = New-Faitza_AD_Password -pass_longueur 32
        $user_params["AccountPassword"] = (ConvertTo-SecureString -String $user_password -AsPlainText -Force)

        try {
            if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$user_ou'" -ErrorAction SilentlyContinue)) { throw "OU introuvable dans l'AD : '$user_ou'" }
            $user = New-ADUser @user_params -PassThru -ErrorAction Stop
        } catch { throw "creation compte AD echouee ($user_sam) : $_" }

        foreach ($group in $groupe_liste) {
            try {
                $group_find = Get-ADGroup -Filter "Name -eq '$group'" -ErrorAction Stop
                if ($null -eq $group_find) { throw "groupe '$group' introuvable dans l'AD" }
                Add-ADGroupMember -Identity $group_find.distinguishedName -Members $user.SamAccountName -ErrorAction Stop
            } catch {
                Write-Host "Erreur ajout groupe '$group' : $_"
                $groupe_erreur += "Erreur ajout groupe '$group' : $_"
            }
        }

        try {
            $userSid = [System.Security.Principal.SecurityIdentifier]$user.SID
            if ($null -eq $userSid) { throw "SID de l'utilisateur introuvable" }
            $homedir_acl = Get-Acl $user_homedir
            $user_accessrule = New-Object System.Security.AccessControl.FileSystemAccessRule($userSid, [System.Security.AccessControl.FileSystemRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit", [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow)
            $homedir_acl.SetAccessRule($user_accessrule)
            Set-Acl $user_homedir $homedir_acl
        } catch { throw "erreur application ACL home '$user_homedir' : $_" }

        return [PSCustomObject]@{ User = $user; password = $user_password; Groupe_erreur = $groupe_erreur; Groupe = $groupe_liste }
    } catch {
        throw "Erreur New-Faitza_AD_user : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< New-Faitza_AD_user >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$user_base = [PSCustomObject]@{
    Mail        = "[email protected]"
    GivenName   = "Jean"
    Surname     = "Dupont"
    Departement = "Corporate M&A"
    Profil      = "Collaborateur"
    Description = "Nouveau collaborateur"
    Groupe      = "Groupe1"
    Externe     = $false
}

$result = New-Faitza_AD_user -user_base $user_base -groupe_liste @("GRP_VPN")

Write-Host "Compte cree : $($result.User.SamAccountName)"
Write-Host "Mot de passe : $($result.password)"
if ($result.Groupe_erreur) { Write-Warning "Erreurs groupes : $($result.Groupe_erreur -join ', ')" }

Get-Faitza_AD_Aduser

Recherche des utilisateurs AD par OU (filtre sur Enabled) ou directement par adresse email / UPN. Retourne un objet @{ retour = []; erreur = [] }.

function Get-Faitza_AD_Aduser {
    param (
        [string[]]$ou = @("OU=Utilisateurs,OU=T2-Utilisateurs,OU=_TIER-2,DC=faitza,DC=local"),
        [string[]]$properties = @("*"),
        [bool]$enabled = $true,
        [string]$Email = ""
    )
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_AD_Aduser <<<$($PSStyle.Reset)"
        $retour = @()
        $erreur = @()
        if ($Email) {
            $retour += Get-ADUser -Filter { EmailAddress -eq $Email -or UserPrincipalName -eq $Email } -Properties $properties -ErrorAction Stop
        } else {
            foreach ($searchOu in $ou) {
                try { $retour += Get-ADUser -Filter { Enabled -eq $enabled } -SearchBase $searchOu -Properties $properties -ErrorAction Stop }
                catch { $erreur += "OU introuvable ou inaccessible : $searchOu" }
            }
        }
        return @{ retour = $retour; erreur = $erreur }
    } catch {
        throw "Erreur Get-Faitza_AD_Aduser : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_AD_Aduser >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

# Tous les utilisateurs actifs de l'OU par defaut
$result = Get-Faitza_AD_Aduser
$result.retour | Select-Object DisplayName, EmailAddress

# Par email
$result = Get-Faitza_AD_Aduser -Email "[email protected]"
$user = $result.retour[0]

# OU specifique avec proprietes limitees
$result = Get-Faitza_AD_Aduser `
    -ou @("OU=Avocats,OU=Utilisateurs,OU=T2-Utilisateurs,OU=_TIER-2,DC=faitza,DC=local") `
    -properties @("DisplayName", "EmailAddress", "LastLogonDate")

Get-Faitza_AD_PasswordExpiration

Calcule la date d'expiration du mot de passe d'un utilisateur AD depuis l'attribut msDS-UserPasswordExpiryTimeComputed et renvoie le nombre de jours restants.

function Get-Faitza_AD_PasswordExpiration {
    [CmdletBinding()]
    param ([Parameter(ValueFromPipeline)][Microsoft.ActiveDirectory.Management.ADUser]$User)
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_AD_PasswordExpiration <<<$($PSStyle.Reset)"
        if ($null -eq $User) { throw "parametre -User manquant" }
        $today = Get-Date
        $expiration_raw = $User."msDS-UserPasswordExpiryTimeComputed"
        if ($null -eq $expiration_raw -or $expiration_raw -eq 0 -or $expiration_raw -eq [long]::MaxValue) { throw "[$($User.UserPrincipalName)] mot de passe sans expiration ou attribut non renseigne" }
        $expiration_date = [datetime]::FromFileTime($expiration_raw)
        return [PSCustomObject]@{ ExpirationDate = $expiration_date; DaysLeft = ($expiration_date - $today).Days }
    } catch {
        throw "Erreur Get-Faitza_AD_PasswordExpiration : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_AD_PasswordExpiration >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$result = Get-Faitza_AD_Aduser -Email "[email protected]" `
    -properties @("msDS-UserPasswordExpiryTimeComputed")
$user = $result.retour[0]

$exp = Get-Faitza_AD_PasswordExpiration -User $user
Write-Host "Expiration : $($exp.ExpirationDate) — dans $($exp.DaysLeft) jour(s)"

Get-Faitza_AD_User_LastLogin

Retourne la dernière date de connexion d'un utilisateur AD et le nombre de jours depuis cette connexion. Renvoie 999 jours si la propriété LastLogonDate est nulle (compte jamais utilisé).

function Get-Faitza_AD_User_LastLogin {
    [CmdletBinding()]
    param ($user)
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_AD_User_LastLogin <<<$($PSStyle.Reset)"
        if ($null -eq $user) { throw "parametre -user manquant" }
        if ([string]::IsNullOrWhiteSpace($user.SamAccountName)) { throw "SamAccountName vide ou null (UPN=$($user.UserPrincipalName))" }
        if ($null -ne $user.LastLogonDate) { $lastLogon = [datetime]$user.LastLogonDate; $joursDepuis = [math]::Round((New-TimeSpan -Start $lastLogon -End (Get-Date)).TotalDays, 1); $dateAffichee = $lastLogon.ToString("dd/MM/yyyy HH:mm") }
        else { $joursDepuis = 999; $dateAffichee = "Jamais" }
        return [PSCustomObject]@{ SamAccountName = $user.SamAccountName; UPN = $user.UserPrincipalName; DerniereConnexion = $dateAffichee; JoursDepuis = $joursDepuis }
    } catch {
        throw "Erreur Get-Faitza_AD_User_LastLogin : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_AD_User_LastLogin >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

$result = Get-Faitza_AD_Aduser -properties @("LastLogonDate")
$inactifs = foreach ($user in $result.retour) {
    $login = Get-Faitza_AD_User_LastLogin -user $user
    if ($login.JoursDepuis -gt 90) { $login }
}
$inactifs | Sort-Object JoursDepuis -Descending | Format-Table

Disable-Faitza_AD_user

Désactive un compte AD par SamAccountName, UPN ou email. En cas d'échec de la première tentative, recherche le compte par email et réessaie avec son GUID. Vérifie ensuite que le compte est bien désactivé.

function Disable-Faitza_AD_user {
    [CmdletBinding()]
    param ([string]$user)
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Disable-Faitza_AD_user <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($user)) { throw "Le parametre -user est obligatoire pour la desactivation AD." }
        $TargetId = $user.Trim()
        try { Disable-ADAccount -Identity $TargetId -ErrorAction Stop }
        catch {
            $SearchResult = Get-Faitza_AD_Aduser -Email $TargetId -properties @("ObjectGUID")
            if ($SearchResult.erreur.Count -gt 0 -or $null -eq $SearchResult.retour -or $SearchResult.retour.Count -eq 0) { throw "Utilisateur definitivement introuvable dans l'Active Directory avec la valeur : $TargetId" }
            $AdUser = if ($SearchResult.retour -is [array]) { $SearchResult.retour[0] } else { $SearchResult.retour }
            $TargetId = $AdUser.ObjectGUID.ToString()
            Disable-ADAccount -Identity $TargetId -ErrorAction Stop
        }
        $user_test = Get-ADUser -Identity $TargetId -Properties Enabled
        if ($user_test.Enabled -eq $true) { throw "Test de verification echoue pour $TargetId, le compte AD est toujours actif." }
        return $true
    } catch {
        throw "Erreur Disable-Faitza_AD_user : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Disable-Faitza_AD_user >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

# Par SamAccountName
Disable-Faitza_AD_user -user "jdupont"

# Par email
Disable-Faitza_AD_user -user "[email protected]"

Remove-Faitza_AD_user

Supprime définitivement un compte AD. Même logique de fallback que Disable-Faitza_AD_user : en cas d'échec, recherche le compte par email et supprime via son GUID.

function Remove-Faitza_AD_user {
    [CmdletBinding()]
    param ([string]$userId)
    try {
        Write-Host "$($PSStyle.Background.Red)>>> Remove-Faitza_AD_user <<<$($PSStyle.Reset)"
        if ([string]::IsNullOrWhiteSpace($userId)) { throw "Le parametre -userId est obligatoire pour la suppression AD." }
        $TargetId = $userId.Trim()
        try { Remove-ADUser -Identity $TargetId -Confirm:$false -ErrorAction Stop }
        catch {
            $SearchResult = Get-Faitza_AD_Aduser -Email $TargetId -properties @("ObjectGUID")
            if ($SearchResult.erreur.Count -gt 0 -or $null -eq $SearchResult.retour -or $SearchResult.retour.Count -eq 0) { throw "Utilisateur definitivement introuvable dans l'Active Directory avec la valeur : $TargetId" }
            $AdUser = if ($SearchResult.retour -is [array]) { $SearchResult.retour[0] } else { $SearchResult.retour }
            Remove-ADUser -Identity $AdUser.ObjectGUID -Confirm:$false -ErrorAction Stop
        }
        return $true
    } catch {
        throw "Erreur Remove-Faitza_AD_user : $_"
    } finally {
        Write-Host "$($PSStyle.Background.Red)<<< Remove-Faitza_AD_user >>>$($PSStyle.Reset)"
    }
}

Exemple d'utilisation

# Par SamAccountName
Remove-Faitza_AD_user -userId "jdupont"

# Par email
Remove-Faitza_AD_user -userId "[email protected]"

// Commentaires

Aucun commentaire pour l'instant.