I’m working on revising and updating a password reminder script that will email users from several different Office 365 accounts if their password is within 14 days of expiring(people ignore the Outlook notifications).
It currently works, but poorly. I want to add HTML reports via email or SMB to an admin as well as much better error handling(and add errors to the html report in case of failed logins). I also want to bring the entire thing in line with best practices and more secure credential handling. Below is my script, any tips or suggestions on how to go about this?
Function PullCredentials { ForEach ($account in $credhash.GetEnumerator()) # Go through each account listed in the INI file. { # Make a PSCredential object out of the gathered credentials Try { $365creds = New-Object System.Management.Automation.PSCredential (($account.Value.user).ToString(), (ConvertTo-SecureString ($account.Value.pass).tostring() -AsPlainText -Force)) Write-Host "Logging in with" $account.Value.user # Connect to Office 365 $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $365creds -Authentication Basic -AllowRedirection Import-PSSession $Session -AllowClobber Import-Module MSOnline Connect-MsolService -credential $365creds # Move to function for gathering users and emailing them. EmailUsers($account) # End Office 365 Connection Remove-PSSession $Session $365creds = $null } Catch { Write-Verbose "Failed to connect to $account.Name" } } } Function EmailUsers($arg1) { # Get Users From 365 who are Enabled, Passwords Expire and are Not Currently Expired Write-Host $arg1.Name $users = Get-MsolUser | where {$_.PasswordNeverExpires -eq $false} $users = $users | where {$_.userprincipalname.endswith($arg1.Name)} $domain = Get-MsolDomain | where {$_.IsDefault -eq $true } $maxPasswordAge = ((Get-MsolPasswordPolicy -DomainName $domain.Name).ValidityPeriod) # If the default password policy is configured(90 days) the previous command returns null. If ($maxPasswordAge -eq $null) { $maxPasswordAge = "90" } $maxPasswordAge = $maxPasswordAge.ToString() foreach ($user in $users) { $Name = $user.DisplayName $emailaddress = $user.UserPrincipalName $passwordSetDate = $user.LastPasswordChangeTimestamp $ExpiresOn = $passwordSetDate + $maxPasswordAge $today = (get-date) $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days #write-host (Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson") # Set Greeting based on Number of Days to Expiry. $messageDays = $daystoexpire if (($messageDays) -ge "1") { $messageDays = "in " + "$daystoexpire" + " days." } else { $messageDays = "today." } # Email Subject Set Here $subject="Your Office 365 password will expire $messageDays" # Email Body Set Here, Note You can use HTML, including Images. $body =" Dear $name, Your Office 365 Password will expire $messageDays. To change your password, please follow the attached instructions. You will need to visit http://mail.office365.com/ to complete this process. You will receive this notice once per day until your password is changed. Thanks, Support Staff Please do not reply to this email." # 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 if (($daystoexpire -ge "0") -and ($daystoexpire -lt $expireindays)) { # If Logging is Enabled Log Details if (($logging) -eq "Enabled") { Add-Content $logfile "$date,$Name,$emailaddress,$daystoExpire,$expireson" } # Send Email Message if (($daystoexpire -ge "0") -and ($daystoexpire -lt $expireindays)) { Write-Host $user.UserPrincipalName "is expiring" Send-Mailmessage -smtpServer $smtpServer -from $from -to $emailaddress -subject $subject -body $body -bodyasHTML -priority High -Attachments $attachments -UseSsl -Port 587 -Credential $SMTPcreds } # End Send Message } # End User Processing } } Function Get-IniContent { <# .Synopsis Gets the content of an INI file .Description Gets the content of an INI file and returns it as a hashtable .Notes Author : Oliver Lipkau Blog : http://oliver.lipkau.net/blog/ Source : https://github.com/lipkau/PsIni http://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91 Version : 1.0 - 2010/03/12 - Initial release 1.1 - 2014/12/11 - Typo (Thx SLDR) Typo (Thx Dave Stiff) #Requires -Version 2.0 .Inputs System.String .Outputs System.Collections.Hashtable .Parameter FilePath Specifies the path to the input file. .Example $FileContent = Get-IniContent "C:\myinifile.ini" ----------- Description Saves the content of the c:\myinifile.ini in a hashtable called $FileContent .Example $inifilepath | $FileContent = Get-IniContent ----------- Description Gets the content of the ini file passed through the pipe into a hashtable called $FileContent .Example C:\PS>$FileContent = Get-IniContent "c:\settings.ini" C:\PS>$FileContent["Section"]["Key"] ----------- Description Returns the key "Key" of the section "Section" from the C:\settings.ini file .Link Out-IniFile #> [CmdletBinding()] Param( [ValidateNotNullOrEmpty()] [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Extension -eq ".ini")})] [Parameter(ValueFromPipeline=$True,Mandatory=$True)] [string]$FilePath ) Begin {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} Process { Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" $ini = @{} switch -regex -file $FilePath { "^\[(.+)\]$" # Section { $section = $matches[1] $ini[$section] = @{} $CommentCount = 0 } "^(;.*)$" # Comment { if (!($section)) { $section = "No-Section" $ini[$section] = @{} } $value = $matches[1] $CommentCount = $CommentCount + 1 $name = "Comment" + $CommentCount $ini[$section][$name] = $value } "(.+?)\s*=\s*(.*)" # Key { if (!($section)) { $section = "No-Section" $ini[$section] = @{} } $name,$value = $matches[1..2] $ini[$section][$name] = $value } } Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" Return $ini } End {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} } $myDir = Split-Path -Parent $MyInvocation.MyCommand.Path Start-Transcript -Path "$myDir\log.txt" $credfile = $myDir + "\credfile.ini" $logging = "Enabled" # Set to Disabled to Disable Logging $logFile = $myDir + "\PasswordExpirations.csv" # ie. c:\mylog.csv $smtpServer="smtp.office365.com" $SMTPpass = ConvertTo-SecureString "atotallylegitpassword" -AsPlainText -Force $SMTPcreds = New-Object System.Management.Automation.PSCredential ("Expirations@myorgs-site.com", $SMTPpass) $date = Get-Date -format ddMMyyyy $expireindays = 7 $credhash = Get-IniContent $credfile $testRecipient = "expirations@myorgs-site.com" $testing = "Enabled" # Set to Disabled to Email Users $from = "Password Expirations " $attachments = $myDir + "\Password Change Walkthrough v0.1.docx" if (($logging) -eq "Enabled") { # Test Log File Path $logfilePath = (Test-Path $logFile) if (($logFilePath) -ne "True") { # Create CSV File and Headers New-Item $logfile -ItemType File Add-Content $logfile "Date,Name,EmailAddress,DaystoExpire,ExpiresOn" } } # End Logging Check PullCredentials Stop-Transcript