How can I send an email of who is logged off? - powershell

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

Related

Invoke-VMScript: Do not confirm/re-enter guest credentials

I'm performing an audit on VMs in our environment, and using Invoke-VMScript to get disk info:
$vmErrors = New-Object System.Collections.Generic.List[System.Object]
$report = foreach ($VM in $VMs){
$name = $vm.Name
Write-Host "Retrieving data for $name... " -NoNewline
try{
$output = Invoke-VMScript -ScriptText #'
Get-Disk | Foreach-Object{
[PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
Number = $_.number
Size = [int]($_.size/1GB)
PartitionStyle = $_.PartitionStyle
}
} | ConvertTo-Csv -NoTypeInformation
'# -VM $name -GuestUser $Username -GuestPassword $Password -WarningAction SilentlyContinue -ErrorAction Stop
if($output.ScriptOutput -match 'PartitionStyle')
{
$output.ScriptOutput -replace '(?s)^.+(?="Com)' | ConvertFrom-Csv
}
Write-Host "Done" -ForegroundColor Green
}catch{
$vmErrors.Add($name)
Write-Host "Error!" -ForegroundColor Red
}
}
When running the code in Powershell ISE, I do not need to confirm the credentials for each VM and the script loops perfectly fine, however if I run the script in a standard Powershell window, for each VM I have to enter the username and password for each VM (the credentials will always be the same as what I use to authenticate with the vCenter server.
Example:
Retrieving data for VM NAME...
Specify Credential
Please specify guest credential
User:
How can I run this in a normal window and avoid having to re-enter my credentials each time?
Edit
Example of setting username & password:
$Username = ""
$Password = ""
Function GetUser{
do{
$global:Username = Read-Host "What is your username? (DOMAIN\Username)"
}while($global:Username -eq "")
}
Function GetPassword{
do{
$global:Password = Read-Host "What is your password?" -AsSecureString
$global:Password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($global:Password))
}while($global:Password -eq "")
}
Solved by changing the credential references in Invoke-VMScript to use $global:Username and $global:Password

AD password update via Powershell

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...

Send email to AD users when their password is about to expire

