Le blog technique

Toutes les astuces #tech des collaborateurs de PI Services.

#openblogPI

Retrouvez les articles à la une

[Le saviez vous ?] – Trouver la date d’expiration du mot de passe d’un utilisateur

Trouver la date d’expiration du mot de passe dans l’Active Direcotry

Bien souvent j’entends cette question « Sais tu me dire si le mot de passe de l’utilisateur a expiré ?« .

Eh bien oui, il y a un moyen assez facile pour récupérer la date d’expiration du mot de passe d’un utilisateur, pour cela il suffit simplement de faire une requête Active Directory en demandant le retour de l’attribut ‘msDS-UserPasswordExpiryTimeComputed‘.
Voici donc une simple requête Powershell (nécessitant le module AD) pour retourner la date d’expiration du mot de passe d’un utilisateur:

# Samaccountname
$User = "toto"
# Return date
[Datetime]::FromFileTime((Get-ADUser $User -Properties "msDS-UserPasswordExpiryTimeComputed").'msDS-UserPasswordExpiryTimeComputed') 

SCOM – Récupérer les alertes pour tous les objets d’un groupe et leurs descendants

Une question sur le Reddit r/scom a récemment retenu mon attention : comment récupérer de manière récursive toutes les alertes actives pour les membres d’un groupe ainsi que pour les membres de ces membres à l’aide d’une requête SQL, pour pouvoir ensuite insérer le résultat dans un rapport.
La question peut paraitre compliquée mais il existe une table native dans la base OperationsManager qui va permettre une réponse triviale : dbo.RecursiveMembership.

Comme son nom l’indique, cette table contient de manière récursive tous les objets contenus dans un autre objet ainsi que leurs descendants, avec une indication de la profondeur (Depth) d’appartenance:

Il suffit donc de réaliser une simple jointure INNER JOIN entre la table AlertView et la table RecursiveMembership puis de spécifier une clause WHERE basée sur l’Id du groupe parent et le tour est joué!

/****** Change the group display name  ******/
DECLARE @GroupDisplayName AS VARCHAR(255) = 'Your Group display name'

SELECT *
FROM AlertView
INNER JOIN dbo.RecursiveMembership AS RM 
ON AlertView.MonitoringObjectId = RM.ContainedEntityId 
WHERE ((RM.ContainerEntityId = (SELECT BaseManagedEntityId FROM dbo.BaseManagedEntity WHERE DisplayName = @GroupDisplayName))) 

Changer le mot de passe de n’importe quel compte local sans être administrateur (à condition d’en connaitre le mot de passe actuel)

Si vous avez déjà essayé de modifier le mot de passe d’un compte local Windows via la ligne de commande (cmd ou PowerShell) sans être administrateur de la machine, vous vous êtes probablement heurté à un mur : si la boite de dialogue Ctrl+Alt+Suppr > Modifier un mot de passe permet bien de modifier le mot de passe de n’importe quel compte, les commandes disponibles nativement (net user ou Set-LocalUser) ne permettent elles que de réinitialiser celui d’un autre utilisateur que soi-même; ce qui nécessite évidemment des permissions élevées (Administrateur) sur la machine.

Les cas d’usage ne sont pas courants, mais on peut par exemple rencontrer le cas où un utilisateur simple d’un poste de travail dispose d’un second compte à privilèges sur sa machine qu’il utilise pour effectuer certaines tâches avec des permissions plus importantes via une élévation ponctuelle (RunAs). Les bonnes pratiques dictent que ce compte dispose d’un mot de passe à complexité élevée et à durée de vie faible, ce qui rend son renouvellement peu pratique s’il n’est pas possible d’utiliser de copier/coller, soit exactement la situation rencontrée dans la boite de dialogue Modifier un mot de passe.

Heureusement il est bel et bien possible de modifier ce mot de passe via Powershell à l’aide de la méthode native ChangePassword() de la classe System.DirectoryServices.AccountManagement.UserPrincipal, dont je vous présente ci-après une implémentation sous forme d’une fonction Set-LocalUserPassword facile à utiliser.

Exemple 1 : Changer le mot de passe pour l’utilisateur local « Bob », sans préciser son ancien ni son nouveau mot de passe dans la commande. La fonction se chargera de vous les demander interactivement.

Set-LocalUserPassword -Identity "Bob"

