PowerShell "New-ADUSer" password complexity error - powershell

I will create 100 random AD users via script. But , I am getting the following error message for some random users.
New-ADUser : The password does not meet the length, complexity, or history requirement of the domain.
Get-ADDefaultDomainPasswordPolicy
output :
ComplexityEnabled : True
DistinguishedName : DC=contoso,DC=local
LockoutDuration : 00:00:00
LockoutObservationWindow : 69.10:39:00
LockoutThreshold : 5
MaxPasswordAge : 90.00:00:00
MinPasswordAge : 3.00:00:00
MinPasswordLength : 8
objectClass : {domainDNS}
objectGuid : 346664da-c908-470e-9fc3-5487983c92ae
PasswordHistoryCount : 12
ReversibleEncryptionEnabled : False
Here is my script :
$UserList = Import-CSV -Path C:\temp\CreateUsers.csv
$targetOU = 'OU=Test,DC=contoso,DC=local'
$upnDomain = 'contoso.local'
$Path = "C:\temp\output.csv"
foreach ($Person in $UserList) {
#If username does not exist
if(-not(Get-ADUser -Filter "SamAccountName -eq '$($Person.Sam)'")) {
$PassWord = -join ([char[]]"!##$%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" |Get-Random -Count 10)
$useritems = #{
GivenName = $Person.Firstname
Surname = $Person.LastName
AccountPassword = ConvertTo-SecureString -String $Password -AsPlainText -force
ChangePasswordAtLogon = $false
Enabled = $true
DisplayName = "$($Person.Firstname) $($Person.Lastname)"
Name = "$($Person.Firstname) $($Person.Lastname)"
SamAccountName = $Person.Sam
UserPrincipalName = "$($Person.Sam)#$upnDomain"
}
New-ADUser #useritems -Path $targetOU
Add-Content -Path $Path -Value "Username: $($Person.Sam) Password: $Password"
}
}