I've been working on a PowerShell script that sends an automated email out to AD users when their password is about to expire. My script has a ForEach statement in it but nothing within this actually runs. I've set it up so that it logs all activities in a .txt file so I can see when each step is being worked on. It's running on a Windows 2016 Essentials.
Script Below:
# VAR
$SMTPHost = "smtp.office365.com"
$FromEmail = "***"
$expireindays = 3
$Date = Get-Date
# Set DIR
$DirPath = "C:\TEMP"
# Check is DIR is present
$DirPathCheck = Test-Path -Path $DirPath
if (!($DirPathCheck)) {
try {
#Create DIR if not present
New-Item -ItemType Directory $DirPath -Force
}
catch {
$_ | Out-File ($DirPath + "\" + "Log.txt") -Append
}
}
# CredObj
$CredObj = ($DirPath + "\" + "EmailExpiry.cred")
# Check if CredObj is Present
$CredObjCheck = Test-Path -Path $CredObj
If (!($CredObjCheck))
{
"$Date - INFO: creating cred object" | Out-File ($DirPath + "\" + "Log.txt") -Append
#If not present get O365 cred and store
$Credential = Get-Credential -Message "Please enter your Office 365 credentials."
#Export CredObj
$Credential | Export-Clixml -Path $CredObj
}
Write-Host "INFO | Importing Cred Object" -ForegroundColor Yellow
$Cred = (Import-Clixml -Path $CredObj)
"$Date - INFO: Importing AD Module" | Out-File ($DirPath + "\" + "Log.txt") -Append
Import-Module ActiveDirectory
"$Date - INFO: Getting Users" | Out-File ($DirPath + "\" + "Log.txt") -Append
Write-Host "INFO | Getting Users" -ForegroundColor Yellow
$users = Get-ADUser -properties Name, PasswordExpired, PasswordLastSet, EmailAddress -filter { (enabled -eq 'True') } | Where-Object { $_.PasswordExpired -eq 'False'}
# Process Each User for Password Expiry
ForEach ($User in $Users) {
$Name = (Get-ADUser $user | Get-ADUser -Property Name)
Write-Host "Working on $Name..." -ForegroundColor White
Write-Host "Getting email address for $Name..." -ForegroundColor Yellow
$emailaddress = $user.EmailAddress
if (!($emailaddress)) {
Write-Host "$Name has no E-Mail address listed, looking at their proxy address attributes..."
if (!($emailaddress)) {
Write-Host "$Name has no email address to send an e-mail to!" -ForegroundColor Red
"$Date - WARNING: No email found for $Name" | Out-File ($DirPath + "\" + "Log.txt") -Append
}
}
#Get password last set
$passwordSetDate = (Get-AAUser $user -properties * | ForEach-Object { $_.PasswordLastSet})
#Get the count on how many days until the password expires and stores it in the $daystoexpire VAR
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
if (($daystoexpire -ge "0") -and ($daystoexpire -lt $expireindays)) {
"$Date - INFO: Sending expiry notice email to $Name" | Out-File ($DirPath + "\" + "Log.txt")
Write-Host "Sending Password expiry email to $Name" -ForegroundColor Yellow
$SmtpClient = New-Object system.net.mail.smtpclient
$MailMessage = New-Object system.net.mail.mailmessage
#Email Sender
$MailMessage.From = $FromEmail
#SMTP Server
$SmtpClient.Host = $SMTPHost
#SMTP SSL
$SmtpClient.EnableSsl = $true
#SMTP Credentials
$SmtpClient.Credentials = $Cred
#Email Recipients
$MailMessage.To.add($emailaddress)
#Subject
$MailMessage.Subject = "Your password will expire $daystoexpire days"
#Delivery Success
$MailMessage.DeliveryNotificationOptions = ("onSuccess", "onFailure")
#Set Priority
$MailMessage.Priority = "High"
#Body
$MailMessage.Body = "Password will expire, better change it!"
Write-Host "Sending email to $emailaddress..." -ForegroundColor Green
try {
$SmtpClient.Send($MailMessage)
}
catch {
$_ | Out-File ($DirPath + "\" + "Log.txt") -Append
}
else {
"$Date- INFO: Password for $Name not expiring for another $daystoexpire days" | Out-File ($DirPath + "\" + "Log.txt") -Append
Write-Host "Password for $Name does not expire for $daystoexpire days" -ForegroundColor White
}
}
}
Output:
I noticed you did not set the $Expireson variable anywhere, so you would not get a correct value for $daystoexpire aswell.
As a matter of fact, I made a script to do the same some time ago. I have edited it a bit for you to try out.
This uses the Send-Mailmessage instead of the System.Net.Mail.SmtpClient to make things easier. This way, we can also use Splatting to make the code more readable.
Import-Module ActiveDirectory
$smtpServer = "smtp.office365.com"
$expireInDays = 3 # 3 is a bit close... better do something like 7
$from = "YOUR EMAILADDRESS"
$logPath = "C:\TEMP"
$logFile = Join-Path -Path $logPath -ChildPath 'PasswordExpiryLog.txt'
$logDate = '{0:dd-MM-yyyy}' -f (Get-Date)
$credPath = Join-Path -Path $logPath -ChildPath 'EmailExpiry.cred'
# create the output path if it does not exist
if (!(Test-Path -Path $logPath -PathType Container)) {
New-Item -Path $logPath -ItemType Directory | Out-Null
}
# Credentials
If (!(Test-Path -Path $credPath -PathType Leaf)) {
# write to the log and screen
$msg = "Creating credentials object"
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Yellow
# If not present get O365 cred and store
$cred = Get-Credential -Message "Please enter your Office 365 credentials."
# Export CredObj
$cred | Export-Clixml -Path $credPath
}
else {
# write to the log and screen
$msg = "Importing credentials Object"
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Yellow
$cred = Import-Clixml -Path $credPath
}
# create a template for the emails
$emailTemplate = #"
<html>
<head>
<title>Password Expire Notification</title>
<meta name="generator" content="PowerShell" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
body {
font-family: verdana, arial, helvetica, sans-serif;
font-size: 12px;
}
</style>
</head>
<body>
Dear _NAME_,
<p>Your password will expire in _DAYS_ days.<br /><br />
To change your password on a Windows pc in the office press CTRL-ALT-Delete and choose <strong>Change a password...</strong><br />
</p>
Regards
</body>
</html>
"#
# get all users that are enabled and that have a password expiry date
# test it out on dummy user(s) first of course !
$users = Get-ADUser -Filter * -Properties GivenName, Name, SamAccountName, PasswordNeverExpires, PasswordExpired,
PasswordLastSet, EmailAddress, AccountExpirationDate, accountExpires |
Where-Object { $_.Enabled -eq $true -and $_.PasswordNeverExpires -eq $false}
# get the domains default max password age
$defaultMaxPasswordAge = (Get-ADDefaultDomainpasswordPolicy).MaxPasswordAge
$mailCount = 0
foreach ($user in $users) {
if ([string]::IsNullOrWhiteSpace($emailAddress)) {
# write to the log and screen
$msg = "$userName has no email address to send an e-mail to!"
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Yellow
# skip this user because we cannot send mail..
continue
}
# just for convenience, store some properties in variables
$firstName = $user.GivenName
$userName = $user.Name
$accountName = $user.SamAccountName
$emailAddress = $user.EmailAddress
$passwordSetDate = $user.PasswordLastSet
$passwordPolicy = (Get-AduserResultantpasswordPolicy $user)
# check if there is a 'Fine Grained Password' policy for this user
if ($null -ne $passwordPolicy) {
$maxPasswordAge = ($passwordPolicy).MaxPasswordAge
}
else {
# no 'Fine Grained Password' policy, so use the default domain password age
$maxPasswordAge = $defaultMaxPasswordAge
}
# prevent errors when the 'User must change password at next logon' checkmark is set
if (!$passwordSetDate -or !$maxPasswordAge) {
# write to the log and screen
$msg = "Please check if the 'User must change password at next logon' checkmark is off for user '$userName'"
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Yellow
}
# calculate the expiry date for the password
$passwordExpiresAt = $passwordSetDate + $maxPasswordAge
# check if the account does not expire before the password does using the accountExpires property.
# 0 means the expiration date has been removed. 9223372036854775807 means the account never had an expiration date
if ($user.accountExpires -ne 0 -and $user.accountExpires -ne 9223372036854775807 -and $user.AccountExpirationDate -ne $null) {
if ($user.AccountExpirationDate -le $passwordExpiresAt) {
# skip this user if the account expires before the password needs changing
$msg = "The account for user '$userName' expires before the password needs changing."
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Yellow
continue
}
}
# calculate how many days are left
$daysToExpire = [int](New-TimeSpan -Start (Get-Date) -End $passwordExpiresAt).Days
if (($daysToExpire -ge 0) -and ($daysToExpire -lt $expireInDays)) {
# if there are still days left to change the password, send an email
# using Send-MailMessage rather than System.Net.Mail.SmtpClient
$msg = "Sending expiry notice email to '$userName'"
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Yellow
# use splatting for cmdlets that take a lot of parameters
$params = #{
SmtpServer = $smtpServer
From = $from
To = $emailAddress
Subject = "Your password will expire in $daysToExpire days."
Body = $emailTemplate -replace "_NAME_", $firstName -replace "_DAYS_", $daysToExpire
BodyAsHtml = $true
Encoding = [System.Text.Encoding]::UTF8
Credential = $cred
UseSsl = $true
Priority = 'High'
DeliveryNotificationOption = 'OnSuccess', 'OnFailure'
# Port = 587
}
Send-Mailmessage #params
# update the counter for the users that were sent an email
$mailCount++
}
elseif ($daysToExpire -le 0) {
$msg = "Password for user '$userName' is already expired!"
Add-Content -Path $logFile -Value "$logDate - WARNING: $msg"
Write-Host $msg -ForegroundColor Red
}
}
$msg = "Password expiry notifications have been sent to $mailCount users"
Add-Content -Path $logFile -Value "$logDate - INFO: $msg"
Write-Host $msg -ForegroundColor Green
Note: As always, create a couple of testusers to try this out first. You can use the -Filter parameter on the Get-ADUser cmdlet to only get the testuser or use the -SearchBase parameter and put your test accounts in a special OU.

Today Date and Password Expirary Date less than 14 Days?

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”)}}
}
}

AD Password expiry notification from technet gallery

I try edit powershell password email notification from
- https://gallery.technet.microsoft.com/Password-Expiry-Email-177c3e27
or
- https://blogs.technet.microsoft.com/askpfeplat/2015/05/04/how-to-setup-a-password-expiration-notification-email-solution/
I like to send log in html file and I try add 2 new column $sent, $expireson.
I add 2 columns to get-aduser query but results in this columns is bad.
I don't know what can I do to make the code work properly.
My code below:
$smtpServer="mail.company.com"
$expireindays = 52
$from = "Company Administrator <support#mycompany.com>"
$logging = "Enabled" # Set to Disabled to Disable Logging
$logFile = "C:\scripts\test.html" # ie. c:\mylog.csv
$testing = "Enabled" # Set to Disabled to Email Users
$testRecipient = "testuser#company.com"
$filecontent = (Get-Content $logfile | Out-String)
# System Settings
$textEncoding = [System.Text.Encoding]::UTF8
$date = Get-Date -format ddMMyyyy
# End System Settings
# Get Users From AD who are Enabled, Passwords Expire and are Not Currently Expired
Import-Module ActiveDirectory
$users = get-aduser -filter * -properties Name, ``PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress |
where ($_.Enabled -eq "True") | where {$_.PasswordNeverExpires -eq $false} | where {$_.passwordexpired -eq $false} |
#{N="column1";E= {$expireson}}, #{N="column1";E= {$sent}} |
Convertto-html | out-file $logfile
# Process Each User for Password Expiry
foreach ($user in $users)
{
$Name = $user.Name
$emailaddress = $user.emailaddress
$passwordSetDate = $user.PasswordLastSet
$PasswordPol = (Get-AduserResultantPasswordPolicy $user)
$sent = "" # Reset Sent Flag
# Check for Fine Grained Password
if (($PasswordPol) -ne $null)
{
$maxPasswordAge = ($PasswordPol).MaxPasswordAge
}
else
{
# No FGP set to Domain Default
$maxPasswordAge = $DefaultmaxPasswordAge
}
$expireson = $passwordsetdate + $maxPasswordAge
$today = (get-date)
$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
# Set Greeting based on Number of Days to Expiry.
# Check Number of Days to Expiry
$messageDays = $daystoexpire
if (($messageDays) -gt "1")
{
$messageDays = "in " + "$daystoexpire" + " days."
}
else
{
$messageDays = "today."
}
# Email Subject Set Here
$subject="Your password will expire $messageDays"
# Email Body Set Here, Note You can use HTML, including Images.
$body ="
Dear $name,
<p> Your Password will expire $messageDays<br>
To change your password on a PC press CTRL ALT Delete and choose Change Password <br>
<p>Thanks, <br>
</P>"
# If Testing Is Enabled - Email Administrator
if (($testing) -eq "Enabled")
{
$emailaddress = $testRecipient
} # End Testing
# If a user has no email address listed
if (($emailaddress) -eq $null)
{
$emailaddress = $testRecipient
}# End No Valid Email
# Send Email Message
if (($daystoexpire -ge "0") -and ($daystoexpire -lt $expireindays))
{
$sent = "Yes"
# If Logging is Enabled Log Details
if (($logging) -eq "Enabled")
{
Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson,$sent"
}
# Send Email Message
#Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High -Encoding $textEncoding
} # End Send Message
else # Log Non Expiring Password
{
$sent = "No"
# If Logging is Enabled Log Details
if (($logging) -eq "Enabled")
{
Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson,$sent"
}
}
} # End User Processing
#Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High -Encoding $textEncoding
# End
I fix my issue converting CSV to HTML and add some CSS. Now i have log in html table sending it to email.
$info = Import-Csv $logFile | ConvertTo-Html -head $style
$mailBody =
"
$info
"
Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject test -body $mailBody -bodyasHTML -priority High -Encoding $textEncoding