Exemple 2 : Changer votre propre mot de passe, sans préciser son ancien ni son nouveau mot de passe dans la commande. Dans ce cas pas la peine d’indiquer non-plus votre nom d’utilisateur, la fonction se chargera de tout vous demander interactivement.

Set-LocalUserPassword

Exemple 3 : L’ancien et le nouveau mot de passe peuvent aussi être fournis sous forme de SecureString dans la commande à l’aide des paramètres -OldPassword et -NewPassword.

$OldPassword = "P@ssword1" | ConvertTo-SecureString -AsPlainText -Force
$NewPassword = "P@ssword2" | ConvertTo-SecureString -AsPlainText -Force
Set-LocalUserPassword -Identity "Jane" -OldPassword $OldPassword -NewPassword $NewPassword

Le script complet est dispo sous form de gist Github : Changes the password of any local user account using its current password for verification without requiring administrator permissions

Ou directement ci-dessous :

Function Set-LocalUserPassword
{
    <#
.SYNOPSIS
    Changes the password of a local user account using the current password for verification.

.DESCRIPTION
    This function utilizes the System.DirectoryServices.AccountManagement .NET namespace to change a local user's password.
    
    Unlike the standard 'Set-LocalUser' cmdlet or 'net user' command, this function utilizes the 'ChangePassword' method. 
    This allows standard (non-administrator) users to change the password of any local account, provided they know the current password in the same way as with Set-ADAccountPassword for Active Directory Accounts.

.PARAMETER Identity
    The SAMAccountName of the local user to modify. 
    If omitted, the function will prompt for the username. 
    If this prompt is left empty, it defaults to the currently logged-in user ($env:USERNAME).

.PARAMETER OldPassword
    The current valid password for the account, passed as a SecureString.
    If omitted, the function will prompt the user to enter it securely via the host UI.

.PARAMETER NewPassword
    The desired new password for the account, passed as a SecureString.
    If omitted, the function will prompt the user to enter it securely via the host UI.

.EXAMPLE
    Set-LocalUserPassword -Identity "Bob"

    Description
    -----------
    Attempts to change the password for local user "Bob". The function will interactively prompt for the Old and New passwords.

.EXAMPLE
    Set-LocalUserPassword

    Description
    -----------
    Prompts for the target username. If the user presses Enter without typing a name, it targets the current user. 
    Then prompts for the Old and New passwords.

.INPUTS
    System.String

.OUTPUTS
    None

.NOTES
    Requirements: .NET Framework 3.5 or later (System.DirectoryServices.AccountManagement)
#>
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $false, Position = 0)]
        [String]$Identity,

        [Parameter(Mandatory = $false)]
        [securestring]$OldPassword,

        [Parameter(Mandatory = $false)]
        [securestring]$NewPassword
    )

    Add-Type -AssemblyName System.DirectoryServices.AccountManagement

    try
    {
        if ([string]::IsNullOrWhiteSpace($Identity))
        {
            $Identity = Read-Host -Prompt "Enter local username (Leave empty for current user)"
            if ([string]::IsNullOrWhiteSpace($Identity))
            { 
                $Identity = $env:USERNAME 
            }
        }

        Write-Verbose "Connecting to Local Machine Context..."
        $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new("Machine")

        Write-Verbose "Looking up user: $Identity"
        $User = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($PrincipalContext, $Identity)

        if ($null -eq $User)
        {
            throw "User '$Identity' could not be found on this computer."
        }

        if (-not $OldPassword)
        {
            $OldPassword = Read-Host -Prompt "Enter OLD password for $Identity" -AsSecureString
        }
        if (-not $NewPassword)
        {
            $NewPassword = Read-Host -Prompt "Enter NEW password for $Identity" -AsSecureString
        }

        # Convert SecureString to PlainText as this is what ChangePassword() method requires
        $OldPassPlain = [System.Net.NetworkCredential]::new("", $OldPassword).Password
        $NewPassPlain = [System.Net.NetworkCredential]::new("", $NewPassword).Password

        $User.ChangePassword($OldPassPlain, $NewPassPlain)
        
        Write-Host "Password successfully changed for user: $Identity" -ForegroundColor Green

    }
    catch
    {
        Write-Error $_.Exception.Message
        throw
    }
    finally
    {
        # Clean up unmanaged COM resources and plain text secret variables
        if ($null -ne $User) { $User.Dispose() }
        if ($null -ne $PrincipalContext) { $PrincipalContext.Dispose() }
        
        $OldPassPlain = $null
        $NewPassPlain = $null
    }
}