33

I am working with a Powershell script that adds scheduled tasks to systems in our domain. When I run this script, it will prompt me for my password. I sometimes fat finger the password and the process starts, which locks out my account. Is there a way to verify my credentials to make sure that what I typed in will validate with the Domain?

I'd like to find a way to query the Domain controller. I've done some Google searches and I should be able to do a WMI query and trap for an error. I would like to avoid that style of validation if possible.

Any ideas? Thanks in advance.

Doltknuckle
  • 1,254
  • 7
  • 25
  • 32

4 Answers4

31

I have this in my library:

$cred = Get-Credential #Read credentials
 $username = $cred.username
 $password = $cred.GetNetworkCredential().password

 # Get current domain using logged-on user's credentials
 $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName
 $domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$UserName,$Password)

if ($domain.name -eq $null)
{
 write-host "Authentication failed - please verify your username and password."
 exit #terminate the script.
}
else
{
 write-host "Successfully authenticated with domain $domain.name"
}
Jim B
  • 24,081
  • 4
  • 36
  • 60
  • 1
    If I'm not mistaken, this would end up sending the password in plain text across the network, right? If it does, then am I right in assuming that `AccountManagement.PrincipalContext.ValidateCredentials()` does not (if you provide a securestring for the password)? – Code Jockey Jul 23 '15 at 19:00
  • Why aren't you using the `ActiveDirectory` module to do your LDAP query? – Kellen Stuart Mar 28 '18 at 18:50
  • 2
    6 years ago there was no active directory module – Jim B Mar 28 '18 at 23:36
  • This script also helps for situations where you can't install the AD PowerShell modules for one reason or another. – Dodzi Dzakuma Jul 11 '18 at 16:03
18

This is what I've used in the past; it's supposed to work for local machine accounts and 'application directory', but so far I've only used it successfully with AD credentials:

    function Test-Credential {
    <#
    .SYNOPSIS
        Takes a PSCredential object and validates it against the domain (or local machine, or ADAM instance).

    .PARAMETER cred
        A PScredential object with the username/password you wish to test. Typically this is generated using the Get-Credential cmdlet. Accepts pipeline input.

    .PARAMETER context
        An optional parameter specifying what type of credential this is. Possible values are 'Domain','Machine',and 'ApplicationDirectory.' The default is 'Domain.'

    .OUTPUTS
        A boolean, indicating whether the credentials were successfully validated.

    #>
    param(
        [parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [System.Management.Automation.PSCredential]$credential,
        [parameter()][validateset('Domain','Machine','ApplicationDirectory')]
        [string]$context = 'Domain'
    )
    begin {
        Add-Type -assemblyname system.DirectoryServices.accountmanagement
        $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::$context) 
    }
    process {
        $DS.ValidateCredentials($credential.UserName, $credential.GetNetworkCredential().password)
    }
}
jbsmith
  • 1,301
  • 7
  • 13
  • I'd love to hear if someone notices this - I believe that when I use ValidateCredentials() in this manner with an incorrect password, it seems to trigger two (2) bad password attempts - I can't control the number of attempts threshold on our domain, and it's low, so I'd prefer not to have two bad attempts when I'm making a single call... can anyone see this as well? – Code Jockey Jul 23 '15 at 18:53
  • Are you using domain\user or UPN (user@domain) format? I'm not in a position to replicate this, but the following URL describes a similar problem: https://social.msdn.microsoft.com/Forums/vstudio/en-US/e2328cb6-8764-46bf-94eb-4773e2912d74/wrong-lockout-policy-count-when-using-principalcontextvalidatecredentials?forum=netfxbcl – jbsmith Jul 27 '15 at 21:36
  • You should be able to just pass `$context` as the argument to the constructor. PowerShell will automatically convert strings to an enum. Better yet, just make `[System.DirectoryServices.AccountManagement.ContextType]` the type of `$context`. Also, why are you using `begin` and `process` here? The pipeline seems like an odd way to use this function. – jpmc26 Mar 08 '17 at 22:53
  • @jpmc26: typing the `$context` parameter `[System.DirectoryServices.AccountManagement.ContextType]` is not an option, because the assembly containing isn't loaded until the function _body_ is executed; using the pipeline is helpful if you want to validate multiple credentials. – mklement Mar 09 '18 at 18:52
  • @mklement There's no reason the `Add-Type` call can't be moved outside the function, to before its definition is executed. I'm hesitant to have an `Add-Type` call unconditionally run repeatedly inside the function even if it's already loaded, anyway. Validating multiple credentials simultaneously seems like an odd situation in the first place. In the rare case that's what you want, you can easily wrap the call in `ForEach-Object`, so I don't see a reason to complication the function with it. – jpmc26 Mar 09 '18 at 19:00
  • @jpmc26: Understood, but repeated `Add-Type` calls with the same assembly are benign and fast, and the advantage of having it inside your function is that the function is then self-contained. To me that outweighs the manual duplication of the 3 enum values (that are unlikely to change), but now we've covered both angles and future readers can choose what's right for them. – mklement Mar 09 '18 at 19:05
  • @mklement Functions don't need to be "self contained." That's what scripts and modules are for. – jpmc26 Mar 09 '18 at 19:19