To avoid creating passwords that do not comply to the domains password complexity rules, you could use this helper function:
function Test-DomainPassword {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Password,
[Parameter(Mandatory = $false)]
[string]$AccountSamAccountName = $null,
[Parameter(Mandatory = $false)]
[string]$AccountDisplayName = $null
)
# [Microsoft.ActiveDirectory.Management.ADEntity]
$PasswordPolicy = Get-ADDefaultDomainPasswordPolicy -ErrorAction SilentlyContinue
If ($Password.Length -lt $PasswordPolicy.MinPasswordLength) {
Write-Verbose "Password '$Password' is too short. Minimal length is $($PasswordPolicy.MinPasswordLength)"
return $false
}
if (![string]::IsNullOrWhiteSpace($AccountSamAccountName) -and $Password -match $AccountSamAccountName) {
Write-Verbose "The password '$Password' includes the users SamAccountName"
return $false
}
if (![string]::IsNullOrWhiteSpace($AccountDisplayName)) {
# if ANY PART of the display name that is split by the characters below, the password should fail the complexity rules.
$tokens = $AccountDisplayName.Split(",.-,_ #`t")
foreach ($token in $tokens) {
if (($token) -and ($Password -match "$token")) {
Write-Verbose "The password '$Password' includes (part of) the users DisplayName"
return $false
}
}
}
if ($PasswordPolicy.ComplexityEnabled) {
# check for presence of
# - Uppercase: A through Z, with diacritic marks, Greek and Cyrillic characters
if ($Password -cnotmatch "[A-Z\p{Lu}\s]") {
Write-Verbose "The password '$Password' is missing Uppercase characters"
return $false
}
# - Lowercase: a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters
if ($Password -cnotmatch "[a-z\p{Ll}\s]") {
Write-Verbose "The password '$Password' is missing Lowercase characters"
return $false
}
# - Base 10 digits (0 through 9)
if ($Password -notmatch "[\d]") {
Write-Verbose "The password '$Password' is missing digits (0-9)"
return $false
}
# - Nonalphanumeric characters: ~!##$%^&*_-+=`|\(){}[]:;”‘<>,.?/
if ($Password -notmatch "[^\w]") {
Write-Verbose "The password '$Password' is missing Nonalphanumeric characters: ~!##$%^&*_-+=`|\(){}[]:;`"'<>,.?/"
return $false
}
}
return $true
}
and use that in a loop:
do {
$PassWord = -join ([char[]]"!##$%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" | Get-Random -Count 10)
} until (Test-DomainPassword -Password $PassWord)
If you add the -Verbose switch to the function call, you will see the reason why a password would fail.
Of course, this cannot test if a password meets the domains history requirement, that remains a trial and error thing..
Or you could re-use the code from Test-DomainPassword and create a helper function that returns a valid password like this:
function New-DomainPassword {
[CmdletBinding()]
Param (
[Parameter(Mandatory = $false)]
[int]$Length = 10,
[Parameter(Mandatory = $false)]
[string]$SamAccountName = $null,
[Parameter(Mandatory = $false)]
[string]$DisplayName = $null
)
# [Microsoft.ActiveDirectory.Management.ADEntity]
$passwordPolicy = Get-ADDefaultDomainPasswordPolicy -ErrorAction SilentlyContinue
$passwordLength = [math]::Max($Length, $passwordPolicy.MinPasswordLength)
$count = 1
while ($true) {
Write-Verbose ("Generating valid password attempt: {0}" -f $count++)
$password = -join ([char[]]"!##$%^&*0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" | Get-Random -Count $passwordLength)
if (![string]::IsNullOrWhiteSpace($SamAccountName) -and $password -match $SamAccountName) {
continue # bad password, skip and try another one
}
if (![string]::IsNullOrWhiteSpace($DisplayName)) {
# if ANY PART of the display name that is split by the characters below, the password should fail the complexity rules.
$tokens = $DisplayName.Split(",.-,_ #`t")
$bad = foreach ($token in $tokens) {
if (($token) -and ($password -match $token)) { $true; break }
}
if ($bad) { continue } # bad password, skip and try another one
}
if ($passwordPolicy.ComplexityEnabled) {
# check for presence of
# - Uppercase: A through Z, with diacritic marks, Greek and Cyrillic characters
if ($password -cnotmatch "[A-Z\p{Lu}\s]") {
continue # bad password, skip and try another one
}
# - Lowercase: a through z, sharp-s, with diacritic marks, Greek and Cyrillic characters
if ($password -cnotmatch "[a-z\p{Ll}\s]") {
continue # bad password, skip and try another one
}
# - Base 10 digits (0 through 9)
if ($password -notmatch "[\d]") {
continue # bad password, skip and try another one
}
# - Nonalphanumeric characters: ~!##$%^&*_-+=`|\(){}[]:;”‘<>,.?/
if ($password -notmatch "[^\w]") {
continue # bad password, skip and try another one
}
}
# apparently all tests succeeded, so break out of the while loop
break
}
# return the new password
$password
}
and use it like this:
$UserList = Import-CSV -Path C:\temp\CreateUsers.csv
$targetOU = 'OU=Test,DC=contoso,DC=local'
$upnDomain = 'contoso.local'
$Path = "C:\temp\output.csv"
foreach ($Person in $UserList) {
#If username does not exist
if(-not(Get-ADUser -Filter "SamAccountName -eq '$($Person.Sam)'")) {
$accountName = $Person.Sam
$displayName = "$($Person.Firstname) $($Person.Lastname)"
$Password = New-DomainPassword -Length 10 -SamAccountName $accountName -DisplayName $displayName
$useritems = #{
GivenName = $Person.Firstname
Surname = $Person.LastName
AccountPassword = ConvertTo-SecureString -String $Password -AsPlainText -force
ChangePasswordAtLogon = $false
Enabled = $true
DisplayName = $displayName
Name = "$($Person.Firstname) $($Person.Lastname)"
SamAccountName = $accountName
UserPrincipalName = "$($Person.Sam)#$upnDomain"
Path = $targetOU
}
New-ADUser #useritems
Add-Content -Path $Path -Value "Username: $accountName Password: $Password"
}
}

