Too all Powershell Experts: I need some help to figure out why this is working incorrectly. What I mean incorrectly, the accounts in CSV password get updated with a random password and email is sent to the users but i don't under why they are getting the same password, and everyaccount in the AD is addressed. See email output:
Firstuser email:
Hello Guest DefaultAccount user1, user2, user3, user4 test John Doe,
Your AD Account user1, password has been updated to e,wQlsGfBE;J'4
Second useremail
Hello Guest DefaultAccount user1, user2, user3, user4 test John Doe,
Your AD Account jdoe, password has been updated to e,wQlsGfBE;J'4
what I'm trying to accomplish are as follows:
List item
Import one column cvs with just "samAccountName"
reset password of the accounts listed in the csv with a random generated 20 character long password
Email the AD account associated email address with the new password. (I'm doing this because their email address if different from their AD Account), so they are able to receive their email.
Import-Module ActiveDirectory
# Import AD user objects and store in users variables
$users = Get-ADUser -Filter * -Properties Name, EmailAddress,SamAccountName | Select SamAccountName,Name,EmailAddress
$Name = $users.Name
$SamAccountName = $users.SamAccountName
$EmailAddress = $users.EmailAddress
#Date
$date = (Get-Date -Format F)
#Generate a random password and store in newpwd
$newpwd = -join (33..126|%{[char]$_}|Get-Random -Count 20)
$newPassword = ConvertTo-SecureString -AsPlainText "$newpwd" -Force
# Import users from CSV
Import-Csv "C:\Userlist.csv" | ForEach-Object {
$samAccountName= $_."samAccountName"
#Proceed with password reset
Set-ADAccountPassword -Identity $samAccountName -NewPassword $newPassword -Reset
Set-AdUser -Identity $samAccountName -ChangePasswordAtLogon $true
#Server configuration
$smtpServer =""
$SmtpPort = ""
# sending option
$from = "it-operation#example.com"
$to = $EmailAddress
$subject ="AD Password updated - " + $date
$priority = "Normal"
$from = ""
$body = #"
Hello $name, <br>
Your AD Account $samAccountName, password has been updated to $newpwd
"#
try {
# Send the report email
Send-MailMessage -To $to -Subject $subject -BodyAsHtml -body $body -SmtpServer $smtpServer -Port $SmtpPort -From $From -Priority $priority
}
catch{
write-warning "error in sending. $_"
} # End User Processing
}
The Account password is getting updated, but just can't send the emai
When you do $Name = $users.Name, the $Name variable gets an array containing ALL usernames in the CSV.
Remove that line and instead set the variable inside your ForEach-Object loop:
# Import users from CSV
$UserList = Import-Csv "C:\Userlist.csv" | ForEach-Object {
$samAccountName= $_.samAccountName
$name = $_.Name
# then the rest of your code
}
Note:
SmtpPort takes an integer, not a string
From cannot be an empty string
P.S. Try to do some proper code indentation, so it is much easier to see where a code block (like ForEach-Object) starts and ends)
Your code revised:
Import-Module ActiveDirectory
# Import AD user objects and store in users variables
$users = Get-ADUser -Filter * -Properties Name, EmailAddress,SamAccountName | Select SamAccountName,Name,EmailAddress
#Date
$date = (Get-Date -Format F)
# Import users from CSV
Import-Csv "C:\Userlist.csv" | ForEach-Object {
# read this from the CSV
$samAccountName= $_.samAccountName
# get the user object from the $users array
$user = $users | Where-Object { $_.SamAccountName -eq $samAccountName }
$name = $user.Name
$emailAddress = $user.EmailAddress
#Generate a random password and store in newpwd
$newpwd = -join (33..126|%{[char]$_}|Get-Random -Count 14)
$newPassword = ConvertTo-SecureString -AsPlainText "$newpwd" -Force
$body = #"
Hello $name, <br>
Your AD Account $samAccountName, password has been updated to $newpwd
"#
#Proceed with password reset
Set-ADAccountPassword -Identity $samAccountName -NewPassword $newPassword -Reset
Set-AdUser -Identity $samAccountName -ChangePasswordAtLogon $true
#Server configuration
$mailParams = #{
SmtpServer = "YourSMTPServer"
Port = 25 # <-- an integer value
# sending option
From = "it-operation#example.com"
To = $user.EmailAddress
Subject ="AD Password updated - " + $date
Priority = "Normal"
Body = $body
BodyAsHtml = $true
}
try {
# Send the report email
Send-MailMessage #mailParams -ErrorAction Stop
}
catch{
write-warning "error in sending. $_"
} # End User Processing
}
Don't accept my answer. I'm just building off what #Theo did. (I'd love an up-vote though)
Import-Module ActiveDirectory
# Import AD user objects and store in users variables
$Users = #{}
Get-ADUser -Filter * -Properties Name,EmailAddress,SamAccountName | Select-Object SamAccountName,Name,EmailAddress |
ForEach-Object{ $Users.Add( $_.samAccountName, $_ ) }
#Date
$Date = (Get-Date -Format F)
# Generate a random password and store in newPassword
$newPassword = -join ( 33..126 | ForEach-Object{ [char]$_ }| Get-Random -Count 20 )
$newPassword = ConvertTo-SecureString -AsPlainText "$newPassword" -Force
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Move to the loop if you want each user's pwd to be different!!!
# Put this here so you can indent the loop easier...
$BaseBody =
#"
Hello %NAME%, <br>
Your AD Account %SAMACCOUNTNAME%, password has been updated to %NEWPASSWORD%
"#
# Server configuration & recipien configurations...:
$MailParams = #{
$smtpServer = "YorServer.example.com" #MAKE SURE YOU CHANGE THIS
$SmtpPort = 25
$from = "it-operation#example.com"
$To = ""
$subject ="AD Password updated - " + $date
$Body = ""
}
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# Removed -Priority if it's alwasy going to be normal you don't need it.
# Import users from CSV
Import-Csv "C:\Userlist.csv" |
ForEach-Object {
$samAccountName = $_."samAccountName"
# Proceed with password reset
Set-ADAccountPassword -Identity $samAccountName -NewPassword $newPassword -Reset
Set-ADUser -Identity $samAccountName -ChangePasswordAtLogon $true
# Get the Name & Email address from the $Users Hash...:
$EmailAddress = $Users[$samAccountName].EmailAddress
$Name = $Users[$samAccountName].Name
# Reset the To & Body values
$MailParams['To'] = $EmailAddress
$MailParams['Body'] = $BaseBody.Replace('%NAME%', $Name).Replace('%SAMACCOUNTNAME%', $samAccountName).Replace('%NEWPASSWORD%', $newPassword)
try { # Send the report email
Send-MailMessage #MailParams -ErrorAction Stop
}
catch{
write-warning "Error in sending : $_"
}
} # End User Processing
Used a hash table to store the AD user objects so you don't have to re-execute a Where{} on every loop iteration. Depending on the number of users this could be quite a bit faster. When relating data between 2 sources I gravitate toward a hash tables.
Moved the $Body var out of the loop. Here strings don't like to be indented, by moving it out we don't have to interfere with the loop indentation; improving readability. Then inside the loop we can get the actual body by doing some string replacement.
I also placed the $MailParams parameter hash outside the loop. Then inside the loop I reset the 2 keys that vary. Nitpicking, but I'd rather not declare the hash on every loop iteration either.
$Reused the $newPassword variable rather than have the extra $newpdw variable just laying around.
Expanded the aliases, just because PSScriptAnalyzer complains about it...
Obviously this is untested! But, I hope it's helpful...
Related
I have a csv file, i want to send email separately to each user, currently from my script i am getting emails to all users addresses those are in my csv file "user" column. Here is my CSV file Data.
#TYPE System.Management.Automation.PSCustomObject
"time","remip","user","msg"
"11:12:15","124.29.228.164","abc#xyz.com.pk","SSL tunnel shutdown"
"11:12:43","124.29.228.164","efg#gmail.com","SSL tunnel established"
"11:13:25","124.29.228.164","abc#xyz.com.pk","SSL tunnel established"
"11:14:05","202.63.194.8","efg#gmail.com","SSL tunnel established"
This is my powershell code
$Subject = " Alerts $(get-date -Format "dddd MM-dd-yyyy HH:mm")"
$Server = "qs42.xyz.com"
$From = "logalerts#abc.com"
$To = $ID = Import-Csv myfile.txt | Select-Object -ExpandProperty user -Unique
$PWD = ConvertTo-SecureString "test123" -AsPlainText -force
$Cred = New-Object System.Management.Automation.PSCredential("logalerts#abc.com" , $pwd)
$path = "myfile.txt"
$file = (Import-Csv $path) | Select-Object -Unique -Property Time , remip , user , Msg
Send-MailMessage -From $from -to $To -Port 587 -SmtpServer $server -Subject $Subject -Credential $cred -UseSsl -Body ($file | Out-String)
Iam able to get emails of this data on both address abc#xyz.com, efg#gmail.com which i dont want, i know emails are receiving because of my variable setting but my requirement is to get emails of each user data on the same user email address.
>>time remip user msg
---- ----- ---- ---
11:12:15 124.29.228.164 abc#xyz.com SSL tunnel shutdown
11:12:59 124.29.228.164 efg#gmail.com SSL tunnel shutdown
11:13:25 124.29.228.164 abc#xyz.com SSL tunnel established
11:14:05 202.63.194.8 efg#gmail.com SSL tunnel established
I don't know how to do this any help please.
There were a couple of things wrong with your code:
$PWD is an automatic variable and means the current working directory. You must not use that name as self-defined variable
You use Import-Csv multiple times where once is enough
The way you try to get a value in the $To variable is wrong
you need Group-Object because some users may have more than one message
I'd recommend using Splatting the parameters to the Send-MailMessage cmdlet
Try:
$password = ConvertTo-SecureString "test123" -AsPlainText -force
$cred = New-Object System.Management.Automation.PSCredential("logalerts#abc.com" , $password)
# import the data from the csv, group on field 'user' (which is the emailaddress to send to)
Import-Csv -Path 'D:\Test\myfile.txt' | Group-Object user | ForEach-Object {
# build a Hashtable to neatly send the parameters to Send-MailMessage cmdlet
# this is called Splatting.
$mailParams = #{
From = 'logalerts#abc.com'
To = $_.Name # the Name of the group is from the user field in the csv
Port = 587
SmtpServer = 'qs42.xyz.com'
Subject = 'Alerts {0:dddd MM-dd-yyyy HH:mm}' -f (Get-Date)
Credential = $cred
UseSsl = $true
Body = $_.Group | Format-Table -AutoSize | Out-String
}
Send-MailMessage #mailParams
}
This is very similar to the answer given by #theo, but it doesn't use the Group-Object cmdlet. I've had problems with memory consumption when the data set is very large because it stores structures (PSObject or PSCustomObjects) within objects and the objects tend to be pretty large.
$PassWord = ConvertTo-SecureString "test123" -AsPlainText -force
$Cred = New-Object System.Management.Automation.PSCredential("logalerts#abc.com"
, $PassWord)
$path = "myfile.txt"
# Get all alerts
$Alerts = Import-Csv $path | Select-Object -Unique -Property Time , remip , user
, Msg
# get individual recipient addresses from the log file
$UniqueUsers = $Alerts |
Select-Object -ExpandProperty user -Unique
ForEach ($User in $UniqueUsers){
$UniqueAlerts = $Alerts |
Where-Object {$_.user -eq $User}
$params = #{
From = 'logalerts#abc.com'
To = $User
Subject = 'Alerts {0:dddd MM-dd-yyyy HH:mm}' -f (Get-Date)
Body = $UniqueAlerts | Out-String # This probably won't align
the
columns properly
# if a mono-spaced font isn't
used by the e-mail client.
# you might consider sending the
message in HTMP format
SmtpServer = 'qs42.xyz.com'
Port = 587
UseSsl = $true
Credential = $cred
}
Send-MailMessage #params
}
As noted in comments, the body of the message will probably be hard to read if it's in plain-text. A HTML message with the data within a table will produces a much easier to read message.
I'm trying to create a script that will read a CSV full of users to create. It will then create the users and generate them random passwords, it then needs to send me an email with all of the created users and all of their corresponding passwords along with the original CSV. My current script doesn't work correctly and the problems are described below.
The script currently doesn't work very well at all. There are five test users in the CSV and all it manages to do is create the first user and doesn't do that correctly. There is no first name or display name. Neither is it created in the set group, and it's disabled. It then sends the email with the CSV but doesn't give me the one user is has created, it just gives me all of the errors. I have a feeling that if this does work that it will create using the same password for each user, it needs to be unique.
$filePath = “D:\myfilepath\ScriptTestUsers.csv”
# Import the CSV file containing the user information
$users = Import-Csv $filePath
# Create an empty array to store the created users with their passwords
$createdUsers = #()
# Create an empty array to store the users that couldn't be created and the errors
$errorUsers = #()
# Loop through each user in the CSV file
foreach ($user in $users) {
$firstname = $user.'FirstName' # Get the firstname from the CSV file
$lastname = $user.'LastName' # Get the lastname from the CSV file
$username = $user.'UserName' # Get the username from the CSV file
# Check if the 'UserName' field is empty, if so, add the user to the error array and continue to the next iteration
if (!$username) {
$error = "Missing 'UserName' field for a user in the CSV file, skipping."
$errorUsers += "$username $error"
continue
}
# Check if the user already exists in AD, if so, add the user to the error array and continue to the next iteration
if (Get-ADUser -Identity $username -ErrorAction SilentlyContinue) {
$error = "User $username already exists in AD, skipping."
$errorUsers += "$username $error"
continue
}
$ou = $user.'OU' # Get the OU from the CSV file
# Check if the specified OU exists, if not, add the user to the error array and continue to the next iteration
if (!(Get-ADOrganizationalUnit -Identity $ou -ErrorAction SilentlyContinue)) {
$error = "OU $ou does not exist, skipping."
$errorUsers += "$username $error"
continue
}
$groups = $user.'Groups' # Get the groups from the CSV file
# Check if the specified group(s) exists, if not, add the user to the error array and continue to the next iteration
if ($groups) {
$missingGroups = $groups | Where-Object { !(Get-ADGroup -Identity $_ -ErrorAction SilentlyContinue) }
if ($missingGroups) {
$error = "Group(s) $missingGroups do not exist, skipping."
$errorUsers += "$username $error"
continue
}
}
$expiry = $user.'ExpiryDate' # Get the account expiry date from the CSV file
$description = $user.'Description' # Get the description from the CSV file
# Generate a random password
$password = [System.Web.Security.Membership]::GeneratePassword(16, 2)
#$email = "<myemail#email.com>"
# Create the AD user and add them to the specified groups
try {
New-ADUser -Name $firstname -Surname $lastname -SamAccountName $username -UserPrincipalName $username -Path $ou -Description $description -AccountExpirationDate $expiry -PassThru | Set-ADAccountPassword -Reset -NewPassword (ConvertTo-SecureString $password -AsPlainText -Force)
if ($groups) {
Add-ADGroupMember -Identity $groups -Members $username -ErrorAction Stop
}
# Add the user and their password to the array of created users
$createdUsers += "$username $password"
} catch {
$error = $_.Exception.Message
$errorUsers += "$username $error"
}
}
# Create the HTML body of the email with CSS styling
$body = "<html><head><style>body { background-color: lightblue; font-family: calibri;}</style></head><body>Successfully Created Users:<br>" + ($createdUsers -join "<br>") + "<br><br>Users that couldn't be created and the errors:<br>" + ($errorUsers -join "<br>") + "</body></html>"
# Send the email with the list of created users and their passwords and attach the original CSV file
$DateSh = (Get-Date).ToString("yyyyMMdd")
#Email options
$options = #{
'SmtpServer' = "relay"
'To' = "<myemail#email.com>"
'From' = "SERVER1 <server1#domain>"
'Subject' = "$DateSh-NewUsersCreated"
'BodyAsHtml' = $true
'Attachments' = $filePath
}
#Send email
Send-MailMessage #options -Body $Body
The Errors I receive after it has run are:
Get-ADUser : Cannot find an object with identity: 'ScriptTestUser001' under: 'DC=mydc'
Get-ADGroup : Cannot find an object with identity: 'Group1, Group2' under: 'DC=mydc'
Cannot overwrite variable Error because it is read-only or constant.
Please help.
To check if the user exists you can use:
$userFound = $true
try {
Get-ADUser -Identity $username -ErrorAction Stop
}
catch {
$userFound = $false
}
if($userFound) {
$errorText = "User $username already exists in AD, skipping."
$errorUsers += "$username $errorText"
continue
}
For the groups, you cannot just pass a string with all the group separated with commas and spaces, you need to split it into an array first:
$groups = $user.'Groups' -split ', '
$missingGroups = #()
foreach($group in $groups)
{
try {
Get-ADGroup -Identity $group -ErrorAction Stop
}
catch {
$missingGroups += $group
}
}
if($missingGroups.Length -ne 0)
{
$errorText = "Group(s) $($missingGroups -join ',') do not exist, skipping."
$errorUsers += "$username $errorText"
continue
}
Regarding the account being disabled after creation that's normal you did not provide the password when creating the user (this is documented here)
try {
New-ADUser -Name $firstname -Surname $lastname -SamAccountName $username -UserPrincipalName $username -Path $ou -Description $description -AccountExpirationDate $expiry -AccountPassword (ConvertTo-SecureString $password -AsPlainText -Force)
foreach($group in $groups) {
Add-ADGroupMember -Identity $group -Members $username -ErrorAction Stop
}
...
And yes as mentioned by #vonPryz the $Error variable is reserved (this is documented here).
For a school project, i need to make a Powershell script, but to create a username, with only the first letter of the person name, and the full second name, could anyone help me with this? This is what i currently have:
Import-Module ActiveDirectory
# password for accounts
$securePassword = ConvertTo-SecureString "Welkom#1" -AsPlainText -Force
# Import the file into a variable
$users = Import-Csv -Path .\New-GaastraUserBulk.csv
# Loop trough each row, and gather Information
ForEach ($user in $users) {
# Gather the user Information
$fname = $user.FirstName
$lname = $user.LastName
$jtitle = $user.JobTitle
$OUpath = $user.OU
Write-Host $fname
Write-Host $lname
Write-Host $jtitle
Write-Host $OUpath
#Gebruiker aanmaken in AD
New-ADUser -Name "$fname $lname" -GivenName $fname -SamAccountName $lname -Surname $lname -UserPrincipalName "$lname" -Path $OUpath -AccountPassword $securePassword -PasswordNeverExpires $true -Enabled $true
}
As per the comments from others. Add this line after $lname = ...
$sam = "{0}$lname" -f $fname.Substring(0,1)
Then edit your New-ADUser line use $sam
New-ADUser .... -SamAccountName $sam ...
Turning my comment into an answer.
You can create the user's SamAccountName quite easily, combining the first character of the users GivenName with the full LastName. However, you need to check that this SamAccountName is not already in use.
Another thing is that the UserPrincipalName should be in the form of <user>#<DNS-domain-name>.
To improve your code also using Splatting:
Import-Module ActiveDirectory
# password for accounts
$securePassword = ConvertTo-SecureString "Welkom#1" -AsPlainText -Force
# Import the file into a variable
$users = Import-Csv -Path .\New-GaastraUserBulk.csv
# Loop trough each row, and gather Information
foreach ($user in $users) {
# first create the desired SamAccountName for the new user
$accountName = "{0}{1}" -f $user.FirstName.Substring(0,1),$user.LastName
# test if a user with that SamAccountName already exists
$checkUser = Get-ADUser -Filter "SamAccountName -eq '$accountName'" -ErrorAction SilentlyContinue
if ($checkUser) {
Write-Warning "SamAccountName $accountName already used for user $($checkUser.Name)"
}
else {
# create a hashtable with all parameters for the New-ADUser cmdlet
$userParams = #{
Name = "$fname $lname"
GivenName = $user.FirstName
Surname = $user.LastName
Title = $user.JobTitle
SamAccountName = $accountName
Path = $user.OU
AccountPassword = $securePassword
PasswordNeverExpires = $true
Enabled = $true
UserPrincipalName = "$accountName#yourdomain.com" # <-- put YOUR domain here after the '#'
# other parameters go here if needed
}
New-ADUser #userParams
}
}
Also, keep in mind that you cannot use just any character for a SamAccountName.
Characters " [ ] : ; | = + * ? < > / \ , # are illegal, aswell as non-printable characters and the dot . can not be the last character of the name.
AND, the system limits sAMAccountName to 20 characters for user objects.
To make sure, use something like:
$accountName = ($accountName -replace '["\[\]:; |=+\*\?<>/\\,#]').TrimEnd(".") -replace '^(.{1,20}).*', '$1'
I want to be able to send an email of who was logged off of each computer. How can I accomplish this?
$username = Read-Host -Prompt 'Input Username'
Create a fresh variable to collect the results. You can use this to output as desired
$SessionList = "ACTIVE SERVER SESSIONS REPORT - " + $today + "nn"
Query Active Directory for computers running a Server operating system
Get-Content -Path '\server\share$\1 - Admin Tools\Power Shell Scripts\Log Users Off Servers\Serverlist.csv' | select -Skip 1 | Set-Content "\data01\it$\1 - Admin Tools\Power Shell Scripts\Log Users Off Servers\servers.csv"
$Servers = Get-Content -Path '\server\share$\1 - Admin Tools\Power Shell Scripts\Log Users Off Servers\Servers.csv'
Loop through the list to query each server for login sessions
ForEach ($Server in $Servers) {
$list = $Server -replace '"', ""
# When running interactively, uncomment the Write-Host line below to show which server is being queried
$message = "Querying" +" "+ $list
$message
# Run the qwinsta.exe and parse the output
$queryResults = (qwinsta $username /server:$Server | foreach { (($_.trim() -replace "\s+",","))} | ConvertFrom-Csv)
# Pull the session information from each instance
ForEach ($queryResult in $queryResults){
$RDPUser = $queryResult.USERNAME
$sessionType = $queryResult.ID
if ($sessionType -eq "Disc"){$sessionType = $RDPUser} else { $sessionType = $queryResult.ID}
Write-Host "Logging $username off of $list with Session# $sessionType"
logoff $sessionType /server:$Server
}
}
I see you already have the username. So if you have the AD module installed you can use the
$Mail = (Get-ADUser $username -properties Mail).mail
to get the email id of the user. Then you can use the
Send-MailMessage -to $mail -From <random mail id for eg noreplyAD#mail.com> -Body "Your $username has been logged off of $server" -Subject "Logoff notification" -SmtpServer <yourcompany's smtpserver>
to send a notification to the user
I want to determine all users from several Domains where the difference between Password expiration date and today date is less than 14 days. Later I want to write a notification mail to all users with powershell. How do I write the Content from Surname to a variable?
Here is my given Code:
$datacoll =#()
$domains = "domain1","domain2","domain3"
$expindays = 14
$today = Get-Date
foreach($domain in $domains){
$datacoll += Get-ADUser -Server $domain -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} `
–Properties “SamAccountName”,”mail”,"GivenName","Surname",”pwdLastSet”,”msDS-UserPasswordExpiryTimeComputed” | where{$_.mail -ne $null} |
Select-Object -Property “SamAccountName”,”mail”,"GivenName","Surname",#{Name=”Password Last Set”;`
Expression={[datetime]::FromFileTime($_.”pwdLastSet”)}}, #{Name=”Password Expiry Date”;`
Expression={[datetime]::FromFileTime($_.”msDS-UserPasswordExpiryTimeComputed”)}}
}
#iterate surname?
# foreach($user in $Surname){
# }
#datacoll output
$datacoll | Export-Csv "C:\pw.csv" -NoTypeInformation -Encoding utf8
What you can do is use DirectoryServices and DirectoryServices.AccountManagement which are part of the .NET framework
You can load .Net assemblies like this using system.reflection.assembly
[system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices") | out-null
You then will create a domain context (Connection to AD) and a searcher object that will go through the domain looking for user objects.
I wrote a function to return users that match a range (How close to expiration date) and a Domain. It will then return a array of directory entries so you can get the information you prefer.
function Get-ADPasswordExpirationDates(){
param(
[Parameter(Mandatory=$True)]
[string]$Domain,
[int]$Range = 14
)
#Call .Net Active Directory Assembies
[system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices") | out-null
[system.reflection.assembly]::LoadWithPartialName("System.DirectoryServices.AccountManagement") | out-null
#Get Domain Context
$Context = new-object System.DirectoryServices.AccountManagement.PrincipalContext(`
[System.DirectoryServices.AccountManagement.ContextType]::Domain,`
$domain
)
#Create Searcher Object looking for User Objects
$Search = new-object System.DirectoryServices.AccountManagement.PrincipalSearcher(`
new-object System.DirectoryServices.AccountManagement.UserPrincipal(`
$Context
)
)
#Arry Varable for holding return reply
$Reply = #()
#For Each User in search
foreach($Result in $Search.FindAll()){
#Turn Result into a Directory Entry
[System.DirectoryServices.DirectoryEntry] $Entry = $Result.GetUnderlyingObject()
#Get Expiration Date
$Expiration = $Entry.InvokeGet("PasswordExpirationDate")
#Create a Timespan from Todays date to Expiration date
$Timespan = $(New-TimeSpan -Start $(get-date) -End $Expiration).Days
#If days are less then $RANGE add Directory Entry to array
if($Timespan -lt $Range -and $Timespan -gt -1 ){
$Reply += $Entry
}
}
return $Reply
}
Here is a quick example of how to use it to get information form the directory object.
$AlmostExpiredUsers = Get-ADPasswordExpirationDates -domain MyAwesomeDomainName -Range 14
foreach($User in $AlmostExpiredUsers){
Write-Output "Username : $($user.SamAccountName)`r`nFull Name : $($user.Properties.name)`r`nEmail : $($user.Properties.mail)`r`nExpires : $($user.InvokeGet("PasswordExpirationDate"))`r`n`r`n"
}
I use this script to send email to user about there password expiration
$MaxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
Get-ADUser -Filter * -SearchBase "OU=Admin user,DC=test,DC=test" -properties PasswordLastSet,
PasswordExpired,
PasswordNeverExpires,
EmailAddress,
DisplayName,
GivenName,
SN |
foreach {
$UserName = $_.DisplayName
$SN = $_.SN
$Email = $_.EmailAddress
$today = Get-Date
$enc = New-Object System.Text.utf8encoding
$ExpiryDate = $_.PasswordLastSet + $maxPasswordAgeTimeSpan
$DaysLeft = ($ExpiryDate-$today).days
If ($DaysLeft -lt 7 -and $DaysLeft -gt 0)
{
Send-mailmessage -to $Email -from noreply#noreply.com -Subject "enter subject" -body "enter body here" -smtpserver x.x.x.x -BodyAsHtml -Encoding $enc
}
Here is a minimal solution. The 14 days timespan, you achieve with:
foreach($user in $datacoll){
$StartDate=(Get-Date).Date
$EndDate=([datetime] $user."Password Expiry Date").Date
$diff = (New-TimeSpan -Start $StartDate -End $EndDate).Days
$firstname = $user.GivenName
$surname = $user.Surname
#body content in variable for parameter in Send-MailMessage
$body = #"
Dear Sir or Madam,
Your Password will expire in $diff days,
please change your password.
Sincerely
IT
"#
}
Than you can check the $diff variable with if, else Statement and send mail to all users with:
if($diff -le 14){
Send-MailMessage -from "Helpdesk <helpdesk#noreply.com>" -to "$firstname $surname<$user.mail>" -subject "Password Expiration in $diff days" -smtp "smtpserver" -body $body
}
To check multiple Domain Controllers you can define multiple Domain Controller with:
$dcs = "domaincontroller1","domaincontroller2","domaincontroller3"
And inlcude a foreach Loop.
With the Option -Server you can Transfer the Parameter $i to achieve all users from all Domain Controllers.
Hint: You can only perform this script from your root Domain Controller.
foreach($i in $dcs){
$datacoll += Get-ADUser -Server $i -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False -and mail -like '*'} `
–Properties “SamAccountName”,"GivenName","Surname",”mail”,”pwdLastSet”,”msDS-UserPasswordExpiryTimeComputed” |
Select-Object -Property “SamAccountName”,"GivenName","Surname",”mail”,#{Name=”Password Last Set”;`
Expression={[datetime]::FromFileTime($_.”pwdLastSet”)}}, #{Name=”Password Expiry Date”;`
Expression={[datetime]::FromFileTime($_.”msDS-UserPasswordExpiryTimeComputed”)}}
}
}