1

I've found this post useful however it didn't solve my problem as I was trying to run it from a script with the local admin account logged on. It does not seem to work as local admin (only when logged on as a domain user).

However I did finally manage to get a working solution and since it was so much trouble I thought I'd share it here so anyone else with this problem will have the answer right here. Both answers on the one page depending on your needs.

Note that higher up in the scipt (not included here as this is just the get-credentials section) powergui is installed and is a requirement for this code below (as well as the "Add-PSSnapin Quest.ActiveRoles.ADManagement" line). Not sure what powergui does that's different but no one else could tell me and it works.

Subsitute your own domain name in the "domain_name" sections.

#Get credentials
$credential_ok = 0
while ($credential_ok -ne 1)
{
    $credential = get-credential
    $result = connect-qadservice -service *domain_name* -credential $credential
    [string]$result_string = $result.domain
    if ($result_string -eq "*domain_name*")
    {
        $credential_ok = 1
        #authenticated
    }
    else
    {
        #failed
    }     
}
$username = $credential.username 
$password = $credential.GetNetworkCredential().password 

$date = get-date
Add-Content "c:\lbin\Install_log.txt" "Successfully authenticated XP script as $username $date"
Michael
  • 11
  • 2
1

(yet) Another version:

param([string]$preloadServiceAccountUserName = "")

function HarvestCredentials()
{

        [System.Management.Automation.PSCredential]$credentialsOfCurrentUser = Get-Credential -Message "Please enter your username & password" -UserName $preloadServiceAccountUserName

        if ( $credentialsOfCurrentUser )
        {
            $credentialsOfCurrentUser = $credentialsOfCurrentUser
        }
        else
        {
            throw [System.ArgumentOutOfRangeException] "Gui credentials not entered correctly"          
        }

    Try
    {


        # see https://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentry.path(v=vs.110).aspx
        # validate the credentials are legitimate
        $validateCredentialsTest = (new-object System.DirectoryServices.DirectoryEntry ("WinNT://"+$credentialsOfCurrentUser.GetNetworkCredential().Domain), $credentialsOfCurrentUser.GetNetworkCredential().UserName, $credentialsOfCurrentUser.GetNetworkCredential().Password).psbase.name
        if ( $null -eq  $validateCredentialsTest)
        {
            throw [System.ArgumentOutOfRangeException] "Credentials are not valid.  ('" + $credentialsOfCurrentUser.GetNetworkCredential().Domain + '\' + $credentialsOfCurrentUser.GetNetworkCredential().UserName + "')"
        }
        else
        {
            $t = $host.ui.RawUI.ForegroundColor
            $host.ui.RawUI.ForegroundColor = "Magenta"
            Write-Output "GOOD CREDENTIALS"
            $host.ui.RawUI.ForegroundColor = $t
        }
    }
    Catch
    {

        $ErrorMessage = $_.Exception.Message
        $FailedItem = $_.Exception.ItemName
        $StackTrace = $_.Exception.StackTrace

        $t = $host.ui.RawUI.ForegroundColor
        $host.ui.RawUI.ForegroundColor = "Red"

        Write-Output "Exception - $ErrorMessage"
        Write-Output "Exception - $FailedItem"
        Write-Output "Exception - $StackTrace"

        $host.ui.RawUI.ForegroundColor = $t

        throw [System.ArgumentOutOfRangeException] "Attempt to create System.DirectoryServices.DirectoryEntry failed.  Most likely reason is that credentials are not valid."
    }

}


Try
{

    HarvestCredentials

}
Catch
{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    $StackTrace = $_.Exception.StackTrace

    $t = $host.ui.RawUI.ForegroundColor
    $host.ui.RawUI.ForegroundColor = "Red"

    Write-Output "Exception - " + $ErrorMessage
    Write-Output "Exception - " + $FailedItem
    Write-Output "Exception - " + $StackTrace

    $host.ui.RawUI.ForegroundColor = $t

    Break
}
Finally
{
    $Time=Get-Date
    Write-Output "Done - " + $Time
}

and

.\TestCredentials.ps1 -preloadServiceAccountUserName "mydomain\myusername"
granadaCoder
  • 136
  • 4