Automatic password management in Active Directory
Once I got tired of it all...
Probably in most cases it is with this phrase begins the work of system administrators. As a result, we see (though, more correctly, do not even notice) the appearance of numerous small programs that perform accurate and well-defined tasks in the system.
Happened (and happens regularly) with me a similar story. Not to say that I invented something new and extraordinary. On the contrary, he used the writings of colleagues found in the Internet and storehouse of wisdom Habra. But I was able to combine them to solve a very specific and quite interesting tasks. Next, I will describe a specific solution to a specific task for managing user passwords in Active Directory. More precisely, the automation of the verification of the validity of these passwords and generate new passwords. As gratitude to colleagues I decided to publish this solution here in the hope that it will be useful to someone or will be a source of new ideas.
So, there is some organization with a powerful and extensive branch network. A lot of branches all over our vast country and all of them different. Most of them are included into the corporate network domain structure, but many are connected on the principle of home-office. In addition, many employees are constantly on long trips without the ability to connect to a domain network and to the Internet at all.
The result is often a problem of expired passwords. The policy of the company determined the prohibition of the indefinite passwords, and requirements with the rigor of the passwords are quite severe, which causes users difficulty with their inventing and replacement. Accordingly, void sumnyashesya their headache, they happily passed on IT support calling and claiming to change their inoperative password. On a regular basis. Tired.
So, what I want to do? I need a tool that:
• self checking the expiration date of the user password;
• pre-warned him about the date the change password email;
• offered the user the option new password;
• if the user has not yet changed the password, automatically replacing it with a new one;
• notify user of the new password via SMS.
The interest was to solve the task as the means at hand, without involving third-party services. Well, I had no desire to choose rates and packages. But it was a free GSM modem. And the Almighty PowerShell.
The result is a script — or rather two scripts. Why is it so just because it happened historically. The fact that the test produces a password script on a virtual machine located on one branch, and by sending notifications via SMS engaged in another machine located in the opposite part of the country. Because of the conditions of the mobile operator to do otherwise would be unprofitable.
Next, bring both script entirely, which I commented on. They look a little curly. I didn't have special needs to brush, because they work well in this form:
the
This script will add in the Windows task Scheduler by configuring it to run at the right time. For example, at night.
Unfortunately, the script checks for an expired password at the time of its execution. So if the validity of the password expires during the day, then he will not be taken into account. But it's not required, because the employee can change the password yourself.
The result is a list of mobile numbers of users who set a new password. This list we'll send to the server is connected to GSM modem. And there this list will already the following script.
the
The scripts have been tested in combat and showed his best side.
I'm not going to explain why they did so, since the task was specific enough. And the solution is quite specific.
But I will appreciate any tips for improvement or optimization scripts.
UPD:
Got an interesting error.
Import passwords from a text file based on the function Import-Csv, which considers a comma as a field delimiter.
And password generator actively uses the comma as a special character.
In the end the password was set the normal length, as expected, but in the SMS the user has come, "truncated" password.
The solution is simple: since it is impossible to use comma, we can use the asterisk (love her more than punctuation)
Add a line immediately after generation of the password:
the
Annoying little thing, ill-conceived in advance (mea culpa), and user confidence in a crippled good.
Article based on information from habrahabr.ru
Probably in most cases it is with this phrase begins the work of system administrators. As a result, we see (though, more correctly, do not even notice) the appearance of numerous small programs that perform accurate and well-defined tasks in the system.
Happened (and happens regularly) with me a similar story. Not to say that I invented something new and extraordinary. On the contrary, he used the writings of colleagues found in the Internet and storehouse of wisdom Habra. But I was able to combine them to solve a very specific and quite interesting tasks. Next, I will describe a specific solution to a specific task for managing user passwords in Active Directory. More precisely, the automation of the verification of the validity of these passwords and generate new passwords. As gratitude to colleagues I decided to publish this solution here in the hope that it will be useful to someone or will be a source of new ideas.
So, there is some organization with a powerful and extensive branch network. A lot of branches all over our vast country and all of them different. Most of them are included into the corporate network domain structure, but many are connected on the principle of home-office. In addition, many employees are constantly on long trips without the ability to connect to a domain network and to the Internet at all.
The result is often a problem of expired passwords. The policy of the company determined the prohibition of the indefinite passwords, and requirements with the rigor of the passwords are quite severe, which causes users difficulty with their inventing and replacement. Accordingly, void sumnyashesya their headache, they happily passed on IT support calling and claiming to change their inoperative password. On a regular basis. Tired.
So, what I want to do? I need a tool that:
• self checking the expiration date of the user password;
• pre-warned him about the date the change password email;
• offered the user the option new password;
• if the user has not yet changed the password, automatically replacing it with a new one;
• notify user of the new password via SMS.
The interest was to solve the task as the means at hand, without involving third-party services. Well, I had no desire to choose rates and packages. But it was a free GSM modem. And the Almighty PowerShell.
The result is a script — or rather two scripts. Why is it so just because it happened historically. The fact that the test produces a password script on a virtual machine located on one branch, and by sending notifications via SMS engaged in another machine located in the opposite part of the country. Because of the conditions of the mobile operator to do otherwise would be unprofitable.
Next, bring both script entirely, which I commented on. They look a little curly. I didn't have special needs to brush, because they work well in this form:
the
# the Script checks the passwords, which expire tomorrow,
# sends the owner a new password via email
# and automatically replaces, if the password expiration period has expired.
#
# logging function.
$dt=Get-Date -Format "dd-MM-yyyy"
$setupFolder = "c:\Active_Directory\Log"
New-Item -ItemType directory -Path $setupFolder -Force | out-null #Create directory for logs
$global:logfilename="C:\Active_Directory\Log\"+$dt+"_LOG.log"
[int]$global:errorcount=0 #calculate error
[int]$global:warningcount=0 #calculate warnings
function global:Write-log # Function writes messages to a log file and displays.
{param($message,[string]$type="info",[string]$logfile=$global:logfilename,[switch]$silent)
$dt=Get-Date -Format "dd.MM.yyyy HH:mm:ss"
$msg=$dt + "`t" + $type + "`t" + $message #format: 01.01.2001 01:01:01 [tab] error [tab] Message
Out-File -FilePath $logfile -InputObject $msg -Append -encoding unicode
{
switch ( $type.toLower() )
{
"error"
{
$global:errorcount++
write-host $msg -ForegroundColor red
}
"warning"
{
$global:warningcount++
write-host $msg -ForegroundColor yellow
}
"completed"
{
write-host $msg -ForegroundColor green
}
"info"
{
write-host $msg
}
default
{
write-host $msg
}
}
}
}
#Function generator for complex passwords
function global:Get-RandomPassword
{
<# Function password generator PasswordLength - length of password #>
[CmdletBinding()]
param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[ValidateRange(4,15)]
[Int]
$PasswordLength
)
Begin{}
Process{
$numberchars=0..9 | % {$_.ToString()}
$lochars = [char]'a' .. [char]'z' | % {[char]$_}
$hichars = [char]'A' .. [char]'Z' | % {[char]$_}
$punctchars = [char[]](33..47)
$PasswordArray = Get-Random -InputObject @($hichars + $lochars + $numberchars + $punctchars) -Count $PasswordLength
$char1 = Get-Random -InputObject $hichars
$char2 = Get-Random -InputObject $lochars
$char3 = Get-Random -InputObject $numberchars
$char4 = Get-Random -InputObject $punctchars
$RndIndexArray = Get-Random (0..($PasswordLength-1)) -Count 4
$PasswordArray[$RndIndexArray[0]] = $char1
$PasswordArray[$RndIndexArray[1]] = $char2
$PasswordArray[$RndIndexArray[2]] = $char3
$PasswordArray[$RndIndexArray[3]] = $char4
return [system.string]::Join(", $PasswordArray)
}
End{}
}
#SMTP mail server address
$smtpServer = "mail.domain.local"
#create object letter
$msg = new-object Net.Mail.MailMessage
$msgr = new-object Net.Mail.MailMessage
#create the object mail server
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
# Function to tell the user
Function EmailStructure($to,$expiryDate,$upn)
{
$msg.IsBodyHtml = $true
$msg.From = "ITHelpDesk@domain.local"
$msg.To.Clear()
$msg.To.Add($to)
$msg.Subject = "Password expiration notice"
$msg.Body =
"<html><body><font face='Arial'>This is an automatically generated message from the IT Service Company.<br><br>
<b>Please note that the password for your account <i><u>domain\$upn</u></i> will expire on $expiryDate.</b><br><br>
The System automatically generated a new password for you. <br>
You can use password - <b>$generated_password</b><br>
Please change your password immediately or at least before this date as you will be unable to access the service without contacting your administrator.<br>
If you will not change your password, the System set it automatically.<br>
</font></body></html>"}
# Function to report to the administrator
EmailStructureReport Function($to)
{
$msgr.IsBodyHtml = $true
$msgr.From = "PasswordChecker@domain.local"
$msgr.To.Add($to)
$msgr.Subject = "Script running report"
$msgr.Body =
"<html><body><font face='Arial'><b>This is a daily report.<br>
<br>Script for check expiried passwords has successfully completed its work.
<br>$NotificationCounter users have recieved notifications:<br>
<br>$ListOfAccounts<br><br></b></font></body></html>"}
# Plugin to work with Active Directory
Import-Module activedirectory
# get a list of all activated Russian users, who have installed the password expiration
$NotificationCounter = 0
$OU = "OU=Russia,DC=local,DC=domain"
$ADAccounts = Get-ADUser -LDAPFilter "(objectClass=user)" -searchbase $OU -properties PasswordExpired, employeeNumber, PasswordNeverExpires, PasswordLastSet, Mail, mobile, Enabled | Where-object {$_.Enabled-eq $true -and $_.PasswordNeverExpires -eq $false}
# for each user
foreach ($ADAccount in $ADAccounts)
#check policy password complexity
{
$accountFGPP = Get-ADUserResultantPasswordPolicy $ADAccount
if ($accountFGPP -ne $null)
{
$maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge
}
else
{
$maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
}
#Fill variables with user data
$samAccountName = $ADAccount.samAccountName
$userEmailAddress = $ADAccount.mail
$userPrincipalName = $ADAccount.UserPrincipalName
$userStorePassword = $ADAccount.employeeNumber
$usermobile = $ADAccount.mobile
# For each user that failed to change the password
if ($ADAccount.PasswordExpired)
{
# Read password from attribute fields AD
# If not previously saved a password, set the default password - Pa$$w0rd
if ($userStorePassword -eq $NULL -or $useStorePassword -eq " ")
{
$userStorePassword = "Pa$$w0rd" }
# Model password
$newpwd = ConvertTo-SecureString-String $userStorePassword -AsPlainText –Force
Set-ADAccountPassword -Identity $samAccountName -NewPassword $newpwd –Reset
# Saved a new password and mobile phone number in the TXT file
if ($usermobile -ne $NULL)
{
$SMSfile="C:\ActiveDirectory\SMS_notice.txt"
$SMSMessage=$usermobile + "," + $userStorePassword
Out-File -FilePath $SMSfile -InputObject $SMSMessage -Append -encoding unicode
}
# Make a log entry
write-log "for $samAccountName will set a stored password is $userStorePassword. Message send to mobile - $usermobile"
write-log "---------------------------------------------------------------------------------------------------------"
# Clean attribute field AD
Set-ADUser $samAccountName -employeeNumber $null
}
else
# For all those who have the password will expire tomorrow, i.e. $DaysToExpireDD less than 2
{
$ExpiryDate = $ADAccount.PasswordLastSet + $maxPasswordAgeTimeSpan
$TodaysDate = Get-Date
$DaysToExpire = $ExpiryDate - $TodaysDate
#Calculated days until late DaysToExpireDD format days
if (($DaysToExpire.Days-le 2))
{
Write-log "The password for account $samAccountName expires on: $ExpiryDate. Days left: $DaysToExpireDD
# Generated new password in the variable $generated_password
$generated_password = Get-RandomPassword 10
write-log "Generated password: $samAccountName - $generated_password"
write-log "-----------------------------------------------------------------------------------------"
# Write the new password in the attribute field of AD. Will use the attribute employeeNumber
Set-ADUser $samAccountName -employeeNumber $generated_password
# send a warning letter to the user
if ($userEmailAddress) #check for email address from the user.
{
EmailStructure $userEmailAddress $expiryDate $samAccountName
$smtp.Send($msg)
write-log "NOTIFICATION - $samAccountName :: e-mail was sent to $userEmailAddress"
$NotificationCounter = $NotificationCounter + 1
$ListOfAccounts = $ListOfAccounts + $samAccountName + " - $DaysToExpireDD days left. Sent to $userEmailAddress<br>" }
}
}
}
#Send the list of passwords on the server that deals with sending SMS
# If list exists
If (Test-Path $SMSfile)
{
Copy-Item -Path $SMSfile -Destination \\SMS-Send-Server.domain.local\C$\ActiveDirectory\SMS_notice.txt
# Remove file with list of passwords
Remove-Item $SMSfile
}
# send a copy of the report to the administrator
Write-log "SENDING REPORT TO IT DEPARTMENT"
EmailStructureReport("ITHelpdesk@domain.local")
$smtp.Send($msgr)
This script will add in the Windows task Scheduler by configuring it to run at the right time. For example, at night.
Unfortunately, the script checks for an expired password at the time of its execution. So if the validity of the password expires during the day, then he will not be taken into account. But it's not required, because the employee can change the password yourself.
The result is a list of mobile numbers of users who set a new password. This list we'll send to the server is connected to GSM modem. And there this list will already the following script.
the
#
#The script gets the list of mobile numbers and messages from file and send to users
#
# specify where the list file
$sms_text_filename = "SMS_notice.txt"
$PathToSmsPrepareToSend = "C:\ActiveDirectory" + "\" + $sms_text_filename
$dt=Get-Date -Format "dd.MM.yyyy"
# specify where we're going to keep a log of events
$of="C:\ActiveDirectory\Log\"+$dt+"_LOG.log"
# Check existence of message list
If (Test-Path $PathToSmsPrepareToSend)
{
$SMS = Import-Csv $PathToSmsPrepareToSend -Header mobile, newpassword
# for each line from the message list
foreach ($SM in $SMS)
{
# $mobileForSMS = $SM.mobile
# $passwordFroSMS = $SM.newpassword
# echo $mobileForSMS
# Declare an instance of the SerialPort class
$serialPort = new-Object System.IO.Ports.SerialPort
# Set settings variables of the port to which the modem is connected
<#
!!!Important!!! The USB modem uses three COM ports. We need someone that is displayed in the device Manager in the modem settings. If you stick a GSM modem to a different USB port, the COM port number will change.
#>
$serialPort.PortName = "COM3"
$serialPort.BaudRate = 115200
$serialPort.WriteTimeout = 500
$serialPort.ReadTimeout = 3000
$serialPort.DtrEnable = "true"
# Open the port
# $serialPort.Open()
# Save the phone number and message variables
# Delete extra spaces in the phone number
$phoneNumber = [Regex]::replace($SM.mobile,'\s',")
$textMessage = "Your new password is" + $SM.newpassword
try {
$serialPort.Open()
}
catch
{
# Wait 5 seconds and try again
Sleep-Milliseconds 500
$serialPort.Open()
}
If ($serialPort.IsOpen -eq $true)
{
# Specify the modem that will use the AT command mode
$serialPort.Write("AT+CMGF=1'r'n")
Sleep-Milliseconds 500
# Send data to the modem
# First phone number in international format
# and the symbols <CL> at the end
$serialPort.Write("AT+CMGS=`"$phoneNumber`"`R'n")
# Give the modem time to process
Sleep-Milliseconds 500
# Written to the modem our message
$serialPort.Write("$textMessage'R'n")
Sleep-Milliseconds 500
# send the modem a Ctrl+Z at the end of the message.
$serialPort.Write($([char] 26))
# wait until the modem will send the message
Sleep-Milliseconds 500
}
# Close the port
$serialPort.Close()
if ($serialPort.IsOpen -eq $false)
{
# write result to log
$dts=Get-Date-Format "dd.MM.yyyy HH:mm:ss"
Out-File -FilePath $of-InputObject $msg -Append -encoding unicode
}
Sleep-Milliseconds 1000
} #End of loop to process the string from the list
# rename the file list of messages to store in history
$newname =$dt+"_"+$sms_text_filename
rename-item -path $PathToSmsPrepareToSend -newname $newname
} #End check for existence of list
Else
# If the message list does not exist
{
# Make a log entry for that message to send was not
$dts=Get-Date-Format "dd.MM.yyyy HH:mm:ss"
$msg=$dts + ":No data to send SMS"
Out-File -FilePath $of-InputObject $msg -Append -encoding unicode
}
The scripts have been tested in combat and showed his best side.
I'm not going to explain why they did so, since the task was specific enough. And the solution is quite specific.
But I will appreciate any tips for improvement or optimization scripts.
UPD:
Got an interesting error.
Import passwords from a text file based on the function Import-Csv, which considers a comma as a field delimiter.
And password generator actively uses the comma as a special character.
In the end the password was set the normal length, as expected, but in the SMS the user has come, "truncated" password.
The solution is simple: since it is impossible to use comma, we can use the asterisk (love her more than punctuation)
Add a line immediately after generation of the password:
the
$generated_password = $generated_password_comma -replace ",","*"
Annoying little thing, ill-conceived in advance (mea culpa), and user confidence in a crippled good.
Комментарии
Отправить комментарий