Your random password generator is very poor and you do not guarantee that it meets your requirements. There is a .NET function, that you can use to generate random passwords: .GeneratePassword(Int32, Int32).
The first argument specifies the length of the password and the second argument specifies the minimum number of special characters.
You can use the following PowerShell function to generate a random password according to your needs:
function Get-RandomPassword {
param($Length = 8) # Default length of 8, if not provided
# Generate a random password with at least one special character
$password = [System.Web.Security.Membership]::GeneratePassword($Length, 1)
# Check password complexity
if ($password -cnotmatch '[A-Z]' -or $password -cnotmatch '[a-z]' -or $password -cnotmatch '\d') {
# Recursively generate a new password if the complexity requirements are not met
return Get-RandomPassword -Length $Length
} else {
# Return the password, if the complexity requirements are met
return $password
}
}
Example usage:
PS C:\> Get-RandomPassword -Length 10
A8$JfE1e%i
The function generates a random password of the provided length and with at least one special character. It then checks if all other (your individual) requirements are met. If not, the function will call itself recursively, until a password is found, that meets all complexity criteria.
To be honest, it's not the best design, as it might run forever in theory, but you will rather win a lottery than seeing this function running forever ;-)

Related

Powershell script for generating

I have a question regarding the script.
I found a script that generates a password, but how to do it so that, for example, you can set the length of the password and probably some characters when generating, if you do not set it only after that, let it generate a default password, for some length
function Get-RandomCharacters($length, $characters) {
$random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length }
$private:ofs=""
return [String]$characters[$random]
}
function Scramble-String([string]$inputString){
$characterArray = $inputString.ToCharArray()
$scrambledStringArray = $characterArray | Get-Random -Count $characterArray.Length
$outputString = -join $scrambledStringArray
return $outputString
}
$password = Get-RandomCharacters -length 5 -characters 'abcdefghiklmnoprstuvwxyz'
$password += Get-RandomCharacters -length 1 -characters 'ABCDEFGHKLMNOPRSTUVWXYZ'
$password += Get-RandomCharacters -length 1 -characters '1234567890'
$password += Get-RandomCharacters -length 1 -characters '!"§$%&/()=?}][{##*+'
Write-Host $password
$password = Scramble-String $password
Write-Host $password
There are more ways than one to generate a password.
For instance this one:
function New-Password {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[ValidateRange(8, 128)]
[int]$TotalLength = 10,
[int]$Digits = 3,
[int]$Symbols = 2
)
# the number of symbols must be => 0 and <= $TotalLength
$Symbols = [math]::Max(0,[math]::Min($Symbols, $TotalLength))
# same for the number of digits
$Digits = [math]::Max(0,[math]::Min($Digits, $TotalLength))
$alphas = $TotalLength - ($Digits + $Symbols)
if ($alphas -lt 0) {
throw "Too many digits or symbols for the total password length"
}
$list = [System.Collections.Generic.List[char]]::new()
if ($Digits -gt 0) { $list.AddRange([char[]]([char[]]'0123456789' | Get-Random -Count $Digits)) }
if ($Symbols -gt 0) { $list.AddRange([char[]]([char[]]'!##$%^&*()_-+=[{]};:<>|./?~' | Get-Random -Count $Symbols)) }
if ($alphas -gt 0) { $list.AddRange([char[]]([char[]]'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' | Get-Random -Count $alphas)) }
($list | Sort-Object {Get-Random}) -join ''
}
Usage:
$password = New-Password -TotalLength 12 -Symbols 4 -Digits 2 # or without parameters to accept the defaults
Or this:
$TotalLength = 10
$password = ([char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9 | Sort-Object {Get-Random})[0..$TotalLength] -join ''
Or this:
Add-Type -AssemblyName System.Web
# the first parameter specifies the total password length.
# the second one specifies the minimum number of non-alphanumeric characters
$password = [System.Web.Security.Membership]::GeneratePassword(10, 3)
Note: Apparently you cannot use this last method in .NET Core as it does not support System.Web.dll. See this github issue
How about condensing down into a smaller function:
Function Generate-Password
{
-join ('abcdefghkmnrstuvwxyzABCDEFGHKLMNPRSTUVWXYZ23456789$%&*#'.ToCharArray() | Get-Random -Count 12) # Add characters and/or password length to suit your organisation's requirements
}
And then call whenever you need it:
$password = Generate-Password
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
replace line
function Get-RandomCharacters($length, $characters) {
with:
function Get-RandomCharacters($length = 8, $characters = 'abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890') {
it defines default length to 8 chars, and charset as abcdefghiklmnoprstuvwxyzABCDEFGHKLMNOPRSTUVWXYZ1234567890

Get-ADPrincipalGroupMembership An unspecified error has occurred

I am getting errors with Get-ADPrincipalGroupMembership command on Windows 10 (x64) machine. I have installed the required RSAT- 'Active directory Domain service and Lightweight Directory service tools' and 'Server manager' dependencies as specified int this document. I am able to execute Get-AdUser and see the results but Get-ADPrincipalGroupMembership is throwing below error.
PS C:\Users\JYOTHI> Get-ADPrincipalGroupMembership jyothi
Get-ADPrincipalGroupMembership : An unspecified error has occurred
At line:1 char:1
+ Get-ADPrincipalGroupMembership gapalani
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (jyothi:ADPrincipal) [Get-ADPrincipalGroupMembership], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADPrincipalGroupMembership
I can try the other way
(Get-Aduser jyothi -Properties MemberOf | Select MemberOf).MemberOf
but like to know what is the fix for Get-ADPrincipalGroupMembership
As you have noticed, Get-ADPrincipalGroupMembership fails with an obscure error if the reference object's name contains certain characters, or if it's a member of one or more groups that contain certain characters in their names.
I don't have definitive proof, but my testing indicates that the underlying issue is that Get-ADPrincipalGroupMembership, internally, uses ADSI and fails to correctly escape distinguished names that contain characters that need to be escaped. (If this is the case, Microsoft should be using the IADsPathname interface to escape names correctly. This would be an embarrassing oversight on their part.)
Unfortunately, this problem renders the cmdlet broken and unusable in production environments.
Here's a relatively short PowerShell script that doesn't suffer from this annoyance and also supports retrieving recursive group memberships:
# Get-ADGroupMembership.ps1
# Written by Bill Stewart
#requires -version 2
# Version history:
# 1.0 (2019-12-02)
# * Initial version. Only searches the current domain.
<#
.SYNOPSIS
Gets the distinguished names of the Active Directory groups that have a specified object as a member.
.DESCRIPTION
Gets the distinguished names of the Active Directory groups that have a specified object, represented by the -Identity parameter, as a member.
.PARAMETER Identity
Specifies an Active Directory object. You can specify either the distinguishedName or the sAMAccountName of the object.
.PARAMETER Recursive
Specifies to include the object's nested group memberships.
.NOTES
If you use the ActiveDirectory PowerShell module and want Microsoft.ActiveDirectory.Management.ADGroup objects as output, pipe this command's output to the Get-ADGroup cmdlet.
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of:
PS C:\> Get-ADGroupMembership kendyer
.EXAMPLE
Get the distinguished names of the groups that the kendyer account is a member of, including nested groups:
PS C:\> Get-ADGroupMembership kendyer -Recursive
.EXAMPLE
Get the ADGroup objects representing the groups that the kendyer account is a member of (requires the Active Directory module):
PS C:\> Get-ADGroupMembership kendyer | Get-ADGroup
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,ValueFromPipeline = $true)]
[String[]] $Identity,
[Switch] $Recursive
)
begin {
$CommandName = $MyInvocation.MyCommand.Name
# Set up Pathname COM object
$ADS_ESCAPEDMODE_ON = 2
$ADS_SETTYPE_DN = 4
$ADS_FORMAT_X500_DN = 7
$Pathname = New-Object -ComObject "Pathname"
if ( -not $Pathname ) {
return
}
[Void] $Pathname.GetType().InvokeMember("EscapedMode","SetProperty",$null,$Pathname,$ADS_ESCAPEDMODE_ON)
# Outputs correctly escaped distinguished name using Pathname object
function Get-EscapedName {
param(
[String] $distinguishedName
)
[Void] $Pathname.GetType().InvokeMember("Set","InvokeMethod",$null,$Pathname,#($distinguishedName,$ADS_SETTYPE_DN))
$Pathname.GetType().InvokeMember("Retrieve","InvokeMethod",$null,$Pathname,$ADS_FORMAT_X500_DN)
}
# Outputs the memberOf attribute of an object using paged search (in case
# an object is a member of a large number of groups)
function Get-MemberOfAttribute {
param(
[String] $distinguishedName,
[Ref] $memberOf,
[Switch] $recursive
)
$searcher = [ADSISearcher] "(objectClass=*)"
$searcher.SearchRoot = [ADSI] "LDAP://$(Get-EscapedName $distinguishedName)"
$lastQuery = $false
$rangeStep = 1500
$rangeLow = 0
$rangeHigh = $rangeLow + ($rangeStep - 1)
do {
if ( -not $lastQuery ) {
$property = "memberOf;range={0}-{1}" -f $rangeLow,$rangeHigh
}
else {
$property = "memberOf;range={0}-*" -f $rangeLow
}
$searcher.PropertiesToLoad.Clear()
[Void] $searcher.PropertiesToLoad.Add($property)
$searchResults = $searcher.FindOne()
if ( $searchResults.Properties.Contains($property) ) {
foreach ( $searchResult in $searchResults.Properties[$property] ) {
if ( $memberOf.Value.Count -gt 100 ) {
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-CurrentOperation $searchResult
}
if ( $recursive ) {
if ( -not $memberOf.Value.Contains($searchResult) ) {
Get-MemberOfAttribute $searchResult $memberOf -recursive
}
}
if ( -not $memberOf.Value.Contains($searchResult) ) {
$memberOf.Value.Add($searchResult)
}
}
$done = $lastQuery
}
else {
if ( -not $lastQuery ) {
$lastQuery = $true
}
else {
$done = $true
}
}
if ( -not $lastQuery ) {
$rangeLow = $rangeHigh + 1
$rangeHigh = $rangeLow + ($rangeStep - 1)
}
}
until ( $done )
Write-Progress `
-Activity $CommandName `
-Status "Getting membership of '$distinguishedName'" `
-Completed:$true
}
function Get-ADGroupMembership {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[String] $identity,
[Switch] $recursive
)
$ldapString = $identity -replace '\\','\5c' -replace '\(','\28' -replace '\)','\29' -replace '\*','\2a' -replace '\/','\2f'
$searcher = [ADSISearcher] "(|(distinguishedName=$ldapString)(sAMAccountName=$ldapString))"
try {
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$memberOf = New-Object Collections.Generic.List[String]
Get-MemberOfAttribute $searchResult.Properties["distinguishedname"][0] ([Ref] $memberOf) -recursive:$recursive
$memberOf
}
}
else {
Write-Error "Cannot find an object with identity '$identity'." -Category ObjectNotFound
}
}
catch {
Write-Error -ErrorRecord $_
}
finally {
$searchResults.Dispose()
}
}
}
process {
foreach ( $IdentityItem in $Identity ) {
Get-ADGroupMembership $IdentityItem -recursive:$Recursive
}
}
I've also added this script as a public gist on github in case something needs fixing or if I add new features.
Get-ADPrincipalGroupMembership -Identity "jyothi"

Combining Powershell script to call function and get the AD attributes value

I need to use a PowerShell function to format the phone number like below:
Function Format-TelephoneNumber
{
Param (
[Parameter(ValueFromPipeline = $true, Position = 0)]
[Alias('Number')]
[string]$TelephoneNumber,
[Parameter(Position = 1)]
[string]$DefaultCountryCode = '+44'
)
Process
{
$formattedNumber = $TelephoneNumber -replace '[\x09 ]'
If ($formattedNumber -match '\A(?<CountryCode>\+[1-9]\d|0)(?<Number>\d*)\Z')
{
If ($Matches['CountryCode'] -eq '0')
{
$countryCode = $defaultCountryCode
}
Else
{
$countryCode = $Matches['CountryCode']
}
$formattedNumber = $countryCode + ' '
$formattedNumber += -join $Matches['Number'][0 .. 2] + ' '
$formattedNumber += -join $Matches['Number'][3 .. 5] + ' '
$formattedNumber += -join $Matches['Number'][6 .. 8]
$formattedNumber
}
Else
{
Write-Error "Unable to parse the string '$($number)' as telephone number!"
}
}
}
The below script is for retrieving the value of Phone Number from AD Attribute:
$sysInfo = New-Object -ComObject 'ADSystemInfo'
$userDN = $sysInfo.GetType().InvokeMember('UserName', 'GetProperty', $null, $sysInfo, $null)
$adUser = [ADSI]"LDAP://$($userDN)"
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($sysInfo)
Write-Host $adUser.mobile.ToString() -ForegroundColor Green
How can I call the script?
I have tried below but failed:
Write-Host "This is raw from AD: $($adUser.mobile.ToString())" -ForegroundColor Yellow
$Formatted = Format-TelephoneNumber -TelephoneNumber $adUser.mobile.ToString()
Write-Host "This is processed using Function: " "$($Formatted)" -ForegroundColor Green
Personally, I'd use a different Format-TelephoneNumber function because as James C commented, your function may truncate last digit(s) from the number.
Below is my attempt:
function Format-TelephoneNumber {
Param(
[Parameter(ValueFromPipeline = $true, Position = 0)]
[Alias('Number')]
[string]$TelephoneNumber,
[Parameter(Position = 1)]
[string]$DefaultCountryCode = '+44'
)
Process {
# replace all hyphens and other possible joining characters with space and trim the result
$number = ($TelephoneNumber -replace '[._~-]', ' ').Trim()
# test if the number starts with a country code
if ($number -match '^(\+\d+)\s') {
$countryCode = $Matches[1]
$number = $number.Substring($countryCode.Length).Trim()
}
else {
$countryCode = $DefaultCountryCode
}
# remove leading zero and any non-digits
$number = $number -replace '^0|\D', ''
if ($number.Length -lt 9) {
Write-Warning "Unable to parse the string '$($TelephoneNumber)' as telephone number!"
}
else {
$parts = #($countryCode)
# split the remaining string in to 3-character parts (+ possible remainder)
$parts += $number -split '(\d{3})' | Where-Object { $_ }
return $parts -join ' '
}
}
}
Why not use the Get-ADUser cmdlet to find the mobile property? Something like:
Import-Module ActiveDirectory
# return the mobile phone number for a user as string or nothing if not found
# $userID is either the users distinguished name, the GUID, the user SID, or the SamAccountName.
$mobile = Get-ADUser -Identity $userID -Properties MobilePhone | Select-Object -ExpandProperty MobilePhone
Note: MobilePhone is the PowerShell or GUI name for the mobile attribute, but you may use either.
Then, if you have this mobile number as string format it using the Format-TelephoneNumber function:
if ($mobile) {
Write-Host "This is raw from AD: $mobile" -ForegroundColor Yellow
$formatted = Format-TelephoneNumber -TelephoneNumber $mobile
Write-Host "This is formatted: $formatted" -ForegroundColor Green
}
Hope that answers your question

Generate random password

I have this script in PowerShell. I want set up random password for each user import from file and I want all comments in popup cmd I want save in other txt file. How can I make this?
#
# Description: Enable accounts, reset passwords and set change password option at first logon.
#
Import-Module ActiveDirectory
# Set default password "XXX"
$password = ConvertTo-SecureString -AsPlainText “XXX” -Force
# get account list from UserList.txt
# 1 user per line
$users = Get-Content -Path 'G:\Shares\XXX\ResetPassword\UserList.txt'
ForEach ($user in $users)
{
# Set default password for account
Get-ADUser $user | Set-ADAccountPassword -NewPassword $password -Reset
# Set option to change password at first logon
Get-ADUser $user | Set-AdUser -ChangePasswordAtLogon $true
# Enable account
Enable-ADAccount -Identity $user
Write-Host “Password change for: $user”
}
Write-Host “New password for all users: XXX”
# ————- End ———–
Read-Host -Prompt "Click enter to quit"
You can use the static GeneratePassword .net method to generate a password:
Add-Type -AssemblyName System.Web
[System.Web.Security.Membership]::GeneratePassword(10, 3)
Edit:
You have to change your script to:
#
# Description: Enable accounts, reset passwords and set change password option at first logon.
#
Import-Module ActiveDirectory
Add-Type -AssemblyName System.Web
$unsecuredPwd = [System.Web.Security.Membership]::GeneratePassword(10, 3)
# Set default password "XXX"
$password = ConvertTo-SecureString -AsPlainText $unsecuredPwd -Force
# get account list from UserList.txt
# 1 user per line
$users = Get-Content -Path 'G:\Shares\XXX\ResetPassword\UserList.txt'
ForEach ($user in $users)
{
# Set default password for account
Get-ADUser $user | Set-ADAccountPassword -NewPassword $password -Reset
# Set option to change password at first logon
Get-ADUser $user | Set-AdUser -ChangePasswordAtLogon $true
# Enable account
Enable-ADAccount -Identity $user
Write-Host "Password change for: $user"
}
Write-Host "New password for all users: $unsecuredPwd"
# ————- End ———–
Read-Host -Prompt "Click enter to quit"
#MartinBrandl's answer works but requires importing System.Web. Below is a bespoke solution that'll generate a random printable ASCII string using the system's cryptographic generator and a little bit of maths; sample usage: [SecureRandom]::GeneratePassword(64). Integrating into a script is left as an exercise for the reader...
class SecureRandom {
hidden static [System.Security.Cryptography.RandomNumberGenerator] $m_randomNumberGenerator = ([System.Security.Cryptography.RNGCryptoServiceProvider]::new());
hidden static [System.UInt32] NextUInt32([System.UInt32]$exclusiveHigh) {
[System.UInt32]$range = ([System.UInt32]::MaxValue - ((([System.UInt32]::MaxValue % $exclusiveHigh) + 1) % $exclusiveHigh));
[System.UInt32]$result = 0;
do { # perform rejection sampling to avoid bias; see https://en.wikipedia.org/wiki/Rejection_sampling, https://en.wikipedia.org/wiki/Rejection_sampling, https://www.pcg-random.org/posts/bounded-rands.html
$result = [SecureRandom]::NextUInt32();
} while ($result -gt $range);
return ($result % $exclusiveHigh);
}
static [System.String] GeneratePassword([System.UInt32]$length) {
$result = [char[]]::new($length);
for ($i = 0; ($i -lt $length); ++$i) { # generate a random ASCII character within the "printable" decimal range of 32-126; this currently "wastes" 3 bytes per iteration and could be made more efficient...
$result[$i] = ([char][SecureRandom]::NextUInt32(32, 126));
}
return [System.String]::new($result);
}
static [byte[]] GetBytes([int]$count) {
[byte[]]$result = [byte[]]::new($count);
[SecureRandom]::m_randomNumberGenerator.GetBytes($result);
return $result;
}
static [System.UInt32] NextUInt32() {
return [System.BitConverter]::ToUInt32([SecureRandom]::GetBytes(4), 0);
}
static [System.UInt32] NextUInt32([System.UInt32]$x, [System.UInt32]$y) {
if ($x -gt $y) {
$z = $x;
$x = $y;
$y = $z;
}
[System.UInt32]$range = ($y - $x);
if ($range -ne [System.UInt32]::MaxValue) {
return ([SecureRandom]::NextUInt32($range + 1) + $x);
}
else {
return [SecureRandom]::NextUInt32();
}
}
}

PowerShell Remove the last error

In my PowerShell script I try to do some error handling. However, I'm depending on an advanced function that uses the Try/Catch clauses. So once in a while the code block in the function fails and goes to the Catch clause after generating an error. At this point the variable $Error is filled with one error.
If I then consult within my script the variable $Error it tells me there's one record, which is correct. But I would like to know if it's possible to only delete the last error within the function in the Catch clause? So I can keep my $Error variable clear for the script errors.
The problem is within Get-ADTSProfileHC. I tried to delete the last error with $Error[0] | Remove-Item but it failed.
The function:
Function Get-ADusersHC {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,Position=0)]
[String[]] $OU
)
Begin {
Function Get-ADOUNameHC {
$CanonicalName = $_.CanonicalName
[System.Collections.ArrayList]$Pieces = $CanonicalName.split(“/”)
$Pieces.Remove($Pieces[-1])
$OU = $Pieces -join '\'
$OU -replace ($Pieces[0],$Pieces[0].ToUpper())
}
Function Get-ADManagerDisplayNameHC {
$m = Get-ADObject -Identity $_.manager -Properties displayName,cn
if($m.ObjectClass -eq "user") { $m.displayName } Else{ $m.cn }
}
Function Get-ADTSProfileHC {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[String] $DistinguishedName,
[parameter(Mandatory=$true,Position=1)]
[ValidateNotNullOrEmpty()]
[ValidateSet('UserProfile','AllowLogon','HomeDirectory','HomeDrive')]
[String]$Property
)
Begin {
$User = [ADSI]"LDAP://$DistinguishedName"
}
Process {
Try {
Switch ($Property) {
'AllowLogon' {if ($($User.psbase.InvokeGet('allowLogon')) -eq '1'){$True}else{$False}}
'HomeDirectory' {$User.psbase.InvokeGet('TerminalServicesHomeDirectory')}
'HomeDrive' {$User.psbase.InvokeGet('TerminalServicesHomeDrive')}
'UserProfile' {$User.psbase.InvokeGet('TerminalServicesProfilePath')}
}
}
Catch {
# When we receive an error, it means the field has never been used before and is blank
# this is due to an error in the AD (same problem with the Quest CmdLet), AllowLogon is
# always 'TRUE' but we don't set it because we can't read it sometimes so we write 'blanks'
Write-Output $null
}
}
}
}
Process {
Foreach ($_ in $OU) {
Write-Verbose "Function Get-HCADusersNoManager > OU: $_"
Write-Verbose "Function Get-HCADusersNoManager > Manager field empty"
Get-ADUser -SearchBase $_ -Filter 'SAMAccountName -eq "shenn"' -Properties * |
#Get-ADUser -SearchBase $_ -Filter * -Properties * |
Foreach {
$Properties = ([Ordered] #{
"Creation date" = $_.whenCreated;
"Display name" = $_.displayName;
"CN name" = $_.name;
"Last name" = $_.sn;
"First name" = $_.givenName;
"Logon name" = $_.sAMAccountName;
"Manager" = if($_.manager){Get-ADManagerDisplayNameHC};
"Employee ID" = $_.EmployeeID;
"HeidelbergcCement Billing ID" = $_.extensionAttribute8
"Type of account" = $_.employeeType;
"OU" = Get-ADOUNameHC;
"Notes" = $_.info -replace "`n"," ";
"E-mail" = $_.EmailAddress;
"Logon script" = $_.scriptPath;
"TS User Profile" = Get-ADTSProfileHC $_.DistinguishedName 'UserProfile';
"TS Home directory" = Get-ADTSProfileHC $_.DistinguishedName 'HomeDirectory';
"TS Home drive" = Get-ADTSProfileHC $_.DistinguishedName 'HomeDrive';
"TS Allow logon" = Get-ADTSProfileHC $_.DistinguishedName 'AllowLogon'
})
$Object = New-Object -TypeName PSObject -Property $Properties
Write-Output $Object
}
}
}
}
Two easy ways to do this:
$error.Remove($error[0])
$Error.RemoveAt(0)
Don't forget to check there is an error first.
$Error.Remove($error[$Error.Count-1])
If errors variable is empty, you will not get any exception
I hope it helps