Powershell: How to Treat an password policy?

PauloJosMartinsCosta-9915 41 Reputation points
2021-01-06T01:01:05.577+00:00

@Andreas Baumgarten Hi, how are you?

I developed a Powershell script to create an AD account where a IT Support must populating many fields, like Name, Surname, Description, E-mail, password and Account expires (dd/mm/yyyy).

The script works almost perfectly. Function TestPasswordComplexy gives error on screen:

New-ADUser : The password does not meet the length, complexity, or history requirement of the domain.
At C:\Users\Administrator\Documents\scripts\testes\Versoes\Criar_conta_usuario_v.1.4 - test.ps1:123 char:1
+ New-ADUser -SamAccountName $SamAccountName -Name $DisplayName -Displa ...

The script still prints the outputs below before displaying the above error:

Passwords match.
.........
Valid password.
Username Test1 and password created successfully.

And do not create the user in AD.

But if I remove/comment Function TestPasswordComplexy, it works.
I would like Function to return in the step of entering the password after the failure.

Please give me a helping hand or a way forward. Thanks!

Full Script below:

# Variable FQDN  
$DNSRoot = "@weg.art.br"  
  
# Variables MemberOf  
$MemberOfEstMediVol = "Limited Internet"  
$MemberOfConc = "Ilimited Internet"  
  
  
do {  
  do {  
    Write-Host "================ User account creation script ================"  
    write-host ""  
    write-host "Type 'A' to create an account for Intern."  
    write-host "Type 'B' to create an account for Manager."  
    write-host "Type 'C' to create an account for Employee."  
    write-host "Type 'D' to create an account for Director."  
    write-host ""  
    write-host "X - Exit"  
    write-host ""  
    Write-Host "====================================================================================="  
    write-host -nonewline "Type the desired option and hit Enter: "  
    $choice = read-host  
    write-host ""  
    $ok = @("A","B","C","D","S") -contains $choice  
    if ( -not $ok) { write-host "Invalid option. Enter only the letters in the Menu." -F red   
                     write-host ""}  
                       
  }  
  until ( $ok )  
  switch ( $choice ) {  
    "A" {write-host "You chose option 'A' - Intern" -F green  
  
    # Variables that will have values ​​entered by the user  
$Path = "OU=Users,OU=Departments,dc=weg,dc=art,dc=br"  
$GivenName = Read-Host -Prompt 'Enter only the user's first name'  
$Surname = Read-Host -Prompt 'Enter the user's last name'  
$DisplayName = $GivenName + " " + $Surname  
$Description = Read-Host -Prompt 'Enter Description'  
$SamAccountName = Read-Host -Prompt 'Enter user login'  
$UserPrincipalName = $SamAccountName+$DNSRoot  
$Mail = $UserPrincipalName  
  
  
# Insert Contract Date  
Function InsertDate {  
$global:expirationDate = ""  
  $Date = Read-Host -Prompt 'Enter the contract end date - dd/mm/yyyy'  
  try{  
       $global:expirationDate = [DateTime]::ParseExact($date,'dd/MM/yyyy',$null)  
  }  
  catch{  
     Write-Host ""  
     Write-Host "            Date $date NOT VALID! " -ForegroundColor Red  
     Write-Host ""  
     Write-Host ""  
     Write-Host ""  
         Clear-Variable -name date  
         InsertDate   
  
}  
  
}  
InsertDate  
  
# Enter password, confirmation and Complexity test  
Function TestPasswordComplexy {  
   do {  
Write-Host "Enter the password carefully, because if you make a mistake you will have to enter it again." -ForegroundColor Green  
$secpass = Read-Host "Type the password" -AsSecureString  
$secpass2 = Read-Host "Confirm the Password" -AsSecureString  
$secpass_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secpass))  
$secpass2_text = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($secpass2))  
}  
while ($secpass_text -cne $secpass2_text)  
Write-Host "Passwords match." -F green  
Write-Host "........." -F green  
  
  
if ($null -eq (Get-Module -listavailable activedirectory))  
{  
    # next step required AD module.  
}  
$policy = Get-ADDefaultDomainPasswordPolicy  
  
$complexityRulesMet = 0  
if ($policy.ComplexityEnabled)  
{  
    # If complexity is enabled, the password must contain three of the following four categories  
    if ($secpass -cmatch '[a-z]') { $complexityRulesMet += 1 }  # lower case  
    if ($secpass -cmatch '[A-Z]') { $complexityRulesMet += 1 }  # capital letter  
    if ($secpass -match '\d') { $complexityRulesMet += 1 }      # number 0...9  
    if ($secpass -match '[`\~!@#$%^&*()_+-=\\{}|;'''':",./<>?\[\]]') { $complexityRulesMet += 1 }  # special characters  
}  
else  
{  
    # Domain does not impose complexity requirement  
    $complexityRulesMet = 4  
}  
  
if($secpass.Length -ge $policy.MinPasswordLength -and $complexityRulesMet -ge 3)  
{  
    Write-Host "Valid password." -ForegroundColor Green  
    Write-Host "...." -F green  
    Write-Host "........." -F green  
}  
else  
{  
    Write-Host "Password NOT valid! Password must meet complexity requirements." -ForegroundColor Red  
    Write-Host ""  
    Write-Host ""  
    TestPasswordComplexy  
      
}  
}  
TestPasswordComplexy  
  
  
New-ADUser -SamAccountName $SamAccountName -Name $DisplayName -DisplayName $DisplayName -GivenName $GivenName -Surname $Surname -Description $Description -EmailAddress $Mail -UserPrincipalName $Mail -ChangePasswordAtLogon $true -Path $Path -AccountPassword $secpass -Enabled $true     
Set-ADAccountExpiration -Identity $SamAccountName -DateTime $global:expirationDate -Server (Get-ADDomain).PDCEmulator -ErrorAction Ignore  
Add-ADPrincipalGroupMembership -Identity $SamAccountName -MemberOf $MemberOfEstMediVol  
  
Write-Host Username " -ForegroundColor green -NoNewline  
Write-Host "$DisplayName" -ForegroundColor Black -BackgroundColor White -NoNewline  
Write-Host " and password created successfully." -ForegroundColor green  
Write-Host "" -F green  
Write-Host "" -F green  
        
      break  
    }  
    "B" {write-host "You chose option 'B' - Manager" -F green  
      break  
    }  
    "C" {  
      write-host "You chose option 'C' - Employee" -F grenn  
      break  
    }  
    "D" {  
      write-host "You chose option 'D' - Director" -F grenn  
      break  
    }  
  }  
}  
until ( $choice -eq "S" )  

Thank You!

Windows
Windows
A family of Microsoft operating systems that run across personal computers, tablets, laptops, phones, internet of things devices, self-contained mixed reality headsets, large collaboration screens, and other devices.
5,495 questions
{count} votes

5 answers

Sort by: Most helpful
  1. Andreas Baumgarten 111.1K Reputation points MVP
    2021-01-06T01:36:15.897+00:00

    Hi @Paulo José Martins Costa ,
    Are you sure the complexity check works with $secpass as a secure string?

    I did some tests with different passwords and the $complexityRulesMet is always "3" even with Test1434542+# . Also the password test will set the $complexityRulesMet variable on 3 ;-). I am pretty sure the content/value of $secpass in your script is always System.Security.SecureString -> 3 of 4 criteria.

    $password = 'Test1434542+#'  
    $complexityRulesMet = 0  
    $secpass =  ConvertTo-SecureString $password -AsPlainText -Force  
    if ($secpass -cmatch '[a-z]') { $complexityRulesMet += 1 }  # lower case  
    if ($secpass -cmatch '[A-Z]') { $complexityRulesMet += 1 }  # capital letter  
    if ($secpass -match '\d') { $complexityRulesMet += 1 }      # number 0...9  
    if ($secpass -match '[`~!@#$%^&*()_+-=\\{}|;'''':",./<>?\[\]]') { $complexityRulesMet += 1 }  # special characters  
    $complexityRulesMet  
    $secpass.Length  
    

    Could this be the reason? I haven't an AD available at the moment for additional testing. Sorry.

    ----------

    (If the reply was helpful please don't forget to upvote and/or accept as answer, thank you)

    Regards
    Andreas Baumgarten

    0 comments No comments

  2. PauloJosMartinsCosta-9915 41 Reputation points
    2021-01-06T17:27:27.567+00:00

    @Andreas Baumgarten Hi, thanks for replying!
    Yes, the $secpass complexity check works 100% without the Function TestPasswordComplexy.
    Actually, the $complexityRulesMet variable is always 3 and it still gives error. I can't see where my mistake is.
    I didn't understand your code, mainly, on line 8.

    0 comments No comments

  3. Andreas Baumgarten 111.1K Reputation points MVP
    2021-01-06T17:49:26.007+00:00

    The part line 88 to 92 doesn't work at all this way. That I tried to explain in my last post above.

    My code above is doing the same complexity test your code in the function is doing. Only the input method of the password is different. Instead of Read-Host i am using the variable $password.

    You will always get a $ComplexityRulesMet = 3 in your code.
    It doesn't matter which password you enter.

    $secpass value is a secure-string. So the match and cmatch doesn't work with the password you entered as secure code. Instead the match is testing the value System.Security.SecureString which has capital letters, lowercase letters and special characters (.) = $complexityRulesMet = 3

    Thats what I said in my last post.

    Try your or my code with a "simple password" -> test you will see the $complexityRulesMet value will be 3 (even if test just contains lower case)

    Line 8 in my code just outputs the value of $complexityRulesMet ... and you will see every time the value is 3 it doesn't matter which password you enter.


    (If the reply was helpful please don't forget to upvote and/or accept as answer, thank you)

    Regards
    Andreas Baumgarten

    0 comments No comments

  4. PauloJosMartinsCosta-9915 41 Reputation points
    2021-01-06T23:07:46.587+00:00

    @Andreas Baumgarten Hi, now I understand, thank you so much!
    But just one last doubt: the password "exposed" ($password = 'Test1434542 + #'), is not less secure?

    0 comments No comments

  5. Andreas Baumgarten 111.1K Reputation points MVP
    2021-01-06T23:20:14.727+00:00

    Oh ... the $password variable I just used for testing ;-)

    But the $password variable as a normal/readable string might be the only option for you to check with cmatch and match if the password fits the complexity rules.
    Only this way you get the proper result of $complexityRulesMet.

    At the end, after the check is done, you have to convert the string in a System.Security.SecureString anyway. As far as I know the New-Aduser cmdlets requires the password as a secure string.

    I would do the complexity check on the string variable $password. If the password passed successfully the complexity check convert the $password in a secure-string variable. Use the secure-.string variable to create the new user.

    Related to the security: A user/admin is running the script and is entering the password right? So he knows the password anyway ;-)
    Does this make sense?


    (If the reply was helpful please don't forget to upvote and/or accept as answer, thank you)

    Regards
    Andreas Baumgarten

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.