Last active
March 5, 2026 13:30
-
-
Save PanosGreg/8a027b079eac32ba72afdea50343a136 to your computer and use it in GitHub Desktop.
Verify Windows credentials (either in Active Directory or Locally)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Test-WindowsCredential { | |
| <# | |
| .Synopsis | |
| Verify Active Directory and/or Local credentials | |
| .EXAMPLE | |
| Test-WindowsCredential -Username user1 -Password Secret01 | |
| .EXAMPLE | |
| $creds = Get-WindowsCredential | |
| Test-WindowsCredential -Credential $creds | |
| .EXAMPLE | |
| Test-WindowsCredential -Username user1 -Password Secret01 -PassThru | |
| #> | |
| [CmdletBinding(DefaultParameterSetName = 'PSCreds')] | |
| [OutputType([Boolean],[object])] # <-- by default returns a bool, and with PassThru it returns an object | |
| Param ( | |
| [Parameter(Mandatory=$true,Position=0,ParameterSetName = 'PlainText')] | |
| [string]$Username, | |
| [Parameter(Mandatory=$true,Position=1,ParameterSetName = 'PlainText')] | |
| [string]$Password, | |
| [Parameter(Mandatory=$true,Position=0,ParameterSetName = 'PSCreds')] | |
| [pscredential]$Credential, | |
| [Validateset('Domain','Machine','ApplicationDirectory')] # AppDirectory is for ADAM | |
| [string]$Context = 'Domain', | |
| [string]$Server, # <-- optional parameter, can take either a hostname or an IP address | |
| [switch]$PassThru, # <-- return an object with more info instead of a boolean | |
| [switch]$DontCheckProfile | |
| ) | |
| # get the user/pass from the pscreds | |
| if ($PSCmdlet.ParameterSetName -eq 'PSCreds') { | |
| $Username = $Credential.UserName | |
| $Password = $Credential.GetNetworkCredential().Password | |
| } | |
| $WillContinue = $true # <-- we'll use this flag to skip parts if need be | |
| # strip the domain from the username (the username argument must not be in the form of domain\username or username@domain) | |
| if ($Username.IndexOf('\') -ge 1) {$Username = $Username.Split('\')[1]} | |
| elseif ($Username.IndexOf('@') -ge 1) {$Username = $Username.Split('@')[0]} | |
| # see if the user's profile already exists in the system | |
| if (-not $DontCheckProfile) { | |
| # get the domain we are currently in | |
| if ($env:USERDOMAIN -eq 'WORKGROUP') {$CurrentDomain = $env:COMPUTERNAME} # <-- this happens if running as SYSTEM | |
| else {$CurrentDomain = $env:USERDOMAIN} | |
| $NtAccount = [Security.Principal.NTAccount]::new($CurrentDomain, $Username) | |
| # find the Security ID (SID) of the user | |
| try {$UserSID = $NtAccount.Translate([Security.Principal.SecurityIdentifier]).Value} | |
| catch { | |
| if (-not $PassThru) {Write-Warning "Could not find user $Username in $CurrentDomain"} | |
| $CanLogin = $false | |
| $LoginError = $_.Exception | |
| $WillContinue = $false | |
| } | |
| if ($WillContinue) { | |
| # load the CimCmdlets module | |
| if ((Get-Module).Name -notcontains 'CimCmdlets') {Import-Module -Name CimCmdlets -Verbose:$false} | |
| # get the local user profile | |
| $UserProfile = Get-CimInstance -ClassName Win32_UserProfile -Filter "SID = '$UserSID'" -Verbose:$false | |
| # inform the user that the profile will be created | |
| if ($null -eq $UserProfile) { | |
| Write-Verbose "The user $Username does not have a profile on this computer ($env:COMPUTERNAME)" | |
| Write-Verbose 'The credential check will take a bit longer, because the user profile will also be created for the 1st time.' | |
| } | |
| } #if WillContinue | |
| } #if check profile | |
| # load the .net type for DirectoryServices | |
| if (-not ('System.DirectoryServices.AccountManagement.ContextType' -as [type]) -and $WillContinue) { | |
| Add-Type -AssemblyName System.DirectoryServices.AccountManagement -ErrorAction Stop | |
| } | |
| # check the account | |
| if ($WillContinue) { | |
| $LoginDuration = Measure-Command -Expression { | |
| $CtxType = [System.DirectoryServices.AccountManagement.ContextType]::$Context | |
| try { | |
| if ($PSBoundParameters.ContainsKey('Server')) { | |
| $Principal = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($CtxType,$Server) # <-- this is the connection to the server | |
| } | |
| else { | |
| $Principal = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($CtxType) | |
| } | |
| $CanLogin = $Principal.ValidateCredentials($Username, $Password) # <-- this is the actual validation | |
| } | |
| catch { | |
| if (-not $PassThru) {Write-Warning "There was an error when trying the credential in the '$Context' context"} | |
| $CanLogin = $false | |
| $LoginError = $_.Exception # <-- alternatively we could do: "Resolve-Error $_" if we have the Resolve-Error function | |
| $WillContinue = $false | |
| } | |
| } #Measure-Command | |
| } #if WillContinue | |
| # throw if erroraction is stop | |
| if ($ErrorActionPreference -eq 'Stop' -and $LoginError) { | |
| $PSCmdlet.ThrowTerminatingError((Write-Error -Exception $LoginError 2>&1)) | |
| } | |
| # show the output | |
| if ($PassThru) { | |
| $out = [pscustomobject]@{ | |
| PSTypeName = 'Credential.Test' | |
| Context = $Context | |
| LoginFrom = $env:COMPUTERNAME | |
| LoginTo = $Principal.ConnectedServer | |
| LoginAsUser = $Username | |
| CanLogin = $CanLogin | |
| LoginDuration = $LoginDuration | |
| } | |
| if ($LoginError) {$out | Add-Member -NotePropertyMembers @{LoginError = $LoginError}} | |
| } | |
| else {$out = $CanLogin} # <-- boolean | |
| # clean up | |
| if ($Principal) {$Principal.Dispose()} | |
| # show the output | |
| Write-Output $out | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment