Validating username and password with PowerShell

Page content

Intro

Even though we have group managed service account, regular user accounts are still used by various services and applications. The passwords for these accounts are (hopefully) hard to remember and might be shared by a group of people. This means that when it’s time to modify that service , scheduled task or application we haven’t touched in years I really want to make sure I have the right username and password before I start. I also use Test-SWADCredentials in various automation scenarios to verify that I have a valid account or to validate a password reset of an account. I’m sure you can come up with a bunch more scenarios, let’s get to the technical parts!

Sure I can try to mount a network share or log on somewhere with the account to verify that it works, but service accounts should be locked down and following the principle of least privilege, it might be hard to find a suitable service, share or place to log on to test the credential. Also not all services are kind enough to tell if the logon/authentication failed because I supplied the wrong password or if the account doesn’t have access.

To solve this scenario I came up with the PowerShell function Test-SWADCredential a few years ago that I use regularly. It is part of the module SWAD, a module I wrote as a proof of concept to show how Active Directory work could be simplified by creating simple custom functions for common tasks like resetting passwords.

TestSWADCredential

Implementation

This function has two sets of parameters that can be used, either taking a username and a password in clear text, or taking a PowerShell PSCredential object. (Please be aware that entering a password in clear text is NOT recommended and should NOT be used unless you absolutely have to, the password might be stored in clear text in a numerous of places on your computer for others to read later on.) These sets are created using ParameterSets in PowerShell which you can read more in PowerShell functions and ParameterSets.

Test-SWADCredential is essentially just a wrapper around the method ValidateCredentials on System.DirectoryServices.AccountManagement.PrincipalContext and can be used to validate credentials for both machine local accounts as well as Active Directory accounts. It does not require the ActiveDirectory module and works in PowerShell 6.1, but only on Windows.

Usage

On a domain computer, you can simply run:

Test-SWADCredential -Credential (Get-Credential)

This will make PowerShell ask for a username and password which will be validated directly. You probably want to use your credential for something once validated and if you’ll use it in PowerShell you can store them in a variable before validating them, this way you don’t need to type the password twice and risk misspelling something, like this:

$Credential = Get-Credential
Test-SWADCredential -Credential $Credential

If you want to see that password you typed you can decrypt the password from a credential by converting it to a NetworkCredential like this (be aware that printing the password to you screen will write it in clear text to any transcript logs running):

$Credential.GetNetworkCredential().Password

Source code

<#
.SYNOPSIS
Test a credential

.DESCRIPTION
Test a credential object or a username and password against a machine or domain.
Can be used to validate service account passwords.

.PARAMETER Credential
Credential to test

.PARAMETER UserName
Username to test

.PARAMETER Password
Clear text password to test.
ATT!: Be aware that the password is written to screen and memory in clear text, it might also be stored in clear text on your computer.

.PARAMETER ContextType
Set where to validate the credential.
Can be Domain, Machine or ApplicationDirectory

.PARAMETER Server
Set remote computer or domain to validate against.

.EXAMPLE
Test-SWADCredential -UserName svc-my-service -Password Kgse(70g!S.
True

.EXAMPLE
Test-SWADCredential -Credential $Cred
True
#>
function Test-SWADCredential
{
[CmdletBinding(DefaultParameterSetName='Credential')]
Param
(
[Parameter(Mandatory=$true,ParameterSetName='Credential')]
[pscredential]
$Credential,
[Parameter(Mandatory=$true,ParameterSetName='Cleartext')]
[ValidateNotNullOrEmpty()]
[string]$UserName,
[Parameter(Mandatory=$true,ParameterSetName='Cleartext')]
[string]$Password,

[Parameter(Mandatory=$false,ParameterSetName='Cleartext')]
[Parameter(Mandatory=$false,ParameterSetName='Credential')]
[ValidateSet('ApplicationDirectory','Domain','Machine')]
[string]$ContextType = 'Domain',

[Parameter(Mandatory=$false,ParameterSetName='Cleartext')]
[Parameter(Mandatory=$false,ParameterSetName='Credential')]
[String]$Server
)
try {
Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop
if($PSCmdlet.ParameterSetName -eq 'ClearText') {
$EncPassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName,$EncPassword
}
try {
if($PSBoundParameters.ContainsKey('Server'))
{
$PrincipalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($ContextType,$Server)
}
else
{
$PrincipalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($ContextType)
}
}
catch {
Write-Error -Message "Failed to connect to server using contect: $ContextType"
}
try
{
$PrincipalContext.ValidateCredentials($Credential.UserName, $Credential.GetNetworkCredential().Password,'Negotiate')
}
catch [UnauthorizedAccessException]
{
Write-Warning -Message "Access denied when connecting to server."
return $false
}
catch
{
Write-Error -Exception $_.Exception -Message "Unhandled error occured"
}
}
catch {
throw
}
}